Compare commits
2 Commits
0e19f20200
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 40a419509d | |||
| a2e4bacb16 |
+2
-2
@@ -21,8 +21,8 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Mixamo Rig",
|
"name": "Mixamo Rig",
|
||||||
"author": "Mixamo",
|
"author": "Mixamo",
|
||||||
"version": (1, 11, 11),
|
"version": (2, 0, 0),
|
||||||
"blender": (2, 80, 0),
|
"blender": (4, 2, 0),
|
||||||
"location": "3D View > Mixamo> Control Rig",
|
"location": "3D View > Mixamo> Control Rig",
|
||||||
"description": "Generate a control rig from the selected Mixamo Fbx skeleton",
|
"description": "Generate a control rig from the selected Mixamo Fbx skeleton",
|
||||||
"category": "Animation"}
|
"category": "Animation"}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import bpy, sys, linecache, ast
|
||||||
|
|
||||||
|
def get_error_message():
|
||||||
|
exc_type, exc_obj, tb = sys.exc_info()
|
||||||
|
f = tb.tb_frame
|
||||||
|
lineno = tb.tb_lineno
|
||||||
|
filename = f.f_code.co_filename
|
||||||
|
linecache.checkcache(filename)
|
||||||
|
line = linecache.getline(filename, lineno, f.f_globals)
|
||||||
|
error_message = 'Error in ({}\nLine {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj)
|
||||||
|
return error_message
|
||||||
|
|
||||||
|
|
||||||
|
def get_addon_preferences():
|
||||||
|
return bpy.context.preferences.addons[__package__].preferences
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
import bpy
|
||||||
|
from .maths_geo import *
|
||||||
|
from .bones_pose import *
|
||||||
|
from .version import *
|
||||||
|
|
||||||
|
def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True, bake_object=False, ik_data=None):
|
||||||
|
scn = bpy.context.scene
|
||||||
|
obj_data = []
|
||||||
|
bones_data = []
|
||||||
|
armature = bpy.data.objects.get(bpy.context.active_object.name)
|
||||||
|
|
||||||
|
def get_bones_matrix():
|
||||||
|
matrix = {}
|
||||||
|
for pbone in armature.pose.bones:
|
||||||
|
if only_selected and not pbone.bone.select:
|
||||||
|
continue
|
||||||
|
|
||||||
|
bmat = pbone.matrix
|
||||||
|
|
||||||
|
# IK poles
|
||||||
|
if pbone.name.startswith("Ctrl_ArmPole") or pbone.name.startswith("Ctrl_LegPole"):
|
||||||
|
b1 = b2 = None
|
||||||
|
src_arm = ik_data["src_arm"]
|
||||||
|
type = ""
|
||||||
|
if "Leg" in pbone.name:
|
||||||
|
type = "Leg"
|
||||||
|
elif "Arm" in pbone.name:
|
||||||
|
type = "Arm"
|
||||||
|
|
||||||
|
name_split = pbone.name.split('_')
|
||||||
|
side = name_split[len(name_split)-1]
|
||||||
|
b1_name = ik_data[type+side][0]
|
||||||
|
b2_name = ik_data[type+side][1]
|
||||||
|
b1 = src_arm.pose.bones.get(b1_name)
|
||||||
|
b2 = src_arm.pose.bones.get(b2_name)
|
||||||
|
|
||||||
|
_axis = None
|
||||||
|
if type == "Leg":
|
||||||
|
_axis = (b1.z_axis*0.5) + (b2.z_axis*0.5)#b1.z_axis#
|
||||||
|
elif type == "Arm":
|
||||||
|
if side == "Left":
|
||||||
|
_axis = b2.x_axis
|
||||||
|
elif side == "Right":
|
||||||
|
_axis = -b2.x_axis
|
||||||
|
|
||||||
|
pole_pos = get_ik_pole_pos(b1, b2, method=2, axis=_axis)
|
||||||
|
#pole_pos = b2.head + (b2.z_axis.normalized() * (b2.tail-b2.head).magnitude)
|
||||||
|
bmat = Matrix.Translation(pole_pos)
|
||||||
|
|
||||||
|
# Child Of constraints are preserved after baking
|
||||||
|
# need to compensate the matrix with the Child Of transformation
|
||||||
|
child_of_cns = pbone.constraints.get("Child Of")
|
||||||
|
if child_of_cns:
|
||||||
|
if child_of_cns.influence == 1.0 and child_of_cns.mute == False:
|
||||||
|
bmat = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ bmat
|
||||||
|
|
||||||
|
matrix[pbone.name] = armature.convert_space(pose_bone=pbone, matrix=bmat, from_space="POSE", to_space="LOCAL")
|
||||||
|
|
||||||
|
return matrix
|
||||||
|
|
||||||
|
def get_obj_matrix():
|
||||||
|
parent = armature.parent
|
||||||
|
matrix = armature.matrix_world
|
||||||
|
if parent:
|
||||||
|
return parent.matrix_world.inverted_safe() @ matrix
|
||||||
|
else:
|
||||||
|
return matrix.copy()
|
||||||
|
|
||||||
|
# store matrices
|
||||||
|
current_frame = scn.frame_current
|
||||||
|
|
||||||
|
for f in range(int(frame_start), int(frame_end+1)):
|
||||||
|
scn.frame_set(f)
|
||||||
|
bpy.context.view_layer.update()
|
||||||
|
|
||||||
|
if bake_bones:
|
||||||
|
bones_data.append((f, get_bones_matrix()))
|
||||||
|
if bake_object:
|
||||||
|
obj_data.append((f, get_obj_matrix()))
|
||||||
|
|
||||||
|
# set new action
|
||||||
|
action = bpy.data.actions.new("Action")
|
||||||
|
anim_data = armature.animation_data_create()
|
||||||
|
anim_data.action = action
|
||||||
|
|
||||||
|
def store_keyframe(bn, prop_type, fc_array_index, fra, val):
|
||||||
|
fc_data_path = 'pose.bones["' + bn + '"].' + prop_type
|
||||||
|
fc_key = (fc_data_path, fc_array_index)
|
||||||
|
if not keyframes.get(fc_key):
|
||||||
|
keyframes[fc_key] = []
|
||||||
|
keyframes[fc_key].extend((fra, val))
|
||||||
|
|
||||||
|
|
||||||
|
# set transforms and store keyframes
|
||||||
|
if bake_bones:
|
||||||
|
for pb in armature.pose.bones:
|
||||||
|
if only_selected and not pb.bone.select:
|
||||||
|
continue
|
||||||
|
|
||||||
|
euler_prev = None
|
||||||
|
quat_prev = None
|
||||||
|
keyframes = {}
|
||||||
|
|
||||||
|
for (f, matrix) in bones_data:
|
||||||
|
pb.matrix_basis = matrix[pb.name].copy()
|
||||||
|
|
||||||
|
for arr_idx, value in enumerate(pb.location):
|
||||||
|
store_keyframe(pb.name, "location", arr_idx, f, value)
|
||||||
|
|
||||||
|
rotation_mode = pb.rotation_mode
|
||||||
|
|
||||||
|
if rotation_mode == 'QUATERNION':
|
||||||
|
if quat_prev is not None:
|
||||||
|
quat = pb.rotation_quaternion.copy()
|
||||||
|
quat.make_compatible(quat_prev)
|
||||||
|
pb.rotation_quaternion = quat
|
||||||
|
quat_prev = quat
|
||||||
|
del quat
|
||||||
|
else:
|
||||||
|
quat_prev = pb.rotation_quaternion.copy()
|
||||||
|
|
||||||
|
for arr_idx, value in enumerate(pb.rotation_quaternion):
|
||||||
|
store_keyframe(pb.name, "rotation_quaternion", arr_idx, f, value)
|
||||||
|
|
||||||
|
elif rotation_mode == 'AXIS_ANGLE':
|
||||||
|
for arr_idx, value in enumerate(pb.rotation_axis_angle):
|
||||||
|
store_keyframe(pb.name, "rotation_axis_angle", arr_idx, f, value)
|
||||||
|
|
||||||
|
else: # euler, XYZ, ZXY etc
|
||||||
|
if euler_prev is not None:
|
||||||
|
euler = pb.rotation_euler.copy()
|
||||||
|
euler.make_compatible(euler_prev)
|
||||||
|
pb.rotation_euler = euler
|
||||||
|
euler_prev = euler
|
||||||
|
del euler
|
||||||
|
else:
|
||||||
|
euler_prev = pb.rotation_euler.copy()
|
||||||
|
|
||||||
|
for arr_idx, value in enumerate(pb.rotation_euler):
|
||||||
|
store_keyframe(pb.name, "rotation_euler", arr_idx, f, value)
|
||||||
|
|
||||||
|
for arr_idx, value in enumerate(pb.scale):
|
||||||
|
store_keyframe(pb.name, "scale", arr_idx, f, value)
|
||||||
|
|
||||||
|
|
||||||
|
# Add keyframes
|
||||||
|
for fc_key, key_values in keyframes.items():
|
||||||
|
data_path, index = fc_key
|
||||||
|
fcurve = action.fcurves.find(data_path=data_path, index=index)
|
||||||
|
if fcurve == None:
|
||||||
|
fcurve = action.fcurves.new(data_path, index=index, action_group=pb.name)
|
||||||
|
|
||||||
|
num_keys = len(key_values) // 2
|
||||||
|
fcurve.keyframe_points.add(num_keys)
|
||||||
|
fcurve.keyframe_points.foreach_set('co', key_values)
|
||||||
|
|
||||||
|
if blender_version._float >= 290:# internal error when doing so with Blender 2.83, only for Blender 2.90 and higher
|
||||||
|
linear_enum_value = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items['LINEAR'].value
|
||||||
|
fcurve.keyframe_points.foreach_set('interpolation', (linear_enum_value,) * num_keys)
|
||||||
|
else:
|
||||||
|
for kf in fcurve.keyframe_points:
|
||||||
|
kf.interpolation = 'LINEAR'
|
||||||
|
|
||||||
|
|
||||||
|
if bake_object:
|
||||||
|
euler_prev = None
|
||||||
|
quat_prev = None
|
||||||
|
|
||||||
|
for (f, matrix) in obj_data:
|
||||||
|
name = "Action Bake"
|
||||||
|
armature.matrix_basis = matrix
|
||||||
|
|
||||||
|
armature.keyframe_insert("location", index=-1, frame=f, group=name)
|
||||||
|
|
||||||
|
rotation_mode = armature.rotation_mode
|
||||||
|
if rotation_mode == 'QUATERNION':
|
||||||
|
if quat_prev is not None:
|
||||||
|
quat = armature.rotation_quaternion.copy()
|
||||||
|
quat.make_compatible(quat_prev)
|
||||||
|
armature.rotation_quaternion = quat
|
||||||
|
quat_prev = quat
|
||||||
|
del quat
|
||||||
|
else:
|
||||||
|
quat_prev = armature.rotation_quaternion.copy()
|
||||||
|
armature.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name)
|
||||||
|
elif rotation_mode == 'AXIS_ANGLE':
|
||||||
|
armature.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name)
|
||||||
|
else: # euler, XYZ, ZXY etc
|
||||||
|
if euler_prev is not None:
|
||||||
|
euler = armature.rotation_euler.copy()
|
||||||
|
euler.make_compatible(euler_prev)
|
||||||
|
armature.rotation_euler = euler
|
||||||
|
euler_prev = euler
|
||||||
|
del euler
|
||||||
|
else:
|
||||||
|
euler_prev = armature.rotation_euler.copy()
|
||||||
|
armature.keyframe_insert("rotation_euler", index=-1, frame=f, group=name)
|
||||||
|
|
||||||
|
armature.keyframe_insert("scale", index=-1, frame=f, group=name)
|
||||||
|
|
||||||
|
|
||||||
|
# restore current frame
|
||||||
|
scn.frame_set(current_frame)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
def restore_armature_layers(layers_select):
|
||||||
|
# restore the armature layers visibility
|
||||||
|
for i in range(0, 32):
|
||||||
|
bpy.context.active_object.data.layers[i] = layers_select[i]
|
||||||
|
|
||||||
|
|
||||||
|
def enable_all_armature_layers():
|
||||||
|
# enable all layers
|
||||||
|
# and return the list of each layer visibility
|
||||||
|
_layers = bpy.context.active_object.data.layers
|
||||||
|
layers_select = []
|
||||||
|
for i in range(0, 32):
|
||||||
|
layers_select.append(_layers[i])
|
||||||
|
for i in range(0, 32):
|
||||||
|
bpy.context.active_object.data.layers[i] = True
|
||||||
|
|
||||||
|
return layers_select
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
def get_data_bone(name):
|
||||||
|
return bpy.context.active_object.data.bones.get(name)
|
||||||
|
|
||||||
|
|
||||||
|
def set_bone_layer(databone, layer_idx, multi=False):
|
||||||
|
if databone == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
databone.layers[layer_idx] = True
|
||||||
|
if multi:
|
||||||
|
return
|
||||||
|
|
||||||
|
for i, lay in enumerate(databone.layers):
|
||||||
|
if i != layer_idx:
|
||||||
|
databone.layers[i] = False
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
def get_edit_bone(name):
|
||||||
|
return bpy.context.object.data.edit_bones.get(name)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_bone_transforms(bone1, bone2):
|
||||||
|
# copy editbone bone1 transforms to bone 2
|
||||||
|
bone2.head = bone1.head.copy()
|
||||||
|
bone2.tail = bone1.tail.copy()
|
||||||
|
bone2.roll = bone1.roll
|
||||||
|
|
||||||
|
|
||||||
|
def create_edit_bone(bone_name, deform=False):
|
||||||
|
b = get_edit_bone(bone_name)
|
||||||
|
if b == None:
|
||||||
|
b = bpy.context.active_object.data.edit_bones.new(bone_name)
|
||||||
|
b.use_deform = deform
|
||||||
|
return b
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import bpy
|
||||||
|
from .objects import *
|
||||||
|
from .version import *
|
||||||
|
|
||||||
|
def get_custom_shape_scale(pbone, uniform=True):
|
||||||
|
if blender_version._float >= 300:
|
||||||
|
if uniform:
|
||||||
|
# uniform scale
|
||||||
|
val = 0
|
||||||
|
for i in range(0,3):
|
||||||
|
val += pbone.custom_shape_scale_xyz[i]
|
||||||
|
return val/3
|
||||||
|
# array scale
|
||||||
|
else:
|
||||||
|
return pbone.custom_shape_scale_xyz
|
||||||
|
# pre-Blender 3.0
|
||||||
|
else:
|
||||||
|
return pbone.custom_shape_scale
|
||||||
|
|
||||||
|
|
||||||
|
def get_selected_pbone_name():
|
||||||
|
try:
|
||||||
|
return bpy.context.selected_pose_bones[0].name#.active_pose_bone.name
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def get_pose_bone(name):
|
||||||
|
return bpy.context.active_object.pose.bones.get(name)
|
||||||
|
|
||||||
|
|
||||||
|
def lock_pbone_transform(pbone, type, list):
|
||||||
|
for i in list:
|
||||||
|
if type == "location":
|
||||||
|
pbone.lock_location[i] = True
|
||||||
|
elif type == "rotation":
|
||||||
|
pbone.lock_rotation[i] = True
|
||||||
|
elif type == "scale":
|
||||||
|
pbone.lock_scale[i] = True
|
||||||
|
|
||||||
|
|
||||||
|
def set_bone_custom_shape(pbone, cs_name):
|
||||||
|
cs = get_object(cs_name)
|
||||||
|
if cs == None:
|
||||||
|
append_cs(cs_name)
|
||||||
|
cs = get_object(cs_name)
|
||||||
|
|
||||||
|
pbone.custom_shape = cs
|
||||||
|
|
||||||
|
|
||||||
|
def set_bone_color_group(obj, pb, grp_name):
|
||||||
|
# mixamo required color
|
||||||
|
orange = (0.969, 0.565, 0.208)
|
||||||
|
orange_light = (0.957, 0.659, 0.416)
|
||||||
|
blue_dark = (0.447, 0.682, 1.0)
|
||||||
|
blue_light = (0.365, 0.851, 1.0)
|
||||||
|
|
||||||
|
# base color
|
||||||
|
green = (0.0, 1.0, 0.0)
|
||||||
|
red = (1.0, 0.0, 0.0)
|
||||||
|
blue = (0.0, 0.9, 1.0)
|
||||||
|
|
||||||
|
grp_color_master = orange_light
|
||||||
|
grp_color_neck = orange_light
|
||||||
|
grp_color_root_master = orange
|
||||||
|
grp_color_head = orange
|
||||||
|
grp_color_body_mid = green
|
||||||
|
grp_color_body_left = blue_dark
|
||||||
|
grp_color_body_right = blue_light
|
||||||
|
|
||||||
|
grp = obj.pose.bone_groups.get(grp_name)
|
||||||
|
if grp == None:
|
||||||
|
grp = obj.pose.bone_groups.new(name=grp_name)
|
||||||
|
grp.color_set = 'CUSTOM'
|
||||||
|
|
||||||
|
grp_color = None
|
||||||
|
if grp_name == "body_mid":
|
||||||
|
grp_color = grp_color_body_mid
|
||||||
|
elif grp_name == "body_left":
|
||||||
|
grp_color = grp_color_body_left
|
||||||
|
elif grp_name == "body_right":
|
||||||
|
grp_color = grp_color_body_right
|
||||||
|
elif grp_name == "master":
|
||||||
|
grp_color = grp_color_master
|
||||||
|
elif grp_name == "neck":
|
||||||
|
grp_color = grp_color_head
|
||||||
|
elif grp_name == "head":
|
||||||
|
grp_color = grp_color_neck
|
||||||
|
elif grp_name == "root_master":
|
||||||
|
grp_color = grp_color_root_master
|
||||||
|
|
||||||
|
# set normal color
|
||||||
|
grp.colors.normal = grp_color
|
||||||
|
|
||||||
|
# set select color/active color
|
||||||
|
for col_idx in range(0,3):
|
||||||
|
grp.colors.select[col_idx] = grp_color[col_idx] + 0.2
|
||||||
|
grp.colors.active[col_idx] = grp_color[col_idx] + 0.4
|
||||||
|
|
||||||
|
pb.bone_group = grp
|
||||||
|
|
||||||
|
|
||||||
|
def update_transform():
|
||||||
|
bpy.ops.transform.rotate(value=0, orient_axis='Z', orient_type='VIEW', orient_matrix=((0.0, 0.0, 0), (0, 0.0, 0.0), (0.0, 0.0, 0.0)), orient_matrix_type='VIEW', mirror=False)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
def add_copy_transf(p_bone, tgt, subtgt):
|
||||||
|
cns_transf = p_bone.constraints.get("Copy Transforms")
|
||||||
|
if cns_transf == None:
|
||||||
|
cns_transf = p_bone.constraints.new("COPY_TRANSFORMS")
|
||||||
|
cns_transf.name = "Copy Transforms"
|
||||||
|
cns_transf.target = tgt
|
||||||
|
cns_transf.subtarget = subtgt
|
||||||
|
|
||||||
|
|
||||||
|
def set_constraint_inverse_matrix(cns):
|
||||||
|
# set the inverse matrix of Child Of constraint
|
||||||
|
tar_obj = cns.target
|
||||||
|
subtarget_pbone = tar_obj.pose.bones.get(cns.subtarget)
|
||||||
|
if subtarget_pbone:
|
||||||
|
#cns.inverse_matrix = tar_obj.matrix_world.inverted() @ subtarget_pbone.matrix_basis.inverted()
|
||||||
|
print("reset child of cns", cns.name, cns.subtarget)
|
||||||
|
cns.inverse_matrix = subtarget_pbone.bone.matrix_local.to_4x4().inverted()
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
def get_current_mode():
|
||||||
|
return bpy.context.mode
|
||||||
|
|
||||||
|
|
||||||
|
def restore_current_mode(current_mode):
|
||||||
|
if current_mode == 'EDIT_ARMATURE':
|
||||||
|
current_mode = 'EDIT'
|
||||||
|
bpy.ops.object.mode_set(mode=current_mode)
|
||||||
Binary file not shown.
@@ -0,0 +1,60 @@
|
|||||||
|
import bpy
|
||||||
|
from .version import blender_version
|
||||||
|
|
||||||
|
|
||||||
|
def get_prop_setting(node, prop_name, setting):
|
||||||
|
if blender_version._float >= 300:
|
||||||
|
return node.id_properties_ui(prop_name).as_dict()[setting]
|
||||||
|
else:
|
||||||
|
return node['_RNA_UI'][prop_name][setting]
|
||||||
|
|
||||||
|
|
||||||
|
def set_prop_setting(node, prop_name, setting, value):
|
||||||
|
if blender_version._float >= 300:
|
||||||
|
ui_data = node.id_properties_ui(prop_name)
|
||||||
|
if setting == 'default':
|
||||||
|
ui_data.update(default=value)
|
||||||
|
elif setting == 'min':
|
||||||
|
ui_data.update(min=value)
|
||||||
|
elif setting == 'max':
|
||||||
|
ui_data.update(max=value)
|
||||||
|
elif setting == 'soft_min':
|
||||||
|
ui_data.update(soft_min=value)
|
||||||
|
elif setting == 'soft_max':
|
||||||
|
ui_data.update(soft_max=value)
|
||||||
|
elif setting == 'description':
|
||||||
|
ui_data.update(description=value)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if not "_RNA_UI" in node.keys():
|
||||||
|
node["_RNA_UI"] = {}
|
||||||
|
node['_RNA_UI'][prop_name][setting] = value
|
||||||
|
|
||||||
|
|
||||||
|
def create_custom_prop(node=None, prop_name="", prop_val=1.0, prop_min=0.0, prop_max=1.0, prop_description="", soft_min=None, soft_max=None, default=None):
|
||||||
|
if soft_min == None:
|
||||||
|
soft_min = prop_min
|
||||||
|
if soft_max == None:
|
||||||
|
soft_max = prop_max
|
||||||
|
|
||||||
|
if blender_version._float < 300:
|
||||||
|
if not "_RNA_UI" in node.keys():
|
||||||
|
node["_RNA_UI"] = {}
|
||||||
|
|
||||||
|
node[prop_name] = prop_val
|
||||||
|
|
||||||
|
if default == None:
|
||||||
|
default = prop_val
|
||||||
|
|
||||||
|
if blender_version._float < 300:
|
||||||
|
node["_RNA_UI"][prop_name] = {'use_soft_limits':True, 'min': prop_min, 'max': prop_max, 'description': prop_description, 'soft_min':soft_min, 'soft_max':soft_max, 'default':default}
|
||||||
|
else:
|
||||||
|
set_prop_setting(node, prop_name, 'min', prop_min)
|
||||||
|
set_prop_setting(node, prop_name, 'max', prop_max)
|
||||||
|
set_prop_setting(node, prop_name, 'description', prop_description)
|
||||||
|
set_prop_setting(node, prop_name, 'soft_min', soft_min)
|
||||||
|
set_prop_setting(node, prop_name, 'soft_max', soft_max)
|
||||||
|
set_prop_setting(node, prop_name, 'default', default)
|
||||||
|
|
||||||
|
# set as overridable
|
||||||
|
node.property_overridable_library_set('["'+prop_name+'"]', True)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
def add_driver_to_prop(obj, dr_dp, tar_dp, array_idx=-1, exp="var"):
|
||||||
|
if obj.animation_data == None:
|
||||||
|
obj.animation_data_create()
|
||||||
|
|
||||||
|
drivers_list = obj.animation_data.drivers
|
||||||
|
dr = drivers_list.find(dr_dp, index=array_idx)
|
||||||
|
|
||||||
|
if dr == None:
|
||||||
|
dr = obj.driver_add(dr_dp, array_idx)
|
||||||
|
|
||||||
|
dr.driver.expression = exp
|
||||||
|
|
||||||
|
var = dr.driver.variables.get('var')
|
||||||
|
|
||||||
|
if var == None:
|
||||||
|
var = dr.driver.variables.new()
|
||||||
|
|
||||||
|
var.type = 'SINGLE_PROP'
|
||||||
|
var.name = 'var'
|
||||||
|
var.targets[0].id = obj
|
||||||
|
var.targets[0].data_path = tar_dp
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
from math import *
|
||||||
|
from mathutils import *
|
||||||
|
|
||||||
|
|
||||||
|
def mat3_to_vec_roll(mat):
|
||||||
|
vec = mat.col[1]
|
||||||
|
vecmat = vec_roll_to_mat3(mat.col[1], 0)
|
||||||
|
vecmatinv = vecmat.inverted()
|
||||||
|
rollmat = vecmatinv @ mat
|
||||||
|
roll = atan2(rollmat[0][2], rollmat[2][2])
|
||||||
|
return roll
|
||||||
|
|
||||||
|
|
||||||
|
def vec_roll_to_mat3(vec, roll):
|
||||||
|
target = Vector((0, 0.1, 0))
|
||||||
|
nor = vec.normalized()
|
||||||
|
axis = target.cross(nor)
|
||||||
|
if axis.dot(axis) > 0.0000000001: # this seems to be the problem for some bones, no idea how to fix
|
||||||
|
axis.normalize()
|
||||||
|
theta = target.angle(nor)
|
||||||
|
bMatrix = Matrix.Rotation(theta, 3, axis)
|
||||||
|
else:
|
||||||
|
updown = 1 if target.dot(nor) > 0 else -1
|
||||||
|
bMatrix = Matrix.Scale(updown, 3)
|
||||||
|
bMatrix[2][2] = 1.0
|
||||||
|
|
||||||
|
rMatrix = Matrix.Rotation(roll, 3, nor)
|
||||||
|
mat = rMatrix @ bMatrix
|
||||||
|
return mat
|
||||||
|
|
||||||
|
|
||||||
|
def align_bone_x_axis(edit_bone, new_x_axis):
|
||||||
|
new_x_axis = new_x_axis.cross(edit_bone.y_axis)
|
||||||
|
new_x_axis.normalize()
|
||||||
|
dot = max(-1.0, min(1.0, edit_bone.z_axis.dot(new_x_axis)))
|
||||||
|
angle = acos(dot)
|
||||||
|
edit_bone.roll += angle
|
||||||
|
dot1 = edit_bone.z_axis.dot(new_x_axis)
|
||||||
|
edit_bone.roll -= angle * 2.0
|
||||||
|
dot2 = edit_bone.z_axis.dot(new_x_axis)
|
||||||
|
if dot1 > dot2:
|
||||||
|
edit_bone.roll += angle * 2.0
|
||||||
|
|
||||||
|
|
||||||
|
def align_bone_z_axis(edit_bone, new_z_axis):
|
||||||
|
new_z_axis = -(new_z_axis.cross(edit_bone.y_axis))
|
||||||
|
new_z_axis.normalize()
|
||||||
|
dot = max(-1.0, min(1.0, edit_bone.x_axis.dot(new_z_axis)))
|
||||||
|
angle = acos(dot)
|
||||||
|
edit_bone.roll += angle
|
||||||
|
dot1 = edit_bone.x_axis.dot(new_z_axis)
|
||||||
|
edit_bone.roll -= angle * 2.0
|
||||||
|
dot2 = edit_bone.x_axis.dot(new_z_axis)
|
||||||
|
if dot1 > dot2:
|
||||||
|
edit_bone.roll += angle * 2.0
|
||||||
|
|
||||||
|
|
||||||
|
def signed_angle(u, v, normal):
|
||||||
|
nor = normal.normalized()
|
||||||
|
a = u.angle(v)
|
||||||
|
|
||||||
|
c = u.cross(v)
|
||||||
|
|
||||||
|
if c.magnitude == 0.0:
|
||||||
|
c = u.normalized().cross(v)
|
||||||
|
if c.magnitude == 0.0:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
if c.angle(nor) < 1:
|
||||||
|
a = -a
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
def project_point_onto_plane(q, p, n):
|
||||||
|
n = n.normalized()
|
||||||
|
return q - ((q - p).dot(n)) * n
|
||||||
|
|
||||||
|
|
||||||
|
def get_pole_angle(base_bone, ik_bone, pole_location):
|
||||||
|
pole_normal = (ik_bone.tail - base_bone.head).cross(pole_location - base_bone.head)
|
||||||
|
projected_pole_axis = pole_normal.cross(base_bone.tail - base_bone.head)
|
||||||
|
return signed_angle(base_bone.x_axis, projected_pole_axis, base_bone.tail - base_bone.head)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pose_matrix_in_other_space(mat, pose_bone):
|
||||||
|
rest = pose_bone.bone.matrix_local.copy()
|
||||||
|
rest_inv = rest.inverted()
|
||||||
|
|
||||||
|
if pose_bone.parent and pose_bone.bone.use_inherit_rotation:
|
||||||
|
par_mat = pose_bone.parent.matrix.copy()
|
||||||
|
par_inv = par_mat.inverted()
|
||||||
|
par_rest = pose_bone.parent.bone.matrix_local.copy()
|
||||||
|
|
||||||
|
else:
|
||||||
|
par_mat = Matrix()
|
||||||
|
par_inv = Matrix()
|
||||||
|
par_rest = Matrix()
|
||||||
|
|
||||||
|
smat = rest_inv @ (par_rest @ (par_inv @ mat))
|
||||||
|
|
||||||
|
return smat
|
||||||
|
|
||||||
|
|
||||||
|
def get_ik_pole_pos(b1, b2, method=1, axis=None):
|
||||||
|
|
||||||
|
if method == 1:
|
||||||
|
# IK pole position based on real IK bones vector
|
||||||
|
plane_normal = (b1.head - b2.tail)
|
||||||
|
midpoint = (b1.head + b2.tail) * 0.5
|
||||||
|
prepole_dir = b2.head - midpoint#prepole_fk.tail - prepole_fk.head
|
||||||
|
pole_pos = b2.head + prepole_dir.normalized()# * 4
|
||||||
|
pole_pos = project_point_onto_plane(pole_pos, b2.head, plane_normal)
|
||||||
|
pole_pos = b2.head + ((pole_pos - b2.head).normalized() * (b2.head - b1.head).magnitude * 1.7)
|
||||||
|
|
||||||
|
elif method == 2:
|
||||||
|
# IK pole position based on bone2 Z axis vector
|
||||||
|
pole_pos = b2.head + (axis.normalized() * (b2.tail-b2.head).magnitude)
|
||||||
|
|
||||||
|
return pole_pos
|
||||||
|
|
||||||
|
|
||||||
|
def rotate_point(point, angle, origin, axis):
|
||||||
|
rot_mat = Matrix.Rotation(angle, 4, axis.normalized())
|
||||||
|
# rotate in world origin space
|
||||||
|
offset_vec = -origin
|
||||||
|
offset_knee = point + offset_vec
|
||||||
|
# rotate
|
||||||
|
rotated_point = rot_mat @ offset_knee
|
||||||
|
# bring back to original space
|
||||||
|
rotated_point = rotated_point -offset_vec
|
||||||
|
return rotated_point
|
||||||
|
|
||||||
|
|
||||||
|
def dot_product(x, y):
|
||||||
|
return sum([x[i] * y[i] for i in range(len(x))])
|
||||||
|
|
||||||
|
|
||||||
|
def norm(x):
|
||||||
|
return sqrt(dot_product(x, x))
|
||||||
|
|
||||||
|
|
||||||
|
def normalize(x):
|
||||||
|
return [x[i] / norm(x) for i in range(len(x))]
|
||||||
|
|
||||||
|
|
||||||
|
def project_vector_onto_plane(x, n):
|
||||||
|
d = dot_product(x, n) / norm(n)
|
||||||
|
p = [d * normalize(n)[i] for i in range(len(n))]
|
||||||
|
vec_list = [x[i] - p[i] for i in range(len(x))]
|
||||||
|
return Vector((vec_list[0], vec_list[1], vec_list[2]))
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import bpy
|
||||||
|
from .bones_pose import *
|
||||||
|
from .bones_data import *
|
||||||
|
from ..definitions import naming
|
||||||
|
|
||||||
|
def get_mixamo_prefix():
|
||||||
|
p = ""
|
||||||
|
rig = bpy.context.active_object
|
||||||
|
|
||||||
|
if 'mixamo_prefix' in rig.data.keys():
|
||||||
|
p = rig.data["mixamo_prefix"]
|
||||||
|
|
||||||
|
else:
|
||||||
|
for dbone in rig.data.bones:
|
||||||
|
if dbone.name.startswith("mixamorig") and ':' in dbone.name:
|
||||||
|
p = dbone.name.split(':')[0]+':'
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
rig.data["mixamo_prefix"] = p
|
||||||
|
except:# context error
|
||||||
|
pass
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def get_mix_name(name, use_prefix):
|
||||||
|
if not use_prefix:
|
||||||
|
return name
|
||||||
|
else:
|
||||||
|
p = get_mixamo_prefix()
|
||||||
|
return p+name
|
||||||
|
|
||||||
|
|
||||||
|
def get_bone_side(bone_name):
|
||||||
|
if bone_name.endswith("_Left"):
|
||||||
|
return "Left"
|
||||||
|
elif bone_name.endswith("_Right"):
|
||||||
|
return "Right"
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import bpy, os
|
||||||
|
|
||||||
|
def delete_object(obj):
|
||||||
|
bpy.data.objects.remove(obj, do_unlink=True)
|
||||||
|
|
||||||
|
|
||||||
|
def duplicate_object():
|
||||||
|
try:
|
||||||
|
bpy.ops.object.duplicate(linked=False, mode='TRANSLATION')
|
||||||
|
except:
|
||||||
|
bpy.ops.object.duplicate('TRANSLATION', False)
|
||||||
|
|
||||||
|
|
||||||
|
def get_object(name):
|
||||||
|
return bpy.data.objects.get(name)
|
||||||
|
|
||||||
|
|
||||||
|
def set_active_object(object_name):
|
||||||
|
bpy.context.view_layer.objects.active = bpy.data.objects[object_name]
|
||||||
|
bpy.data.objects[object_name].select_set(state=True)
|
||||||
|
|
||||||
|
|
||||||
|
def hide_object(obj_to_set):
|
||||||
|
obj_to_set.hide_set(True)
|
||||||
|
obj_to_set.hide_viewport = True
|
||||||
|
|
||||||
|
|
||||||
|
def is_object_hidden(obj_to_get):
|
||||||
|
if obj_to_get.hide_get() == False and obj_to_get.hide_viewport == False:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def append_cs(names=[]):
|
||||||
|
context = bpy.context
|
||||||
|
scene = context.scene
|
||||||
|
addon_directory = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
filepath = addon_directory + "\cs.blend"
|
||||||
|
|
||||||
|
# load the objects data in file
|
||||||
|
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
|
||||||
|
data_to.objects = [name for name in data_from.objects if name in names]
|
||||||
|
|
||||||
|
# Add the objects in the scene
|
||||||
|
for obj in data_to.objects:
|
||||||
|
if obj:
|
||||||
|
# link in collec
|
||||||
|
scene.collection.objects.link(obj)
|
||||||
|
|
||||||
|
cs_grp = bpy.data.objects.get("cs_grp")
|
||||||
|
if cs_grp == None:
|
||||||
|
cs_grp = bpy.data.objects.new(name="cs_grp", object_data=None)
|
||||||
|
bpy.context.collection.objects.link(cs_grp)
|
||||||
|
cs_grp.location = [0,0,0]
|
||||||
|
cs_grp.rotation_euler = [0,0,0]
|
||||||
|
cs_grp.scale = [1,1,1]
|
||||||
|
|
||||||
|
# parent the custom shape
|
||||||
|
obj.parent = cs_grp
|
||||||
|
|
||||||
|
# assign to new collection
|
||||||
|
assigned_collections = []
|
||||||
|
for collec in cs_grp.users_collection:
|
||||||
|
try:
|
||||||
|
collec.objects.link(obj)
|
||||||
|
assigned_collections.append(collec)
|
||||||
|
except:# already in collection
|
||||||
|
pass
|
||||||
|
|
||||||
|
if len(assigned_collections):
|
||||||
|
# remove previous collections
|
||||||
|
for i in obj.users_collection:
|
||||||
|
if not i in assigned_collections:
|
||||||
|
i.objects.unlink(obj)
|
||||||
|
# and the scene collection
|
||||||
|
try:
|
||||||
|
scene.collection.objects.unlink(obj)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
class ARP_blender_version:
|
||||||
|
_string = bpy.app.version_string
|
||||||
|
blender_v = bpy.app.version
|
||||||
|
_float = blender_v[0]*100+blender_v[1]+blender_v[2]*0.01
|
||||||
|
|
||||||
|
blender_version = ARP_blender_version()
|
||||||
|
|
||||||
|
|
||||||
|
def convert_drivers_cs_to_xyz(armature):
|
||||||
|
# Blender 3.0 requires Vector3 custom_shape_scale values
|
||||||
|
# convert single uniform driver to vector3 array drivers
|
||||||
|
drivers_armature = [i for i in armature.animation_data.drivers]
|
||||||
|
|
||||||
|
for dr in drivers_armature:
|
||||||
|
if 'custom_shape_scale' in dr.data_path:
|
||||||
|
if not 'custom_shape_scale_xyz' in dr.data_path:
|
||||||
|
for i in range(0, 3):
|
||||||
|
new_dr = armature.animation_data.drivers.from_existing(src_driver=dr)
|
||||||
|
new_dr.data_path = new_dr.data_path.replace('custom_shape_scale', 'custom_shape_scale_xyz')
|
||||||
|
new_dr.array_index = i
|
||||||
|
new_dr.driver.expression += ''# update hack
|
||||||
|
|
||||||
|
armature.driver_remove(dr.data_path, dr.array_index)
|
||||||
|
|
||||||
|
print("Converted custom shape scale drivers to xyz")
|
||||||
|
|
||||||
|
|
||||||
|
def get_custom_shape_scale_prop_name():
|
||||||
|
if blender_version._float >= 300:
|
||||||
|
return 'custom_shape_scale_xyz'
|
||||||
|
else:
|
||||||
|
return 'custom_shape_scale'
|
||||||
Reference in New Issue
Block a user