2026-03-11_4

This commit is contained in:
2026-03-17 15:34:28 -06:00
parent 9706bc055f
commit eef5547a2c
474 changed files with 113268 additions and 27500 deletions
@@ -6,8 +6,11 @@
import bpy
import os
from bpy.types import Operator
from bpy.props import StringProperty, BoolProperty
from bpy.props import StringProperty, IntProperty
from ..utils import collection_containing_armature
ADDON_NAME = __package__.rsplit(".", 1)[0] if "." in __package__ else __package__
@@ -403,40 +406,174 @@ class DLM_OT_migrator_basebody_shapekeys(Operator):
return {"CANCELLED"}
MIGRATOR_STEP_OPS = (
"dlm.migrator_copy_attributes",
"dlm.migrator_migrate_nla",
"dlm.migrator_custom_properties",
"dlm.migrator_bone_constraints",
"dlm.migrator_retarget_relations",
"dlm.migrator_basebody_shapekeys",
)
class DLM_OT_run_character_migration(Operator):
bl_idname = "dlm.run_character_migration"
bl_label = "Run Character Migration"
bl_description = "Run all six migration steps (CopyAttr, MigNLA, MigCustProps, MigBoneConst, RetargRelatives, MigBBodyShapeKeys) in order"
class DLM_OT_migrator_fk_rotations(Operator):
bl_idname = "dlm.migrator_fk_rotations"
bl_label = "MigFKRot"
bl_description = "Copy FK arm and finger rotations from original to replacement (uses constraints)"
bl_icon = "BONE_DATA"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
steps = [
bpy.ops.dlm.migrator_copy_attributes,
bpy.ops.dlm.migrator_migrate_nla,
bpy.ops.dlm.migrator_custom_properties,
bpy.ops.dlm.migrator_bone_constraints,
bpy.ops.dlm.migrator_retarget_relations,
bpy.ops.dlm.migrator_basebody_shapekeys,
]
for i, op in enumerate(steps):
result = op()
if result != {"FINISHED"}:
self.report({"ERROR"}, f"Migration failed at step {i + 1}: {MIGRATOR_STEP_OPS[i]}")
orig, rep = _get_migrator_pair(context)
if not orig or not rep or orig == rep:
self.report({"ERROR"}, "No valid character pair.")
return {"CANCELLED"}
try:
from ..ops.fk_rotations import copy_fk_rotations
ok, msg = copy_fk_rotations(context, orig, rep)
if ok:
self.report({"INFO"}, msg)
return {"FINISHED"}
else:
self.report({"ERROR"}, msg)
return {"CANCELLED"}
self.report({"INFO"}, "Migration complete.")
return {"FINISHED"}
except Exception as e:
self.report({"ERROR"}, str(e))
return {"CANCELLED"}
class DLM_OT_migrator_fk_rotations_bake(Operator):
bl_idname = "dlm.migrator_fk_rotations_bake"
bl_label = "Bake MigFKRot"
bl_description = "Bake FK rotations to keyframes using nla.bake (similar to tweak tools)"
bl_icon = "KEYFRAME"
bl_options = {"REGISTER", "UNDO"}
track_name: StringProperty(name="NLA Track", description="Optional NLA track name for frame range", default="")
post_clean: BoolProperty(name="Post-clean", description="Clean curves after bake", default=False)
def execute(self, context):
orig, rep = _get_migrator_pair(context)
if not orig or not rep or orig == rep:
self.report({"ERROR"}, "No valid character pair.")
return {"CANCELLED"}
try:
from ..ops.fk_rotations import bake_fk_rotations
ok, msg = bake_fk_rotations(context, orig, rep, track_name=self.track_name or None, post_clean=self.post_clean)
if ok:
self.report({"INFO"}, msg)
return {"FINISHED"}
else:
self.report({"ERROR"}, msg)
return {"CANCELLED"}
except Exception as e:
self.report({"ERROR"}, str(e))
return {"CANCELLED"}
class DLM_OT_migrator_fk_rotations_remove(Operator):
bl_idname = "dlm.migrator_fk_rotations_remove"
bl_label = "Remove MigFKRot"
bl_description = "Remove FK rotation COPY_TRANSFORMS constraints (similar to tweak_remove_arm)"
bl_icon = "X"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
orig, rep = _get_migrator_pair(context)
if not orig or not rep or orig == rep:
self.report({"ERROR"}, "No valid character pair.")
return {"CANCELLED"}
try:
from ..ops.fk_rotations import remove_fk_rotations
ok, msg = remove_fk_rotations(context, rep)
if ok:
self.report({"INFO"}, msg)
return {"FINISHED"}
else:
self.report({"ERROR"}, msg)
return {"CANCELLED"}
except Exception as e:
self.report({"ERROR"}, str(e))
return {"CANCELLED"}
class DLM_OT_migrator_remove_original(Operator):
bl_idname = "dlm.migrator_remove_original"
bl_label = "Remove Original"
bl_description = "Delete the original character armature and its data from the scene"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
orig, rep = _get_migrator_pair(context)
if not orig:
self.report({"WARNING"}, "No original character selected")
return {"CANCELLED"}
if orig == rep:
self.report({"ERROR"}, "Original and replacement cannot be the same object")
return {"CANCELLED"}
name = orig.name
# Collect actions from original character before removal
actions_to_remove = set()
if orig.animation_data:
# Active action
if orig.animation_data.action:
actions_to_remove.add(orig.animation_data.action)
# NLA strips
for track in orig.animation_data.nla_tracks:
for strip in track.strips:
if strip.action:
actions_to_remove.add(strip.action)
# Remove collected actions from bpy.data.actions
removed_actions = []
for action in actions_to_remove:
action_name = action.name
try:
bpy.data.actions.remove(action)
removed_actions.append(action_name)
except Exception as e:
self.report({"WARNING"}, f"Could not remove action {action_name}: {e}")
if removed_actions:
self.report({"INFO"}, f"Removed {len(removed_actions)} action(s) from original")
try:
# Try to find and delete the collection containing the original character
coll = collection_containing_armature(orig)
if coll:
coll_name = coll.name # Store name BEFORE removal (RNA invalidates after remove)
context.scene.dynamic_link_manager.original_character = None
try:
bpy.data.collections.remove(coll)
self.report({"INFO"}, f"Removed collection: {coll_name}")
except Exception as remove_err:
# Collection may have already been removed by another process
self.report({"WARNING"}, f"Collection {coll_name} removal issue: {remove_err}")
else:
# Fallback: just delete the armature object
bpy.data.objects.remove(orig, do_unlink=True)
context.scene.dynamic_link_manager.original_character = None
self.report({"INFO"}, f"Removed original character: {name}")
except Exception as e:
self.report({"ERROR"}, f"Failed to remove original: {e}")
return {"CANCELLED"}
# Rename replacement actions with ".rep" suffix
if rep and rep.animation_data:
renamed_actions = []
# Active action
if rep.animation_data.action and ".rep" in rep.animation_data.action.name:
old_name = rep.animation_data.action.name
new_name = old_name.replace(".rep", "")
rep.animation_data.action.name = new_name
renamed_actions.append(f"{old_name} -> {new_name}")
# NLA strips
for track in rep.animation_data.nla_tracks:
for strip in track.strips:
if strip.action and ".rep" in strip.action.name:
old_name = strip.action.name
new_name = old_name.replace(".rep", "")
strip.action.name = new_name
renamed_actions.append(f"{old_name} -> {new_name}")
if renamed_actions:
self.report({"INFO"}, f"Renamed {len(renamed_actions)} replacement action(s)")
return {"FINISHED"}
class DLM_OT_picker_original_character(Operator):
bl_idname = "dlm.picker_original_character"
bl_label = "Pick Original"
@@ -673,7 +810,7 @@ OPERATOR_CLASSES = [
DLM_OT_make_paths_relative,
DLM_OT_make_paths_absolute,
DLM_OT_relocate_single_library,
DLM_OT_run_character_migration,
DLM_OT_migrator_remove_original,
DLM_OT_picker_original_character,
DLM_OT_picker_replacement_character,
DLM_OT_migrator_copy_attributes,
@@ -691,4 +828,7 @@ OPERATOR_CLASSES = [
DLM_OT_tweak_add_both,
DLM_OT_tweak_remove_both,
DLM_OT_tweak_bake_both,
DLM_OT_migrator_fk_rotations,
DLM_OT_migrator_fk_rotations_bake,
DLM_OT_migrator_fk_rotations_remove,
]