2026-03-11_4
This commit is contained in:
@@ -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,
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user