Disclaimer: I'm new to Blender Python. Thank you in advance for your assistance, and patience. I saw this related question but I'm not sure it applies here.
I am developing an addon with a panel class that doesn't unregister when I disable the addon in blender preferences. Not even when I remove the addon. The panel won't be removed unless I restart blender after disabling/removing the addon. So even after removing the addon, I'm left with this panel until I restart blender:
I'm not sure what blender is looking for on the back end when enabling and disabling addons, and having a look at blender's documentation hasn't cleared things up for me. So I'm hoping the wonderful community can shed some light here. What am I doing wrong?
Here's the code
bl_info = {
"Cam-Shape-Matic" : "CamShpMtc",
"category": "3D View",
"author": "BeyondDev",
"blender": (2, 8, 0),
"version": (0, 0, 1),
"category": "Object",
"description": "Drive Shapekeys on Objects based on Relative Camera Angle.",
"doc_url": "",
"tracker_url": "",
}
from asyncio.windows_events import NULL
import bpy
import mathutils
To workaround the "known bug with using a callback" mentioned
in the EnumProperty docs, this function needs to be called on
any EnumProperty's items.
ENUM_STRING_CACHE = {}
def intern_enum_items_strings(items):
def intern_string(s):
if isinstance(s, str):
ENUM_STRING_CACHE.setdefault(s, s)
s = ENUM_STRING_CACHE[s]
return s
return [
tuple([intern_string(s) for s in item])
for item in items
]
This callback will return a list of enum items in the usual
[(identifier, name, description), ...] format.
def my_shapekey_enum_items_callback(self, context):
items = []
if self and self.shape_keys:
items = [
(kb.name, kb.name, '')
for i, kb in enumerate(self.shape_keys.key_blocks)
]
return intern_enum_items_strings(items)
This callback will return a list of enum items in the usual
[(identifier, name, description), ...] format.
def my_object_enum_items_callback(self, context):
items = []
if bpy.context.window and bpy.context.window.view_layer:
items = [
(ob.name, ob.name, '')
for i, ob in enumerate(bpy.context.window.view_layer.objects) if bpy.context.window.view_layer.objects[i].type == 'MESH'
]
return intern_enum_items_strings(items)
def my_bone_enum_items_callback(self, context):
items = []
selObj = bpy.context.window_manager.object
selArmatureParse = bpy.data.objects[selObj].find_armature().name.split('_') #parse returned name 'Object_rig' into list [Object, rig]
selArmature = selArmatureParse[len(selArmatureParse)-1] #get name of rig from last item in parsed list (above)
if bpy.context.window and bpy.context.window.view_layer and selArmature:
items = [
(ob.name, ob.name, '')
for i, ob in enumerate(bpy.data.armatures[selArmature].bones) if bpy.data.armatures[selArmature].bones[i].use_deform == True
]
return intern_enum_items_strings(items)
class CamShapeMaticPanel(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'CamShpMtc'
bl_label = "CamShapeMatic"
bl_idname = "MAIN_PT_camshapematic_panel"
def draw(self, context):
layout = self.layout
box = layout.box()
col = box.column(align=True)
row = col.row(align=True)
col.separator()
row = col.row(align=True)
row.scale_y = 1.1
row.prop(context.window_manager, "EnableCamShapeMatic", text="Enable/Disable Shape Changes")
scene = context.scene
col.separator()
row = col.row(align=True)
row.label(text='Select Camera')
row = col.row(align=True)
row.scale_y = 1.1
row.prop(scene, "camera")
row = col.row(align=True)
row.scale_y = 1.1
row.prop(context.window_manager, "AngleSensitivity", text="Cam Angle Sensitivity")
col.separator()
row = col.row(align=True)
row.label(text='Select Object')
row = col.row(align=True)
row.scale_y = 1.1
row.prop(context.window_manager, "object", icon="MESH_MONKEY", text='Mesh')
selObj = bpy.context.window_manager.object
AngleSens = bpy.context.window_manager.AngleSensitivity
Enable = bpy.context.window_manager.EnableCamShapeMatic
# draw shapekey_top/bottom/left/right for the current object
# only if it's a mesh with shapekeys though
if (
bpy.data and
bpy.data.objects and
bpy.data.objects[selObj].type == 'MESH' and
bpy.data.objects[selObj].data.shape_keys
):
col.separator()
row = col.row(align=True)
row.label(text='Select Shapekeys')
row = col.row(align=True)
row.scale_y = 1.1
row.prop(bpy.data.objects[selObj].data, "shapekey_top", icon='SHAPEKEY_DATA', text='Top')
row = col.row(align=True)
row.scale_y = 1.1
row.prop(bpy.data.objects[selObj].data, "shapekey_bottom", icon='SHAPEKEY_DATA', text='Bottom')
row = col.row(align=True)
row.scale_y = 1.1
row.prop(bpy.data.objects[selObj].data, "shapekey_left", icon='SHAPEKEY_DATA', text='Left')
row = col.row(align=True)
row.scale_y = 1.1
row.prop(bpy.data.objects[selObj].data, "shapekey_right", icon='SHAPEKEY_DATA', text='Right')
# If the selected object has an armature
if (
bpy.data and
bpy.data.objects and
bpy.data.objects[selObj].find_armature()
):
col.separator()
row = col.row(align=True)
row.label(text='Select Bone (ex: Head Bone)')
row = col.row(align=True)
row.scale_y = 1.1
row.prop(context.window_manager, "bone", icon='BONE_DATA', text='Deform Bone')
row = col.row(align=True)
# only if it's a mesh with shapekeys though
if (
bpy.data and
bpy.data.objects and
bpy.data.objects[selObj].type == 'MESH' and
bpy.data.objects[selObj].data.shape_keys and
Enable == True
):
if bpy.data.objects[selObj].data.shapekey_top != NULL:
cam = bpy.context.scene.camera
obj = bpy.data.objects[selObj]
if (
bpy.data and
bpy.data.objects and
bpy.data.objects[selObj].find_armature()
):
selArmature = bpy.data.objects[selObj].find_armature().name
rot = bpy.data.objects[selArmature].pose.bones[bpy.context.window_manager.bone]
self.obj_eul = rot.matrix.to_euler('XYZ') #this works for an obj OR a bone, world space
self.loc = rot.matrix.to_translation()
else:
rot = bpy.data.objects[selObj]
self.obj_eul = rot.rotation_euler
self.loc = rot.location
def drive_shapekey(scene, post):
try:
del line
except:
pass
objKey = obj.data.shape_keys #bpy.data.shapekeys["Key.00_"]
if Enable == True:
#LEFT -----------------------------------------------------
#obj_eul = rot.rotation_euler
cam_vec = self.loc - cam.matrix_world.to_translation()
cam_vec = cam_vec.normalized()*mathutils.Vector((1.0,1.0,0.0))
obj_vec = mathutils.Vector((-1.0, 0.0, 0.0))
obj_vec.rotate(self.obj_eul)
difference = cam_vec.dot(obj_vec)
shapekey_Name = obj.data.shapekey_left #Name of shapekey
key_left = objKey.key_blocks[shapekey_Name] #Datapath ending for shapekey value
key_left.value = difference*AngleSens
#RIGHT -----------------------------------------------------
#obj_eul = rot.rotation_euler
cam_vec = self.loc - cam.matrix_world.to_translation()
cam_vec = cam_vec.normalized()*mathutils.Vector((1.0,1.0,0.0))
obj_vec = mathutils.Vector((1.0, 0.0, 0.0))
obj_vec.rotate(self.obj_eul)
difference = cam_vec.dot(obj_vec)
shapekey_Name = obj.data.shapekey_right #Name of shapekey
key_right = objKey.key_blocks[shapekey_Name] #Datapath ending for shapekey value
key_right.value = difference*AngleSens
#TOP -----------------------------------------------------
#obj_eul = rot.rotation_euler
cam_vec = self.loc - cam.matrix_world.to_translation()
cam_vec = cam_vec.normalized()*mathutils.Vector((0.0,1.0,1.0))
obj_vec = mathutils.Vector((0.0, -1.0, 0.0)) #without rotate math below, this should be (0.0, 0.0, -1.0)
obj_vec.rotate(self.obj_eul)
difference = cam_vec.dot(obj_vec)
shapekey_Name = obj.data.shapekey_top #Name of shapekey
key_top = objKey.key_blocks[shapekey_Name] #Datapath ending for shapekey value
key_top.value = difference*AngleSens - key_right.value - key_left.value
line = [self.loc, cam.location]
#BOTTOM -----------------------------------------------------
#obj_eul = rot.rotation_euler
cam_vec = self.loc - cam.matrix_world.to_translation()
cam_vec = cam_vec.normalized()*mathutils.Vector((0.0,1.0,1.0))
obj_vec = mathutils.Vector((0.0, 1.0, 0.0)) #without rotate math below, this should be (0.0, 0.0, 1.0)
obj_vec.rotate(self.obj_eul)
difference = cam_vec.dot(obj_vec)
shapekey_Name = obj.data.shapekey_bottom #Name of shapekey
key_bottom = objKey.key_blocks[shapekey_Name] #Datapath ending for shapekey value
key_bottom.value = difference*AngleSens - key_right.value - key_left.value
pre_handlers_scene = bpy.app.handlers.depsgraph_update_pre #Drive Shapekey on schene updates
pre_handlers_anim = bpy.app.handlers.frame_change_pre #Drive shapekey before frame changes
pre_handlers_render = bpy.app.handlers.render_pre #Drive shapekey during renders
[pre_handlers_scene.remove(h) for h in pre_handlers_scene if h.__name__ == "drive_shapekey"]
pre_handlers_scene.append(drive_shapekey)
[pre_handlers_anim.remove(h) for h in pre_handlers_anim if h.__name__ == "drive_shapekey"]
pre_handlers_anim.append(drive_shapekey)
[pre_handlers_render.remove(h) for h in pre_handlers_render if h.__name__ == "drive_shapekey"]
pre_handlers_render.append(drive_shapekey)
classes = [CamShapeMaticPanel]
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.WindowManager.object = bpy.props.EnumProperty(
name="Selected Object",
description="Select an object",
items=my_object_enum_items_callback,
)
bpy.types.WindowManager.bone = bpy.props.EnumProperty(
name="Selected Bone",
description="Select a bone",
items=my_bone_enum_items_callback,
)
bpy.types.Mesh.shapekey_top = bpy.props.EnumProperty(
name="Top Shapekey",
description="Select a shapekey",
items=my_shapekey_enum_items_callback,
)
bpy.types.Mesh.shapekey_bottom = bpy.props.EnumProperty(
name="Bottom Shapekey",
description="Select a shapekey",
items=my_shapekey_enum_items_callback,
)
bpy.types.Mesh.shapekey_left = bpy.props.EnumProperty(
name="Left Shapekey",
description="Select a shapekey",
items=my_shapekey_enum_items_callback,
)
bpy.types.Mesh.shapekey_right = bpy.props.EnumProperty(
name="Right Shapekey",
description="Select a shapekey",
items=my_shapekey_enum_items_callback,
)
bpy.types.WindowManager.AngleSensitivity = bpy.props.FloatProperty(default=1.5, max=2.0, min=1.0)
bpy.types.WindowManager.EnableCamShapeMatic = bpy.props.BoolProperty(default=True)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
del bpy.types.WindowManager.object
del bpy.types.WindowManager.bone
del bpy.types.Mesh.shapekey_top
del bpy.types.Mesh.shapekey_bottom
del bpy.types.Mesh.shapekey_left
del bpy.types.Mesh.shapekey_right
del bpy.types.WindowManager.AngleSensitivity
del bpy.types.WindowManager.EnableCamShapeMatic
if name == "main" or name == "CamShapeMatic":
register()

bpy.types...custom properties definitions in both the register and unregister withdel bpy.types....otherwise I think you'll still have a bunch of custom properties associated with your objects after disabling the addon – Gorgious Apr 11 '22 at 08:29