FKrot bake finally working
This commit is contained in:
+3272
-10410
File diff suppressed because one or more lines are too long
+157
-75
@@ -132,62 +132,111 @@ def copy_fk_rotations(context, orig, rep):
|
|||||||
# If selection fails, the constraint result is still applied
|
# If selection fails, the constraint result is still applied
|
||||||
pass # Silently ignore - constraints still drove the pose
|
pass # Silently ignore - constraints still drove the pose
|
||||||
|
|
||||||
# Step 4: Remove constraints
|
# Constraints remain active for bake step
|
||||||
for rep_bone, c in constraints_added:
|
print(f"[DLM MigFKRot] Copied {len(constraints_added)} bones (constraints active)")
|
||||||
try:
|
return True, f"Copied FK rotations for {len(constraints_added)} bones (constraints active - run Bake to finalize)"
|
||||||
if c in rep_bone.constraints:
|
|
||||||
rep_bone.constraints.remove(c)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
print(f"[DLM MigFKRot] Copied {len(constraints_added)} bones")
|
|
||||||
return True, f"Copied FK rotations for {len(constraints_added)} bones"
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[DLM MigFKRot] Error: {e}")
|
print(f"[DLM MigFKRot] Error: {e}")
|
||||||
return False, str(e)
|
# Cleanup constraints on error
|
||||||
|
|
||||||
finally:
|
|
||||||
# Cleanup any remaining constraints
|
|
||||||
for rep_bone, c in constraints_added:
|
for rep_bone, c in constraints_added:
|
||||||
try:
|
try:
|
||||||
if c in rep_bone.constraints:
|
if c in rep_bone.constraints:
|
||||||
rep_bone.constraints.remove(c)
|
rep_bone.constraints.remove(c)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Only restore active object, don't remove constraints
|
||||||
if original_active:
|
if original_active:
|
||||||
context.view_layer.objects.active = original_active
|
context.view_layer.objects.active = original_active
|
||||||
|
|
||||||
|
|
||||||
|
def _get_action_frame_range(action):
|
||||||
|
"""Get the full frame range from action keyframes (not just strip in/out)."""
|
||||||
|
if not action:
|
||||||
|
return None
|
||||||
|
|
||||||
|
frames = set()
|
||||||
|
|
||||||
|
# Try Blender 5.0+ channelbags first
|
||||||
|
if hasattr(action, 'channelbags'):
|
||||||
|
for cb in action.channelbags:
|
||||||
|
if hasattr(cb, 'fcurves'):
|
||||||
|
for fc in cb.fcurves:
|
||||||
|
for kp in fc.keyframe_points:
|
||||||
|
frames.add(int(kp.co.x))
|
||||||
|
|
||||||
|
# Fallback to fcurves
|
||||||
|
if hasattr(action, 'fcurves') and not frames:
|
||||||
|
for fc in action.fcurves:
|
||||||
|
for kp in fc.keyframe_points:
|
||||||
|
frames.add(int(kp.co.x))
|
||||||
|
|
||||||
|
if frames:
|
||||||
|
return (min(frames), max(frames))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_bone_name_from_data_path(data_path):
|
||||||
|
"""Extract bone name from fcurve data_path like 'pose.bones["bone.name"].rotation_euler'."""
|
||||||
|
if not data_path:
|
||||||
|
return None
|
||||||
|
if 'pose.bones["' in data_path:
|
||||||
|
start = data_path.find('["') + 2
|
||||||
|
end = data_path.find('"]', start)
|
||||||
|
if start > 1 and end > start:
|
||||||
|
return data_path[start:end]
|
||||||
|
elif "pose.bones['" in data_path:
|
||||||
|
start = data_path.find("['") + 2
|
||||||
|
end = data_path.find("']", start)
|
||||||
|
if start > 1 and end > start:
|
||||||
|
return data_path[start:end]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def bake_fk_rotations(context, orig, rep, track_name=None, post_clean=False):
|
def bake_fk_rotations(context, orig, rep, track_name=None, post_clean=False):
|
||||||
"""
|
"""
|
||||||
Bake FK arm/finger rotations to keyframes.
|
Bake FK arm/finger rotations to a new NLA track with replace mode.
|
||||||
Similar to tweak_tools.bake_tweak_constraints - adds constraints, bakes, then removes.
|
|
||||||
Returns (True, message) or (False, error_message).
|
Returns (True, message) or (False, error_message).
|
||||||
"""
|
"""
|
||||||
|
print(f"[DLM MigFKRot Bake] START: orig={orig.name}, rep={rep.name}")
|
||||||
|
|
||||||
fk_names = _get_fk_bones(rep)
|
fk_names = _get_fk_bones(rep)
|
||||||
|
print(f"[DLM MigFKRot Bake] Found {len(fk_names)} FK bones: {fk_names[:5]}...")
|
||||||
|
|
||||||
if not fk_names:
|
if not fk_names:
|
||||||
return False, f"No FK bones found on {rep.name}"
|
return False, f"No FK bones found on {rep.name}"
|
||||||
|
|
||||||
# Filter to bones that exist on both
|
# Filter to bones that exist on both
|
||||||
common_bones = [n for n in fk_names if n in orig.pose.bones and n in rep.pose.bones]
|
common_bones = [n for n in fk_names if n in orig.pose.bones and n in rep.pose.bones]
|
||||||
|
print(f"[DLM MigFKRot Bake] {len(common_bones)} common bones between orig and rep")
|
||||||
|
|
||||||
if not common_bones:
|
if not common_bones:
|
||||||
return False, "No matching FK bones found on both armatures"
|
return False, "No matching FK bones found on both armatures"
|
||||||
|
|
||||||
scene = context.scene
|
# Get source action for frame range (from keyframes, not strip bounds)
|
||||||
|
source_action = None
|
||||||
|
if rep.animation_data:
|
||||||
|
if rep.animation_data.action:
|
||||||
|
source_action = rep.animation_data.action
|
||||||
|
elif rep.animation_data.nla_tracks:
|
||||||
|
for track in rep.animation_data.nla_tracks:
|
||||||
|
if track.strips:
|
||||||
|
for strip in track.strips:
|
||||||
|
if strip.action:
|
||||||
|
source_action = strip.action
|
||||||
|
break
|
||||||
|
if source_action:
|
||||||
|
break
|
||||||
|
|
||||||
# Get frame range from track or scene
|
# Get frame range from source action keyframes
|
||||||
frame_range = None
|
frame_range = _get_action_frame_range(source_action) if source_action else None
|
||||||
if track_name and rep.animation_data and rep.animation_data.nla_tracks:
|
|
||||||
track = rep.animation_data.nla_tracks.get(track_name)
|
|
||||||
if track and track.strips:
|
|
||||||
start = min(s.frame_start for s in track.strips)
|
|
||||||
end = max(s.frame_end for s in track.strips)
|
|
||||||
frame_range = (int(start), int(end))
|
|
||||||
|
|
||||||
if not frame_range:
|
if not frame_range:
|
||||||
frame_range = (scene.frame_start, scene.frame_end)
|
# Fallback to scene range
|
||||||
|
frame_range = (context.scene.frame_start, context.scene.frame_end)
|
||||||
|
|
||||||
frame_start, frame_end = frame_range
|
frame_start, frame_end = frame_range
|
||||||
|
|
||||||
@@ -197,58 +246,78 @@ def bake_fk_rotations(context, orig, rep, track_name=None, post_clean=False):
|
|||||||
if rep.mode != "POSE":
|
if rep.mode != "POSE":
|
||||||
bpy.ops.object.mode_set(mode="POSE")
|
bpy.ops.object.mode_set(mode="POSE")
|
||||||
|
|
||||||
# Step 1: Check for existing COPY_TRANSFORMS constraints from MigFKRot
|
# Step 1: Check for existing COPY_TRANSFORMS constraints
|
||||||
constraints_added = []
|
constraints_added = []
|
||||||
for bone_name in common_bones:
|
for bone_name in common_bones:
|
||||||
rep_bone = rep.pose.bones[bone_name]
|
rep_bone = rep.pose.bones[bone_name]
|
||||||
|
|
||||||
# Check if already has constraint from MigFKRot
|
|
||||||
existing = [c for c in rep_bone.constraints if c.type == 'COPY_TRANSFORMS' and getattr(c, 'target', None) == orig]
|
existing = [c for c in rep_bone.constraints if c.type == 'COPY_TRANSFORMS' and getattr(c, 'target', None) == orig]
|
||||||
if existing:
|
if existing:
|
||||||
constraints_added.append((rep_bone, existing[0]))
|
constraints_added.append((rep_bone, existing[0]))
|
||||||
|
|
||||||
print(f"[DLM MigFKRot Bake] Found {len(constraints_added)} existing constraints")
|
print(f"[DLM MigFKRot Bake] Found {len(constraints_added)} constraints from {orig.name} to {rep.name}")
|
||||||
|
print(f"[DLM MigFKRot Bake] Frame range: {frame_start}-{frame_end}")
|
||||||
|
|
||||||
# Step 2: Select FK bones for baking
|
# Step 2: Create new action and bake with nla.bake
|
||||||
selected_count = 0
|
# We use the current action (not a new one) because nla.bake needs use_current_action=True
|
||||||
|
# After baking, we'll clean up non-FK fcurves from the baked action
|
||||||
|
|
||||||
|
# First, ensure there's an action to bake to
|
||||||
|
if rep.animation_data is None:
|
||||||
|
rep.animation_data_create()
|
||||||
|
|
||||||
|
# If there's no current action, create one
|
||||||
|
if not rep.animation_data.action:
|
||||||
|
action_name = f"{rep.name}_FK_Bake_{frame_start}-{frame_end}"
|
||||||
|
new_action = bpy.data.actions.new(name=action_name)
|
||||||
|
if len(new_action.slots) == 0:
|
||||||
|
new_action.slots.new(name=rep.name, id_type='OBJECT')
|
||||||
|
slot = new_action.slots[0]
|
||||||
|
try:
|
||||||
|
rep.animation_data.action_slot = slot
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DLM MigFKRot Bake] Warning: Could not set slot: {e}")
|
||||||
|
|
||||||
|
# Store the action we're baking to
|
||||||
|
baked_action = rep.animation_data.action
|
||||||
|
print(f"[DLM MigFKRot Bake] Baking to action: {baked_action.name if baked_action else 'None'}")
|
||||||
|
|
||||||
|
# Step 3: Select only FK bones and bake with only_selected=True
|
||||||
|
print(f"[DLM MigFKRot Bake] Selecting {len(common_bones)} FK bones...")
|
||||||
try:
|
try:
|
||||||
bpy.ops.pose.select_all(action="DESELECT")
|
# Deselect all bones first
|
||||||
for name in fk_names:
|
bpy.ops.pose.select_all(action='DESELECT')
|
||||||
if name in rep.pose.bones:
|
|
||||||
rep.pose.bones[name].bone.select = True
|
|
||||||
selected_count += 1
|
|
||||||
except (RuntimeError, AttributeError):
|
|
||||||
selected_count = 0 # Selection failed, will bake all
|
|
||||||
|
|
||||||
# Step 3: Frame-by-frame bake (nla.bake with only_selected is unreliable in Blender 5.0)
|
|
||||||
print(f"[DLM MigFKRot Bake] Baking frames {frame_start}-{frame_end} for {len(common_bones)} bones")
|
|
||||||
try:
|
|
||||||
# Create action if needed
|
|
||||||
if not rep.animation_data:
|
|
||||||
rep.animation_data_create()
|
|
||||||
if not rep.animation_data.action:
|
|
||||||
action = bpy.data.actions.new(name=f"{rep.name}_FK_Baked")
|
|
||||||
rep.animation_data.action = action
|
|
||||||
|
|
||||||
# Bake frame by frame
|
# Select only our FK bones
|
||||||
for frame in range(frame_start, frame_end + 1):
|
for bone_name in common_bones:
|
||||||
scene.frame_set(frame)
|
if bone_name in rep.pose.bones:
|
||||||
context.view_layer.update()
|
|
||||||
|
|
||||||
for bone_name in common_bones:
|
|
||||||
rep_bone = rep.pose.bones[bone_name]
|
rep_bone = rep.pose.bones[bone_name]
|
||||||
|
# Try different ways to select the bone (Blender 5.0 compatibility)
|
||||||
# Insert keyframe based on rotation mode
|
try:
|
||||||
if rep_bone.rotation_mode == 'QUATERNION':
|
# Method 1: Direct selection on pose bone
|
||||||
rep_bone.keyframe_insert(data_path="rotation_quaternion")
|
rep_bone.select = True
|
||||||
elif rep_bone.rotation_mode == 'AXIS_ANGLE':
|
except (AttributeError, TypeError):
|
||||||
rep_bone.keyframe_insert(data_path="rotation_axis_angle")
|
try:
|
||||||
else:
|
# Method 2: Selection on the bone property
|
||||||
rep_bone.keyframe_insert(data_path="rotation_euler")
|
if hasattr(rep_bone, 'bone'):
|
||||||
|
rep_bone.bone.select = True
|
||||||
scene.frame_set(frame_start) # Reset to start
|
except (AttributeError, TypeError):
|
||||||
print(f"[DLM MigFKRot Bake] Baked {frame_end - frame_start + 1} frames")
|
pass # Selection failed, continue anyway
|
||||||
|
|
||||||
|
print(f"[DLM MigFKRot Bake] Running nla.bake with only_selected=True...")
|
||||||
|
bpy.ops.nla.bake(
|
||||||
|
frame_start=frame_start,
|
||||||
|
frame_end=frame_end,
|
||||||
|
step=1,
|
||||||
|
only_selected=True,
|
||||||
|
visual_keying=True,
|
||||||
|
clear_constraints=False,
|
||||||
|
clear_parents=False,
|
||||||
|
use_current_action=True,
|
||||||
|
clean_curves=False,
|
||||||
|
bake_types={"POSE"},
|
||||||
|
channel_types={"ROTATION"},
|
||||||
|
)
|
||||||
|
print(f"[DLM MigFKRot Bake] nla.bake completed successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Clean up constraints on failure
|
# Clean up constraints on failure
|
||||||
for rep_bone, c in constraints_added:
|
for rep_bone, c in constraints_added:
|
||||||
@@ -257,21 +326,34 @@ def bake_fk_rotations(context, orig, rep, track_name=None, post_clean=False):
|
|||||||
rep_bone.constraints.remove(c)
|
rep_bone.constraints.remove(c)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return False, str(e)
|
return False, f"nla.bake failed: {e}"
|
||||||
|
|
||||||
# Step 4: Remove the temporary constraints
|
# Step 4: Remove constraints
|
||||||
removed = 0
|
removed_count = 0
|
||||||
for rep_bone, c in constraints_added:
|
for rep_bone, c in constraints_added:
|
||||||
try:
|
try:
|
||||||
if c in rep_bone.constraints:
|
if c in rep_bone.constraints:
|
||||||
rep_bone.constraints.remove(c)
|
rep_bone.constraints.remove(c)
|
||||||
removed += 1
|
removed_count += 1
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
print(f"[DLM MigFKRot Bake] Removed {removed} constraints")
|
print(f"[DLM MigFKRot Bake] Removed {removed_count} constraints")
|
||||||
|
|
||||||
|
# Step 5: Create new NLA track at TOP with replace mode
|
||||||
|
# Use the baked action (which now has only FK fcurves)
|
||||||
|
if rep.animation_data and baked_action:
|
||||||
|
nla_track = rep.animation_data.nla_tracks.new(prev=None)
|
||||||
|
nla_track.name = f"FK_Bake_{frame_start}-{frame_end}"
|
||||||
|
|
||||||
|
strip = nla_track.strips.new(name=baked_action.name, start=frame_start, action=baked_action)
|
||||||
|
strip.frame_end = frame_end
|
||||||
|
strip.blend_type = 'REPLACE'
|
||||||
|
strip.use_auto_blend = False
|
||||||
|
|
||||||
|
print(f"[DLM MigFKRot Bake] Created NLA track '{nla_track.name}' (REPLACE mode)")
|
||||||
|
|
||||||
if not post_clean:
|
if not post_clean:
|
||||||
return True, f"Baked {len(common_bones)} FK bones ({frame_start}-{frame_end})."
|
return True, f"Baked {len(common_bones)} FK bones to NLA track ({frame_start}-{frame_end})."
|
||||||
|
|
||||||
# Post-clean
|
# Post-clean
|
||||||
win = context.window
|
win = context.window
|
||||||
@@ -292,7 +374,7 @@ def bake_fk_rotations(context, orig, rep, track_name=None, post_clean=False):
|
|||||||
pass
|
pass
|
||||||
break
|
break
|
||||||
|
|
||||||
return True, f"Baked and cleaned {len(common_bones)} FK bones ({frame_start}-{frame_end})."
|
return True, f"Baked and cleaned {len(common_bones)} FK bones to NLA track ({frame_start}-{frame_end})."
|
||||||
|
|
||||||
|
|
||||||
def remove_fk_rotations(context, rep):
|
def remove_fk_rotations(context, rep):
|
||||||
|
|||||||
Reference in New Issue
Block a user