2022-10-28 23:23:06 +02:00
|
|
|
|
|
|
|
import bpy
|
|
|
|
from .transform_utils import get_bone_matrix, transform_matrix
|
2023-01-18 16:02:42 +01:00
|
|
|
from mathutils import Matrix
|
2022-10-28 23:23:06 +02:00
|
|
|
#from bone_widget import ctx
|
|
|
|
|
|
|
|
|
|
|
|
def rename_shape(shape, bone, prefix=''):
|
|
|
|
shape.name = shape.data.name = prefix + bone.name
|
|
|
|
#print('Rename Shape', bone, shape.name)
|
|
|
|
|
|
|
|
def get_bone_from_shape(shape):
|
|
|
|
armatures = [o for o in bpy.context.scene.objects if o.type == 'ARMATURE']
|
|
|
|
return next((b for a in armatures for b in a.pose.bones if b.custom_shape is shape), None)
|
|
|
|
|
|
|
|
'''
|
|
|
|
def set_shape(bone, shape):
|
|
|
|
shape.matrix_world = get_bone_matrix(bone)
|
|
|
|
bone.custom_shape = shape
|
|
|
|
ctx.rename_shape(shape, bone)
|
|
|
|
ctx.link_to_widget_col(shape)
|
|
|
|
'''
|
|
|
|
|
|
|
|
def get_side(name, fallback=None):
|
|
|
|
name = name.lower()
|
|
|
|
if name.endswith(('.l', '_l')) or name.startswith(('l.', 'l_')):
|
|
|
|
return 'LEFT'
|
|
|
|
elif name.endswith(('.r', '_r')) or name.startswith(('r.', 'r_')):
|
|
|
|
return 'RIGHT'
|
|
|
|
|
|
|
|
return fallback
|
|
|
|
|
|
|
|
def get_flipped_name(name):
|
|
|
|
import re
|
|
|
|
|
|
|
|
def flip(match, start=False):
|
|
|
|
if not match.group(1) or not match.group(2):
|
|
|
|
return
|
|
|
|
|
|
|
|
sides = {
|
|
|
|
'R' : 'L',
|
|
|
|
'r' : 'l',
|
|
|
|
'L' : 'R',
|
|
|
|
'l' : 'r',
|
|
|
|
}
|
|
|
|
|
|
|
|
if start:
|
|
|
|
side, sep = match.groups()
|
|
|
|
return sides[side] + sep
|
|
|
|
else:
|
|
|
|
sep, side, num = match.groups()
|
|
|
|
return sep + sides[side] + (num or '')
|
|
|
|
|
|
|
|
start_reg = re.compile(r'(l|r)([._-])', flags=re.I)
|
|
|
|
|
|
|
|
if start_reg.match(name):
|
|
|
|
flipped_name = start_reg.sub(lambda x: flip(x, True), name)
|
|
|
|
else:
|
|
|
|
flipped_name = re.sub(r'([._-])(l|r)(\.\d+)?$', flip, name, flags=re.I)
|
|
|
|
|
|
|
|
return flipped_name
|
|
|
|
|
|
|
|
def get_flipped_bone(bone):
|
|
|
|
flipped_name = get_flipped_name(bone.name)
|
|
|
|
|
|
|
|
if flipped_name == bone.name:
|
|
|
|
return
|
|
|
|
|
|
|
|
return bone.id_data.pose.bones.get(flipped_name)
|
|
|
|
|
|
|
|
def link_to_col(shape, col):
|
|
|
|
for c in shape.users_collection:
|
|
|
|
c.objects.unlink(shape)
|
|
|
|
|
|
|
|
col.objects.link(shape)
|
|
|
|
|
2023-01-18 16:02:42 +01:00
|
|
|
def custom_shape_matrix(bone):
|
|
|
|
loc = bone.custom_shape_translation.copy()
|
|
|
|
rot = bone.custom_shape_rotation_euler.copy()
|
|
|
|
scale = bone.custom_shape_scale_xyz.copy()
|
|
|
|
|
|
|
|
if bone.use_custom_shape_bone_size:
|
|
|
|
loc /= bone.bone.length
|
|
|
|
else:
|
|
|
|
scale /= bone.bone.length
|
|
|
|
|
|
|
|
return Matrix.LocRotScale(loc, rot, scale)
|
|
|
|
|
2023-06-06 12:04:20 +02:00
|
|
|
def apply_custom_shape_transform(bone):
|
|
|
|
shape = bone.custom_shape
|
|
|
|
|
|
|
|
if not shape:
|
|
|
|
return
|
|
|
|
|
|
|
|
if bpy.app.version_string < '3.0.0':
|
|
|
|
scale = bone.custom_shape_scale
|
|
|
|
|
|
|
|
if round(scale, 5) == 0:
|
|
|
|
return
|
|
|
|
|
|
|
|
if not bone.use_custom_shape_bone_size:
|
|
|
|
scale /= bone.bone.length
|
|
|
|
|
|
|
|
mat = transform_matrix(scale=(1/scale,)*3)
|
|
|
|
|
|
|
|
shape.data.transform(mat)
|
|
|
|
bone.custom_shape_scale = 1
|
|
|
|
|
|
|
|
else:
|
|
|
|
mat = custom_shape_matrix(bone)
|
|
|
|
|
|
|
|
scale = (1, 1, 1)
|
|
|
|
if round(mat.to_scale().length, 5) == 0:
|
|
|
|
loc, rot, scale = mat.decompose()
|
|
|
|
mat = Matrix.LocRotScale(loc, rot, (1, 1, 1))
|
|
|
|
scale = bone.custom_shape_scale_xyz
|
|
|
|
|
|
|
|
shape.data.transform(mat)
|
|
|
|
|
|
|
|
bone.custom_shape_translation = (0, 0, 0)
|
|
|
|
bone.custom_shape_rotation_euler = (0, 0, 0)
|
|
|
|
bone.custom_shape_scale_xyz = scale
|
|
|
|
|
2022-10-28 23:23:06 +02:00
|
|
|
def get_clean_shape(bone, shape, separate=True, rename=True,
|
2023-01-18 16:02:42 +01:00
|
|
|
col=None, match=True, prefix='', apply_transforms=True):
|
|
|
|
|
|
|
|
#bone.custom_shape = None
|
|
|
|
|
|
|
|
#Remove bone shape if no other bone are using it
|
|
|
|
if bone.custom_shape:
|
|
|
|
old_bones = get_bones(bone.custom_shape)
|
|
|
|
old_bones.remove(bone)
|
2022-10-28 23:23:06 +02:00
|
|
|
|
2023-01-18 16:02:42 +01:00
|
|
|
print('old_bones', old_bones)
|
|
|
|
|
|
|
|
if old_bones:
|
|
|
|
rename_shape(bone.custom_shape, old_bones[0], prefix=prefix)
|
|
|
|
elif bone.custom_shape != shape:
|
|
|
|
bpy.data.objects.remove(bone.custom_shape)
|
2022-10-28 23:23:06 +02:00
|
|
|
|
|
|
|
if separate:
|
|
|
|
shape = shape.copy()
|
|
|
|
shape.data = shape.data.copy()
|
|
|
|
|
|
|
|
bone.custom_shape = shape
|
|
|
|
|
|
|
|
if apply_transforms:
|
2023-06-06 12:04:20 +02:00
|
|
|
apply_custom_shape_transform(bone)
|
2022-10-28 23:23:06 +02:00
|
|
|
bone.use_custom_shape_bone_size = True
|
|
|
|
|
2023-01-18 16:02:42 +01:00
|
|
|
if match:
|
|
|
|
shape.matrix_world = get_bone_matrix(bone)
|
|
|
|
|
2022-10-28 23:23:06 +02:00
|
|
|
if rename:
|
|
|
|
rename_shape(shape, bone, prefix=prefix)
|
|
|
|
|
|
|
|
if col:
|
|
|
|
link_to_col(shape, col)
|
|
|
|
|
|
|
|
return shape
|
|
|
|
|
|
|
|
|
2023-01-18 16:02:42 +01:00
|
|
|
def get_bones(shape):
|
2022-10-28 23:23:06 +02:00
|
|
|
armatures = [o for o in bpy.context.scene.objects if o.type == 'ARMATURE']
|
2023-01-18 16:02:42 +01:00
|
|
|
return [b for a in armatures for b in a.pose.bones if b.custom_shape == shape]
|
2022-10-28 23:23:06 +02:00
|
|
|
|
2023-01-18 16:02:42 +01:00
|
|
|
def get_bone(shape):
|
|
|
|
bones = get_bones(shape)
|
|
|
|
if bones:
|
|
|
|
return bones[0]
|
2022-10-28 23:23:06 +02:00
|
|
|
|
|
|
|
def symmetrize_bone_shape(bone, prefix=None):
|
|
|
|
active_side = get_side(bone.name, 'LEFT')
|
|
|
|
|
|
|
|
shape = bone.custom_shape
|
|
|
|
flipped_bone = get_flipped_bone(bone)
|
|
|
|
|
|
|
|
if not flipped_bone or not shape:
|
|
|
|
return
|
|
|
|
|
|
|
|
if bpy.app.version_string < '3.0.0':
|
|
|
|
flipped_bone.custom_shape_scale = b.custom_shape_scale
|
|
|
|
else:
|
|
|
|
flipped_bone.custom_shape_translation = bone.custom_shape_translation
|
|
|
|
flipped_bone.custom_shape_rotation_euler = bone.custom_shape_rotation_euler
|
|
|
|
flipped_bone.custom_shape_scale_xyz = bone.custom_shape_scale_xyz
|
|
|
|
|
|
|
|
flipped_shape = flipped_bone.custom_shape
|
|
|
|
|
|
|
|
if flipped_shape:
|
|
|
|
flipped_bone.custom_shape = None
|
|
|
|
|
|
|
|
old_bone = get_bone(flipped_shape)
|
|
|
|
if old_bone:
|
|
|
|
rename_shape(flipped_shape, old_bone, prefix=prefix)
|
|
|
|
else:
|
|
|
|
bpy.data.objects.remove(flipped_shape)
|
|
|
|
|
|
|
|
flipped_shape = shape.copy()
|
|
|
|
flipped_shape.data = flipped_shape.data.copy()
|
|
|
|
flipped_shape.data.transform(transform_matrix(scale=(-1, 1, 1)))
|
|
|
|
|
|
|
|
flipped_bone.custom_shape = get_clean_shape(flipped_bone, flipped_shape, rename=True, separate=False, prefix=prefix, apply_transforms=False)
|
|
|
|
|
|
|
|
|
|
|
|
flipped_bone.use_custom_shape_bone_size = bone.use_custom_shape_bone_size
|
|
|
|
|
|
|
|
for c in shape.users_collection:
|
|
|
|
c.objects.link(flipped_bone.custom_shape)
|
|
|
|
|
|
|
|
return flipped_bone.custom_shape
|