2026-03-16

This commit is contained in:
2026-03-17 15:39:39 -06:00
parent 67782275d5
commit 330fed4231
29 changed files with 532 additions and 199 deletions
@@ -3,7 +3,7 @@ schema_version = "1.0.0"
id = "dynamiclinkmanager"
name = "Dynamic Link Manager"
tagline = "Character migrator and linked library tools"
version = "0.2.0"
version = "0.3.0"
type = "add-on"
# Optional: Semantic Versioning
@@ -482,14 +482,36 @@ def run_retarg_relatives(orig, rep, rep_descendants, orig_to_rep):
for ob in candidates:
if ob.parent == orig:
ob.parent = rep
for c in getattr(ob, "constraints", []):
if getattr(c, "target", None) == orig:
c.target = rep
if ob.type == "MESH" and ob.modifiers:
for m in ob.modifiers:
if getattr(m, "object", None) == orig:
m.object = rep
# Retarget constraints on ALL objects (including orig hierarchy like eyes)
for ob in bpy.data.objects:
for c in getattr(ob, "constraints", []):
if getattr(c, "target", None) == orig:
c.target = rep
# Retarget bone constraints on ALL armatures (other characters, etc.)
for ob in bpy.data.objects:
if ob.type != "ARMATURE" or not ob.pose:
continue
for pbone in ob.pose.bones:
for c in pbone.constraints:
if getattr(c, "target", None) == orig:
c.target = rep
# Camera DOF: retarget focus_object from orig to rep
for ob in bpy.data.objects:
if ob.type != 'CAMERA':
continue
camera = ob.data
if not camera.dof:
continue
if camera.dof.focus_object == orig:
camera.dof.focus_object = rep
def _base_body_name_match(ob):
"""True if object looks like the base body mesh (MESH, name has body+base)."""
@@ -3,7 +3,7 @@
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
"""Tweak tools: add/remove/bake COPY_TRANSFORMS on Rigify arm/leg tweak bones."""
"""Tweak tools: add/remove/bake COPY_TRANSFORMS on Rigify tweak bones (arm, leg, or all)."""
import bpy
@@ -22,7 +22,7 @@ TWEAK_CONSTRAINT_NAME = "Copy from Original"
def get_tweak_bones(armature, limb):
"""Return list of tweak bone names that exist on armature. limb in 'arm', 'leg', 'both'."""
"""Return list of tweak bone names that exist on armature. limb in 'arm', 'leg', 'body', 'both'."""
if not armature or armature.type != "ARMATURE" or not armature.pose:
return []
bones = armature.pose.bones
@@ -30,8 +30,20 @@ def get_tweak_bones(armature, limb):
names = ARM_TWEAK_BONES
elif limb == "leg":
names = LEG_TWEAK_BONES
elif limb == "body":
# Body/torso tweaks: tweak_spine, spine_fk, etc. (no arm/leg)
arm_leg_names = set(ARM_TWEAK_BONES + LEG_TWEAK_BONES)
names = [
b.name for b in bones
if ("tweak" in b.name.lower() or "spine_fk" in b.name.lower())
and b.name not in arm_leg_names
]
elif limb == "both":
names = ARM_TWEAK_BONES + LEG_TWEAK_BONES
# ALL tweak bones: any bone with "tweak" or "spine_fk" in the name
names = [
b.name for b in bones
if "tweak" in b.name.lower() or "spine_fk" in b.name.lower()
]
else:
return []
return [n for n in names if n in bones]
@@ -101,7 +113,7 @@ def bake_tweak_constraints(context, orig, rep, limb, track_name, post_clean):
# Select only tweak bones on rep
bpy.ops.pose.select_all(action="DESELECT")
for name in names:
rep.pose.bones[name].bone.select = True
rep.pose.bones[name].select = True
# Bake
try:
@@ -735,10 +735,72 @@ class DLM_OT_tweak_bake_leg(Operator):
return {"CANCELLED"}
class DLM_OT_tweak_add_body(Operator):
bl_idname = "dlm.tweak_add_body"
bl_label = "Add Body Tweaks"
bl_description = "Add tweak bone constraints to body/torso bones (spine, no arm/leg)"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
from ..ops import tweak_tools
tweak_tools.add_tweak_constraints(orig, rep, "body")
self.report({"INFO"}, "Body tweak constraints added.")
return {"FINISHED"}
class DLM_OT_tweak_remove_body(Operator):
bl_idname = "dlm.tweak_remove_body"
bl_label = "Remove Body Tweaks"
bl_description = "Remove body/torso tweak constraints from the replacement character"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
from ..ops import tweak_tools
n = tweak_tools.remove_tweak_constraints(orig, rep, "body")
self.report({"INFO"}, f"Removed {n} body tweak constraints.")
return {"FINISHED"}
class DLM_OT_tweak_bake_body(Operator):
bl_idname = "dlm.tweak_bake_body"
bl_label = "Bake Body Tweaks"
bl_description = "Bake body/torso tweak constraints to keyframes and optionally remove constraints"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return _tweak_poll(context)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
props = context.scene.dynamic_link_manager
from ..ops import tweak_tools
ok, msg = tweak_tools.bake_tweak_constraints(
context, orig, rep, "body",
getattr(props, "tweak_nla_track_name", "") or "",
getattr(props, "tweak_bake_post_clean", False),
)
if ok:
self.report({"INFO"}, msg)
return {"FINISHED"}
self.report({"ERROR"}, msg)
return {"CANCELLED"}
class DLM_OT_tweak_add_both(Operator):
bl_idname = "dlm.tweak_add_both"
bl_label = "Add Arm & Leg Tweaks"
bl_description = "Add tweak bone constraints to both arm and leg bones"
bl_label = "Add All Tweaks"
bl_description = "Add tweak bone constraints to all tweak bones (arm, leg, body)"
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -749,14 +811,14 @@ class DLM_OT_tweak_add_both(Operator):
orig, rep = _get_migrator_pair(context)
from ..ops import tweak_tools
tweak_tools.add_tweak_constraints(orig, rep, "both")
self.report({"INFO"}, "Arm & leg tweak constraints added.")
self.report({"INFO"}, "All tweak constraints added.")
return {"FINISHED"}
class DLM_OT_tweak_remove_both(Operator):
bl_idname = "dlm.tweak_remove_both"
bl_label = "Remove Arm & Leg Tweaks"
bl_description = "Remove all arm and leg tweak constraints from the replacement character"
bl_label = "Remove All Tweaks"
bl_description = "Remove all tweak constraints from the replacement character"
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -773,8 +835,8 @@ class DLM_OT_tweak_remove_both(Operator):
class DLM_OT_tweak_bake_both(Operator):
bl_idname = "dlm.tweak_bake_both"
bl_label = "Bake Arm & Leg Tweaks"
bl_description = "Bake all arm and leg tweak constraints to keyframes and optionally remove constraints"
bl_label = "Bake All Tweaks"
bl_description = "Bake all tweak constraints (arm, leg, body) to keyframes and optionally remove constraints"
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -825,6 +887,9 @@ OPERATOR_CLASSES = [
DLM_OT_tweak_add_leg,
DLM_OT_tweak_remove_leg,
DLM_OT_tweak_bake_leg,
DLM_OT_tweak_add_body,
DLM_OT_tweak_remove_body,
DLM_OT_tweak_bake_body,
DLM_OT_tweak_add_both,
DLM_OT_tweak_remove_both,
DLM_OT_tweak_bake_both,
@@ -119,9 +119,13 @@ class DLM_PT_main_panel(Panel):
row.operator("dlm.tweak_remove_leg", text="Remove Leg", icon="X")
row.operator("dlm.tweak_bake_leg", text="Bake Leg", icon="KEYFRAME")
row = tweak_box.row(align=True)
row.operator("dlm.tweak_add_both", text="Add Both", icon="CONSTRAINT_BONE")
row.operator("dlm.tweak_remove_both", text="Remove Both", icon="X")
row.operator("dlm.tweak_bake_both", text="Bake Both", icon="KEYFRAME")
row.operator("dlm.tweak_add_body", text="Add Body", icon="CONSTRAINT_BONE")
row.operator("dlm.tweak_remove_body", text="Remove Body", icon="X")
row.operator("dlm.tweak_bake_body", text="Bake Body", icon="KEYFRAME")
row = tweak_box.row(align=True)
row.operator("dlm.tweak_add_both", text="Add All", icon="CONSTRAINT_BONE")
row.operator("dlm.tweak_remove_both", text="Remove All", icon="X")
row.operator("dlm.tweak_bake_both", text="Bake All", icon="KEYFRAME")
row = tweak_box.row()
row.prop(props, "tweak_nla_track_name", text="NLA track")
row = tweak_box.row()