Files
blender-portable-repo/scripts/addons/boneWidget-boneWidget_0_2_1/functions/mainFunctions.py
T
2026-03-17 14:30:01 -06:00

483 lines
16 KiB
Python

import bpy
import numpy
from mathutils import Matrix
from .. import __package__
def getCollection(context):
#check user preferences for the name of the collection
if not context.preferences.addons[__package__].preferences.use_rigify_defaults:
bw_collection_name = context.preferences.addons[__package__].preferences.bonewidget_collection_name
else:
bw_collection_name = "WGTS_" + context.active_object.name
collection = recurLayerCollection(context.scene.collection, bw_collection_name)
if collection: # if it already exists
return collection
collection = bpy.data.collections.get(bw_collection_name)
if collection: # if it exists but not linked to scene
context.scene.collection.children.link(collection)
return collection
else: # create a new collection
collection = bpy.data.collections.new(bw_collection_name)
context.scene.collection.children.link(collection)
# hide new collection
viewlayer_collection = context.view_layer.layer_collection.children[collection.name]
viewlayer_collection.hide_viewport = True
return collection
def recurLayerCollection(layer_collection, collection_name):
found = None
if (layer_collection.name == collection_name):
return layer_collection
for layer in layer_collection.children:
found = recurLayerCollection(layer, collection_name)
if found:
return found
def getViewLayerCollection(context, widget = None):
widget_collection = bpy.data.collections[bpy.data.objects[widget.name].users_collection[0].name]
#save current active layer_collection
saved_layer_collection = bpy.context.view_layer.layer_collection
# actually find the view_layer we want
layer_collection = recurLayerCollection(saved_layer_collection, widget_collection.name)
# make sure the collection (data level) is not hidden
widget_collection.hide_viewport = False
# change the active view layer
bpy.context.view_layer.active_layer_collection = layer_collection
# make sure it isn't excluded so it can be edited
layer_collection.exclude = False
#return the active view layer to what it was
bpy.context.view_layer.active_layer_collection = saved_layer_collection
return layer_collection
def boneMatrix(widget, matchBone):
if widget == None:
return
widget.matrix_local = matchBone.bone.matrix_local
widget.matrix_world = matchBone.id_data.matrix_world @ matchBone.bone.matrix_local
if matchBone.custom_shape_transform:
#if it has a tranform override, apply this to the widget loc and rot
org_scale = widget.matrix_world.to_scale()
org_scale_mat = Matrix.Scale(1, 4, org_scale)
target_matrix = matchBone.custom_shape_transform.id_data.matrix_world @ matchBone.custom_shape_transform.bone.matrix_local
loc = target_matrix.to_translation()
loc_mat = Matrix.Translation(loc)
rot = target_matrix.to_euler().to_matrix()
widget.matrix_world = loc_mat @ rot.to_4x4() @ org_scale_mat
if matchBone.use_custom_shape_bone_size:
ob_scale = bpy.context.scene.objects[matchBone.id_data.name].scale
widget.scale = [matchBone.bone.length * ob_scale[0], matchBone.bone.length * ob_scale[1], matchBone.bone.length * ob_scale[2]]
# if the user has added any custom transforms to the bone widget display - calculate this too
loc = matchBone.custom_shape_translation
rot = matchBone.custom_shape_rotation_euler
scale = matchBone.custom_shape_scale_xyz
widget.scale *= scale
widget.matrix_world = widget.matrix_world @ Matrix.LocRotScale(loc , rot, widget.scale)
widget.data.update()
def fromWidgetFindBone(widget):
matchBone = None
for ob in bpy.context.scene.objects:
if ob.type == "ARMATURE":
for bone in ob.pose.bones:
if bone.custom_shape == widget:
matchBone = bone
return matchBone
def createWidget(bone, widget, relative, size, scale, slide, rotation, collection, use_face_data, wireframe_width):
C = bpy.context
D = bpy.data
if not C.preferences.addons[__package__].preferences.use_rigify_defaults:
bw_widget_prefix = C.preferences.addons[__package__].preferences.widget_prefix
else:
bw_widget_prefix = "WGT-" + C.active_object.name + "_"
matrixBone = bone
# delete the existing shape
if bone.custom_shape:
bpy.data.objects.remove(bpy.data.objects[bone.custom_shape.name], do_unlink=True)
# make the data name include the prefix
newData = D.meshes.new(bw_widget_prefix + bone.name)
bone.use_custom_shape_bone_size = relative
# deal with face data
faces = widget['faces'] if use_face_data else []
# add the verts
newData.from_pydata(numpy.array(widget['vertices']) * [size[0] * scale[0], size[1] * scale[2],
size[2] * scale[1]], widget['edges'], faces)
# Create tranform matrices (slide vector and rotation)
widget_matrix = Matrix()
trans = Matrix.Translation(slide)
rot = rotation.to_matrix().to_4x4()
# Translate then rotate the matrix
widget_matrix = widget_matrix @ trans
widget_matrix = widget_matrix @ rot
# transform the widget with this matrix
newData.transform(widget_matrix)
newData.update(calc_edges=True)
newObject = D.objects.new(bw_widget_prefix + bone.name, newData)
newObject.data = newData
newObject.name = bw_widget_prefix + bone.name
collection.objects.link(newObject)
newObject.matrix_world = bpy.context.active_object.matrix_world @ matrixBone.bone.matrix_local
newObject.scale = [matrixBone.bone.length, matrixBone.bone.length, matrixBone.bone.length]
layer = bpy.context.view_layer
layer.update()
bone.custom_shape = newObject
bone.bone.show_wire = not use_face_data # show faces if use face data is enabled
if bpy.app.version >= (4,2,0):
bone.custom_shape_wire_width = wireframe_width
def symmetrizeWidget(bone, collection):
C = bpy.context
D = bpy.data
if not C.preferences.addons[__package__].preferences.use_rigify_defaults:
bw_widget_prefix = C.preferences.addons[__package__].preferences.widget_prefix
rigify_object_name = ''
else:
bw_widget_prefix = "WGT-"
rigify_object_name = C.active_object.name + "_"
widget = bone.custom_shape
if findMirrorObject(bone):
mirrorBone = findMirrorObject(bone)
mirrorWidget = mirrorBone.custom_shape
if mirrorWidget is not None:
if mirrorWidget != widget:
if C.scene.objects.get(mirrorWidget.name):
D.objects.remove(mirrorWidget)
newData = widget.data.copy()
for vert in newData.vertices:
vert.co = numpy.array(vert.co) * (-1, 1, 1)
newObject = widget.copy()
newObject.data = newData
newData.update()
newObject.name = bw_widget_prefix + rigify_object_name + findMirrorObject(bone).name
D.collections[collection.name].objects.link(newObject)
#if there is a override transform, use that bone matrix in the next step
if findMirrorObject(bone).custom_shape_transform:
mirrorBone = findMirrorObject(bone).custom_shape_transform
newObject.matrix_local = mirrorBone.bone.matrix_local
newObject.scale = [mirrorBone.bone.length, mirrorBone.bone.length, mirrorBone.bone.length]
newObject.data.flip_normals()
layer = bpy.context.view_layer
layer.update()
findMirrorObject(bone).custom_shape = newObject
mirrorBone.bone.show_wire = bone.bone.show_wire
if bpy.app.version >= (4,2,0):
mirrorBone.custom_shape_wire_width = bone.custom_shape_wire_width
else:
pass
def symmetrizeWidget_helper(bone, collection, activeObject, widgetsAndBones):
C = bpy.context
bw_symmetry_suffix = C.preferences.addons[__package__].preferences.symmetry_suffix
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
if activeObject.name.endswith(suffix_1):
if bone.name.endswith(suffix_1) and widgetsAndBones[bone]:
symmetrizeWidget(bone, collection)
elif activeObject.name.endswith(suffix_2):
if bone.name.endswith(suffix_2) and widgetsAndBones[bone]:
symmetrizeWidget(bone, collection)
def deleteUnusedWidgets():
C = bpy.context
D = bpy.data
if not C.preferences.addons[__package__].preferences.use_rigify_defaults:
bw_collection_name = C.preferences.addons[__package__].preferences.bonewidget_collection_name
else:
bw_collection_name = 'WGTS_' + C.active_object.name
collection = recurLayerCollection(C.scene.collection, bw_collection_name)
widgetList = []
for ob in D.objects:
if ob.type == 'ARMATURE':
for bone in ob.pose.bones:
if bone.custom_shape:
widgetList.append(bone.custom_shape)
unwantedList = [
ob for ob in collection.all_objects if ob not in widgetList]
# save the current context mode
mode = C.mode
# jump into object mode
bpy.ops.object.mode_set(mode='OBJECT')
# delete unwanted widgets
for ob in unwantedList:
bpy.data.objects.remove(bpy.data.objects[ob.name], do_unlink=True)
# jump back to current mode
bpy.ops.object.mode_set(mode=mode)
return
def editWidget(active_bone):
C = bpy.context
D = bpy.data
widget = active_bone.custom_shape
collection = getViewLayerCollection(C, widget)
collection.hide_viewport = False
# hide all other objects in collection
for obj in collection.collection.all_objects:
if obj.name != widget.name:
obj.hide_set(True)
else:
obj.hide_set(False) # in case user manually hid it
armature = active_bone.id_data
bpy.ops.object.mode_set(mode='OBJECT')
C.active_object.select_set(False)
if C.space_data.local_view:
bpy.ops.view3d.localview()
# select object and make it active
widget.select_set(True)
bpy.context.view_layer.objects.active = widget
bpy.ops.object.mode_set(mode='EDIT')
bpy.context.tool_settings.mesh_select_mode = (True, False, False) # enter vertex mode
def returnToArmature(widget):
C = bpy.context
D = bpy.data
bone = fromWidgetFindBone(widget)
armature = bone.id_data
if C.active_object.mode == 'EDIT':
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
collection = getViewLayerCollection(C, widget)
collection.hide_viewport = True
# unhide all objects in the collection
for obj in collection.collection.all_objects:
obj.hide_set(False)
if C.space_data.local_view:
bpy.ops.view3d.localview()
bpy.context.view_layer.objects.active = armature
armature.select_set(True)
bpy.ops.object.mode_set(mode='POSE')
armature.data.bones[bone.name].select = True
armature.data.bones.active = armature.data.bones[bone.name]
def findMirrorObject(object):
C = bpy.context
D = bpy.data
bw_symmetry_suffix = C.preferences.addons[__package__].preferences.symmetry_suffix
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
if object.name.endswith(suffix_1):
suffix = suffix_2
suffix_length = len(suffix_1)
elif object.name.endswith(suffix_2):
suffix = suffix_1
suffix_length = len(suffix_2)
elif object.name.endswith(suffix_1.lower()):
suffix = suffix_2.lower()
suffix_length = len(suffix_1)
elif object.name.endswith(suffix_2.lower()):
suffix = suffix_1.lower()
suffix_length = len(suffix_2)
else: # what if the widget ends in .001?
print('Object suffix unknown, using blank')
suffix = ''
objectName = list(object.name)
objectBaseName = objectName[:-suffix_length]
mirroredObjectName = "".join(objectBaseName) + suffix
if object.id_data.type == 'ARMATURE':
return object.id_data.pose.bones.get(mirroredObjectName)
else:
return bpy.context.scene.objects.get(mirroredObjectName)
def findMatchBones():
C = bpy.context
D = bpy.data
bw_symmetry_suffix = C.preferences.addons[__package__].preferences.symmetry_suffix
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
widgetsAndBones = {}
if bpy.context.object.type == 'ARMATURE':
for bone in C.selected_pose_bones:
if bone.name.endswith(suffix_1) or bone.name.endswith(suffix_2):
widgetsAndBones[bone] = bone.custom_shape
mirrorBone = findMirrorObject(bone)
if mirrorBone:
widgetsAndBones[mirrorBone] = mirrorBone.custom_shape
armature = bpy.context.object
activeObject = C.active_pose_bone
else:
for shape in C.selected_objects:
bone = fromWidgetFindBone(shape)
if bone.name.endswith(("L","R")):
widgetsAndBones[fromWidgetFindBone(shape)] = shape
mirrorShape = findMirrorObject(shape)
if mirrorShape:
widgetsAndBones[mirrorShape] = mirrorShape
activeObject = fromWidgetFindBone(C.object)
armature = activeObject.id_data
return (widgetsAndBones, activeObject, armature)
def resyncWidgetNames():
C = bpy.context
D = bpy.data
if not C.preferences.addons[__package__].preferences.use_rigify_defaults:
bw_collection_name = C.preferences.addons[__package__].preferences.bonewidget_collection_name
bw_widget_prefix = C.preferences.addons[__package__].preferences.widget_prefix
else:
bw_collection_name = 'WGTS_' + C.active_object.name
bw_widget_prefix = 'WGT-' + C.active_object.name + '_'
widgetsAndBones = {}
if bpy.context.object.type == 'ARMATURE':
for bone in C.active_object.pose.bones:
if bone.custom_shape:
widgetsAndBones[bone] = bone.custom_shape
for k, v in widgetsAndBones.items():
if k.name != (bw_widget_prefix + k.name):
D.objects[v.name].name = str(bw_widget_prefix + k.name)
def clearBoneWidgets():
C = bpy.context
D = bpy.data
if bpy.context.object.type == 'ARMATURE':
for bone in C.selected_pose_bones:
if bone.custom_shape:
bone.custom_shape = None
bone.custom_shape_transform = None
def addObjectAsWidget(context, collection):
selected_objects = bpy.context.selected_objects
if len(selected_objects) != 2:
print('Only a widget object and the pose bone(s)')
return{'FINISHED'}
allowed_object_types = ['MESH','CURVE']
widget_object = None
for ob in selected_objects:
if ob.type in allowed_object_types:
widget_object = ob
if widget_object:
active_bone = context.active_pose_bone
# deal with any existing shape
if active_bone.custom_shape:
bpy.data.objects.remove(bpy.data.objects[active_bone.custom_shape.name], do_unlink=True)
#duplicate shape
widget = widget_object.copy()
widget.data = widget.data.copy()
# reamame it
bw_widget_prefix = context.preferences.addons[__package__].preferences.widget_prefix
widget_name = bw_widget_prefix + active_bone.name
widget.name = widget_name
widget.data.name = widget_name
# link it
collection.objects.link(widget)
# match transforms
widget.matrix_world = bpy.context.active_object.matrix_world @ active_bone.bone.matrix_local
widget.scale = [active_bone.bone.length, active_bone.bone.length, active_bone.bone.length]
layer = bpy.context.view_layer
layer.update()
active_bone.custom_shape = widget
active_bone.bone.show_wire = True
#deselect original object
widget_object.select_set(False)
def advanced_options_toggled(self, context):
if self.advanced_options:
self.global_size_advanced = (self.global_size_simple,) * 3
self.slide_advanced[1] = self.slide_simple
else:
self.global_size_simple = self.global_size_advanced[1]
self.slide_simple = self.slide_advanced[1]