Created
April 19, 2020 13:11
-
-
Save johnathaningle/be3de2920076e5c55a2e78fe730ce845 to your computer and use it in GitHub Desktop.
Dynamic Parent Plugin for Blender 2.8x
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ##### BEGIN GPL LICENSE BLOCK ##### | |
# | |
# This program is free software; you can redistribute it and/or | |
# modify it under the terms of the GNU General Public License | |
# as published by the Free Software Foundation; either version 2 | |
# of the License, or (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program; if not, write to the Free Software Foundation, | |
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
# | |
# ##### END GPL LICENSE BLOCK ##### | |
# <pep8 compliant> | |
bl_info = { | |
"name": "Dynamic Parent", | |
"author": "Roman Volodin, roman.volodin@gmail.com", | |
"version": (0, 51), | |
"blender": (2, 80, 0), | |
"location": "View3D > Tool Panel", | |
"description": "Allows to create and disable an animated ChildOf constraint", | |
"warning": "The addon still in progress! Be careful!", | |
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Animation/Dynamic_Parent", | |
"tracker_url": "", | |
"category": "Animation"} | |
import bpy | |
import mathutils | |
# dp_keyframe_insert_*** functions | |
def dp_keyframe_insert_obj(obj): | |
obj.keyframe_insert(data_path="location") | |
if obj.rotation_mode == 'QUATERNION': | |
obj.keyframe_insert(data_path="rotation_quaternion") | |
elif obj.rotation_mode == 'AXIS_ANGLE': | |
obj.keyframe_insert(data_path="rotation_axis_angle") | |
else: | |
obj.keyframe_insert(data_path="rotation_euler") | |
obj.keyframe_insert(data_path="scale") | |
def dp_keyframe_insert_pbone(arm, pbone): | |
arm.keyframe_insert(data_path='pose.bones["'+pbone.name+'"].location') | |
if pbone.rotation_mode == 'QUATERNION': | |
arm.keyframe_insert(data_path='pose.bones["'+pbone.name+'"].rotation_quaternion') | |
elif pbone.rotation_mode == 'AXIS_ANGLE': | |
arm.keyframe_insert(data_path='pose.bones["'+pbone.name+'"].rotation_axis_angel') | |
else: | |
arm.keyframe_insert(data_path='pose.bones["'+pbone.name+'"].rotation_euler') | |
arm.keyframe_insert(data_path='pose.bones["'+pbone.name+'"].scale') | |
def dp_create_dynamic_parent_obj(op): | |
obj = bpy.context.active_object | |
scn = bpy.context.scene | |
list_selected_obj = bpy.context.selected_objects | |
if len(list_selected_obj) == 2: | |
i = list_selected_obj.index(obj) | |
list_selected_obj.pop(i) | |
parent_obj = list_selected_obj[0] | |
dp_keyframe_insert_obj(obj) | |
bpy.ops.object.constraint_add_with_targets(type='CHILD_OF') | |
last_constraint = obj.constraints[-1] | |
if parent_obj.type == 'ARMATURE': | |
last_constraint.subtarget = parent_obj.data.bones.active.name | |
last_constraint.name = "DP_"+last_constraint.target.name+"."+last_constraint.subtarget | |
else: | |
last_constraint.name = "DP_"+last_constraint.target.name | |
#bpy.ops.constraint.childof_set_inverse(constraint=""+last_constraint.name+"", owner='OBJECT') | |
C = bpy.context.copy() | |
C["constraint"] = last_constraint | |
bpy.ops.constraint.childof_set_inverse(C, constraint=last_constraint.name, owner='OBJECT') | |
current_frame = scn.frame_current | |
scn.frame_current = current_frame-1 | |
obj.constraints[last_constraint.name].influence = 0 | |
obj.keyframe_insert(data_path='constraints["'+last_constraint.name+'"].influence') | |
scn.frame_current = current_frame | |
obj.constraints[last_constraint.name].influence = 1 | |
obj.keyframe_insert(data_path='constraints["'+last_constraint.name+'"].influence') | |
for ob in list_selected_obj: | |
ob.select_set(False) | |
obj.select_set(True) | |
else: | |
op.report({'ERROR'}, "Two objects must be selected") | |
def dp_create_dynamic_parent_pbone(op): | |
arm = bpy.context.active_object | |
pbone = bpy.context.active_pose_bone | |
scn = bpy.context.scene | |
list_selected_obj = bpy.context.selected_objects | |
if len(list_selected_obj) == 2 or len(list_selected_obj) == 1: | |
if len(list_selected_obj) == 2: | |
i = list_selected_obj.index(arm) | |
list_selected_obj.pop(i) | |
parent_obj = list_selected_obj[0] | |
if parent_obj.type == 'ARMATURE': | |
parent_obj_pbone = parent_obj.data.bones.active | |
else: | |
parent_obj = arm | |
selected_bones = bpy.context.selected_pose_bones | |
selected_bones.remove(pbone) | |
parent_obj_pbone = selected_bones[0] | |
# debuginfo = ''' | |
# DEBUG INFO: | |
# obj = {} | |
# pbone = {} | |
# parent = {} | |
# parent_bone = {} | |
# ''' | |
# print(debuginfo.format(arm, pbone, parent_obj, parent_obj_pbone)) | |
dp_keyframe_insert_pbone(arm, pbone) | |
bpy.ops.pose.constraint_add_with_targets(type='CHILD_OF') | |
last_constraint = pbone.constraints[-1] | |
if parent_obj.type == 'ARMATURE': | |
last_constraint.subtarget = parent_obj_pbone.name | |
last_constraint.name = "DP_"+last_constraint.target.name+"."+last_constraint.subtarget | |
else: | |
last_constraint.name = "DP_"+last_constraint.target.name | |
#bpy.ops.constraint.childof_set_inverse(constraint=""+last_constraint.name+"", owner='BONE') | |
C = bpy.context.copy() | |
C["constraint"] = last_constraint | |
bpy.ops.constraint.childof_set_inverse(C, constraint=last_constraint.name, owner='BONE') | |
current_frame = scn.frame_current | |
scn.frame_current = current_frame-1 | |
pbone.constraints[last_constraint.name].influence = 0 | |
arm.keyframe_insert(data_path='pose.bones["'+pbone.name+'"].constraints["'+last_constraint.name+'"].influence') | |
scn.frame_current = current_frame | |
pbone.constraints[last_constraint.name].influence = 1 | |
arm.keyframe_insert(data_path='pose.bones["'+pbone.name+'"].constraints["'+last_constraint.name+'"].influence') | |
else: | |
op.report({'ERROR'}, "Two objects must be selected") | |
def dp_disable_dynamic_parent_obj(op): | |
obj = bpy.context.active_object | |
scn = bpy.context.scene | |
if len(obj.constraints) == 0: | |
op.report({'ERROR'}, "Object has no constraint") | |
else: | |
last_constraint = obj.constraints[-1] | |
if "DP_" in last_constraint.name: | |
current_frame = scn.frame_current | |
scn.frame_current = current_frame-1 | |
obj.constraints[last_constraint.name].influence = 1 | |
obj.keyframe_insert(data_path='constraints["'+last_constraint.name+'"].influence') | |
scn.frame_current = current_frame | |
obj.constraints[last_constraint.name].influence = 0 | |
obj.keyframe_insert(data_path='constraints["'+last_constraint.name+'"].influence') | |
loc, rot, scale = obj.matrix_world.decompose() | |
rot_euler = rot.to_euler() | |
current_frame = scn.frame_current | |
scn.frame_current = current_frame - 1 | |
dp_keyframe_insert_obj(obj) | |
scn.frame_current = current_frame | |
obj.location = loc | |
obj.rotation_euler = rot_euler | |
obj.scale = scale | |
dp_keyframe_insert_obj(obj) | |
else: | |
op.report({'ERROR'}, "Object has no Dynamic Parent constraint") | |
def dp_disable_dynamic_parent_pbone(op): | |
arm = bpy.context.active_object | |
pbone = bpy.context.active_pose_bone | |
scn = bpy.context.scene | |
if len(pbone.constraints) == 0: | |
op.report({'ERROR'}, "Bone has no constraint") | |
else: | |
last_constraint = pbone.constraints[-1] | |
current_frame = scn.frame_current | |
scn.frame_current = current_frame - 1 | |
pbone.constraints[last_constraint.name].influence = 1 | |
arm.keyframe_insert(data_path='pose.bones["'+pbone.name+'"].constraints["'+last_constraint.name+'"].influence') | |
scn.frame_current = current_frame | |
pbone.constraints[last_constraint.name].influence = 0 | |
arm.keyframe_insert(data_path='pose.bones["'+pbone.name+'"].constraints["'+last_constraint.name+'"].influence') | |
final_matrix = pbone.matrix | |
current_frame = scn.frame_current | |
scn.frame_current = current_frame - 1 | |
dp_keyframe_insert_pbone(arm, pbone) | |
scn.frame_current = current_frame | |
pbone.matrix = final_matrix | |
dp_keyframe_insert_pbone(arm, pbone) | |
def dp_clear(obj, pbone): | |
dp_curves = [] | |
dp_keys = [] | |
for fcurve in obj.animation_data.action.fcurves: | |
if "constraints" in fcurve.data_path and "DP_" in fcurve.data_path: | |
dp_curves.append(fcurve) | |
for f in dp_curves: | |
for key in f.keyframe_points: | |
dp_keys.append(key.co[0]) | |
dp_keys = list(set(dp_keys)) | |
dp_keys.sort() | |
for fcurve in obj.animation_data.action.fcurves[:]: | |
# Removing constraints fcurves | |
if fcurve.data_path.startswith("constraints") and "DP_" in fcurve.data_path: | |
obj.animation_data.action.fcurves.remove(fcurve) | |
# Removing keys for loc, rot, scale fcurves | |
else: | |
for frame in dp_keys: | |
for key in fcurve.keyframe_points[:]: | |
if key.co[0] == frame: | |
fcurve.keyframe_points.remove(key) | |
if not fcurve.keyframe_points: | |
obj.animation_data.action.fcurves.remove(fcurve) | |
# Removing constraints | |
if pbone: | |
obj = pbone | |
for const in obj.constraints[:]: | |
if const.name.startswith("DP_"): | |
obj.constraints.remove(const) | |
class DpCreateConstraint(bpy.types.Operator): | |
"""Create a new animated Child Of constraint""" | |
bl_idname = "dp.create" | |
bl_label = "Create Constraint" | |
bl_options = {'REGISTER', 'UNDO'} | |
def execute(self, context): | |
obj = bpy.context.active_object | |
if obj.type == 'ARMATURE': | |
obj = bpy.context.active_pose_bone | |
if len(obj.constraints) == 0: | |
dp_create_dynamic_parent_pbone(self) | |
else: | |
if "DP_" in obj.constraints[-1].name and obj.constraints[-1].influence == 1: | |
dp_disable_dynamic_parent_pbone(self) | |
dp_create_dynamic_parent_pbone(self) | |
else: | |
if len(obj.constraints) == 0: | |
dp_create_dynamic_parent_obj(self) | |
else: | |
if "DP_" in obj.constraints[-1].name and obj.constraints[-1].influence == 1: | |
dp_disable_dynamic_parent_obj(self) | |
dp_create_dynamic_parent_obj(self) | |
return {'FINISHED'} | |
class DpDisableConstraint(bpy.types.Operator): | |
"""Disable the current animated Child Of constraint""" | |
bl_idname = "dp.disable" | |
bl_label = "Disable Constraint" | |
bl_options = {'REGISTER', 'UNDO'} | |
def execute(self, context): | |
obj = bpy.context.active_object | |
if obj.type == 'ARMATURE': | |
dp_disable_dynamic_parent_pbone(self) | |
else: | |
dp_disable_dynamic_parent_obj(self) | |
return {'FINISHED'} | |
class DpClear(bpy.types.Operator): | |
"""Clear Dynamic Parent constraints""" | |
bl_idname = "dp.clear" | |
bl_label = "Clear Dynamic Parent" | |
bl_options = {'REGISTER', 'UNDO'} | |
def execute(self, context): | |
pbone = None | |
obj = bpy.context.active_object | |
if obj.type == 'ARMATURE': | |
pbone = bpy.context.active_pose_bone | |
dp_clear(obj, pbone) | |
return {'FINISHED'} | |
class DpBake(bpy.types.Operator): | |
"""Bake Dynamic Parent animation""" | |
bl_idname = "dp.bake" | |
bl_label = "Bake Dynamic Parent" | |
bl_options = {'REGISTER', 'UNDO'} | |
def execute(self, context): | |
obj = bpy.context.active_object | |
scn = bpy.context.scene | |
if obj.type == 'ARMATURE': | |
obj = bpy.context.active_pose_bone | |
bpy.ops.nla.bake(frame_start=scn.frame_start, | |
frame_end=scn.frame_end, step=1, | |
only_selected=True, visual_keying=True, | |
clear_constraints=False, clear_parents=False, | |
bake_types={'POSE'}) | |
# Removing constraints | |
for const in obj.constraints[:]: | |
if const.name.startswith("DP_"): | |
obj.constraints.remove(const) | |
else: | |
bpy.ops.nla.bake(frame_start=scn.frame_start, | |
frame_end=scn.frame_end, step=1, | |
only_selected=True, visual_keying=True, | |
clear_constraints=False, clear_parents=False, | |
bake_types={'OBJECT'}) | |
# Removing constraints | |
for const in obj.constraints[:]: | |
if const.name.startswith("DP_"): | |
obj.constraints.remove(const) | |
return {'FINISHED'} | |
class DpClearMenu(bpy.types.Menu): | |
"""Clear or bake Dynamic Parent constraints""" | |
bl_label = "Clear Dynamic Parent?" | |
bl_idname = "DP_MT_clear_menu" | |
def draw(self, context): | |
layout = self.layout | |
layout.operator("dp.clear", text="Clear", icon="X") | |
layout.operator("dp.bake", text="Bake and clear", icon="REC") | |
class DpUI(bpy.types.Panel): | |
"""User interface for Dynamic Parent addon""" | |
bl_label = "Dynamic Parent" | |
bl_idname = "DP_PT_ui" | |
bl_space_type = "VIEW_3D" | |
bl_region_type = "TOOLS" | |
def draw(self, context): | |
layout = self.layout | |
col = layout.column(align=True) | |
col.operator("dp.create", text="Create", icon="KEY_HLT") | |
col.operator("dp.disable", text="Disable", icon="KEY_DEHLT") | |
#col.operator("dp.clear", text="Clear", icon="X") | |
#col.operator("wm.call_menu", text="Clear", icon="RIGHTARROW_THIN").name="dp.clear_menu" | |
col.menu("DP_MT_clear_menu", text="Clear") | |
classes = ( | |
DpCreateConstraint, | |
DpDisableConstraint, | |
DpClear, | |
DpBake, | |
DpClearMenu, | |
DpUI, | |
) | |
register, unregister = bpy.utils.register_classes_factory(classes) | |
if __name__ == "__main__": | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment