WIP: Animation: operators to update the pose library #104673
@ -90,15 +90,16 @@ def pose_library_list_item_context_menu(self: UIList, context: Context) -> None:
|
|||||||
with operator_context(layout, 'INVOKE_DEFAULT'):
|
with operator_context(layout, 'INVOKE_DEFAULT'):
|
||||||
layout.operator("poselib.blend_pose_asset", text="Blend Pose")
|
layout.operator("poselib.blend_pose_asset", text="Blend Pose")
|
||||||
|
|
||||||
layout.operator("poselib.replace_pose_asset", text="Replace Pose")
|
|
||||||
layout.operator("poselib.update_pose_asset", text="Update Pose")
|
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
props = layout.operator("poselib.pose_asset_select_bones", text="Select Pose Bones")
|
props = layout.operator("poselib.pose_asset_select_bones", text="Select Pose Bones")
|
||||||
props.select = True
|
props.select = True
|
||||||
props = layout.operator("poselib.pose_asset_select_bones", text="Deselect Pose Bones")
|
props = layout.operator("poselib.pose_asset_select_bones", text="Deselect Pose Bones")
|
||||||
props.select = False
|
props.select = False
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
layout.operator("poselib.replace_pose_asset", text="Replace Pose Asset")
|
||||||
|
layout.operator("poselib.update_pose_asset", text="Update Pose Asset")
|
||||||
|
|
||||||
if not is_pose_asset_view():
|
if not is_pose_asset_view():
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.operator("asset.assign_action")
|
layout.operator("asset.assign_action")
|
||||||
|
@ -248,7 +248,7 @@ class POSELIB_OT_update_pose_asset(Operator):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
original_pose: Action = context.id
|
original_pose: Action = context.id
|
||||||
was_updated = self._update_pose_asset(original_pose, new_pose, storage_frame_nr)
|
was_updated = self._update_pose_asset(original_pose, new_pose)
|
||||||
finally:
|
finally:
|
||||||
# Clean up by removing the temporary pose Action.
|
# Clean up by removing the temporary pose Action.
|
||||||
bpy.data.actions.remove(new_pose)
|
bpy.data.actions.remove(new_pose)
|
||||||
@ -256,12 +256,11 @@ class POSELIB_OT_update_pose_asset(Operator):
|
|||||||
return {'FINISHED'} if was_updated else {'CANCELLED'}
|
return {'FINISHED'} if was_updated else {'CANCELLED'}
|
||||||
|
|
||||||
def _update_pose_asset(self,
|
def _update_pose_asset(self,
|
||||||
original_pose: Action,
|
pose_to_update: Action,
|
||||||
new_pose: Action,
|
update_source: Action) -> bool:
|
||||||
storage_frame_nr: float,) -> bool:
|
"""Copy keys from `update_source` to `pose_to_update`.
|
||||||
"""Perform the pose update.
|
|
||||||
|
|
||||||
:return: True if the original pose was altered, False otherwise.
|
:return: True if `pose_to_update` was altered, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Create lookup table for FCurves. There's also Action.fcurves.find() but
|
# Create lookup table for FCurves. There's also Action.fcurves.find() but
|
||||||
@ -269,52 +268,31 @@ class POSELIB_OT_update_pose_asset(Operator):
|
|||||||
# each lookup.
|
# each lookup.
|
||||||
original_fcurve_lookup: dict[tuple[str, int], FCurve] = {
|
original_fcurve_lookup: dict[tuple[str, int], FCurve] = {
|
||||||
(fcu.data_path, fcu.array_index): fcu
|
(fcu.data_path, fcu.array_index): fcu
|
||||||
for fcu in original_pose.fcurves
|
for fcu in pose_to_update.fcurves
|
||||||
}
|
}
|
||||||
|
|
||||||
num_fcurves_added = 0
|
num_fcurves_added = 0
|
||||||
num_fcurves_edited = 0
|
num_fcurves_edited = 0
|
||||||
for fcu in new_pose.fcurves:
|
for fcu in update_source.fcurves:
|
||||||
fcu_value = fcu.keyframe_points[0].co.y
|
|
||||||
try:
|
try:
|
||||||
orig_fcu = original_fcurve_lookup[fcu.data_path, fcu.array_index]
|
orig_fcu = original_fcurve_lookup[fcu.data_path, fcu.array_index]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
orig_fcu = None
|
orig_fcu = None
|
||||||
|
|
||||||
if orig_fcu is None:
|
if orig_fcu is None:
|
||||||
# No such FCurve, so create a new one.
|
self._create_new_fcurve(pose_to_update, fcu)
|
||||||
group_name = getattr(fcu.group, 'name', '')
|
|
||||||
new_fcu = original_pose.fcurves.new(
|
|
||||||
fcu.data_path,
|
|
||||||
index=fcu.array_index,
|
|
||||||
action_group=group_name,
|
|
||||||
)
|
|
||||||
new_fcu.keyframe_points.insert(storage_frame_nr, value=fcu_value)
|
|
||||||
new_fcu.update()
|
|
||||||
|
|
||||||
num_fcurves_added += 1
|
num_fcurves_added += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Update the exsting FCurve. This assumes that it is a valid
|
was_updated = self._update_existing_fcurve(pose_to_update, orig_fcu, fcu)
|
||||||
# pose asset, i.e. that it has a single key.
|
if was_updated:
|
||||||
kp = orig_fcu.keyframe_points[0]
|
|
||||||
old_value = kp.co.y
|
|
||||||
|
|
||||||
# Don't bother updating the FCurve if the value was unchanged.
|
|
||||||
if abs(old_value - fcu_value) < 0.0001:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Use co_ui to move the handles along with the value.
|
|
||||||
kp.co_ui.y = fcu_value
|
|
||||||
orig_fcu.update()
|
|
||||||
|
|
||||||
num_fcurves_edited += 1
|
num_fcurves_edited += 1
|
||||||
|
|
||||||
if num_fcurves_added == 0 and num_fcurves_edited == 0:
|
if not any((num_fcurves_added, num_fcurves_edited)):
|
||||||
self.report({'WARNING'}, "Pose did not change, so the asset was not updated")
|
self.report({'WARNING'}, "Pose did not change, so the asset was not updated")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
original_pose.asset_generate_preview()
|
pose_to_update.asset_generate_preview()
|
||||||
|
|
||||||
# Present the results.
|
# Present the results.
|
||||||
msg_parts = []
|
msg_parts = []
|
||||||
@ -335,6 +313,37 @@ class POSELIB_OT_update_pose_asset(Operator):
|
|||||||
return original_pose.fcurves[0].keyframe_points[0].co.x
|
return original_pose.fcurves[0].keyframe_points[0].co.x
|
||||||
return context.scene.frame_current
|
return context.scene.frame_current
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_new_fcurve(action: Action, fcu_to_copy: FCurve) -> None:
|
||||||
|
group_name = getattr(fcu_to_copy.group, 'name', '')
|
||||||
|
|
||||||
|
new_fcu = action.fcurves.new(
|
||||||
|
fcu_to_copy.data_path,
|
||||||
|
index=fcu_to_copy.array_index,
|
||||||
|
action_group=group_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
keyframe_co = fcu_to_copy.keyframe_points[0].co
|
||||||
|
new_fcu.keyframe_points.insert(keyframe_co.x, value=keyframe_co.y)
|
||||||
|
new_fcu.update()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _update_existing_fcurve(action: Action, fcu_existing: FCurve, fcu_to_read: FCurve) -> bool:
|
||||||
|
# Update the exsting FCurve. This assumes that it is a valid
|
||||||
|
# pose asset, i.e. that the FCurve has a single key.
|
||||||
dr.sybren marked this conversation as resolved
|
|||||||
|
kp = fcu_existing.keyframe_points[0]
|
||||||
|
old_value = kp.co.y
|
||||||
|
|
||||||
|
# Don't bother updating the FCurve if the value was unchanged.
|
||||||
dr.sybren marked this conversation as resolved
Nathan Vegdahl
commented
Do we expect there to be floating point error involved here? I wouldn't think so, but I could very well be missing something. But if not, I think it makes more sense to just do a straight Do we expect there to be floating point error involved here? I wouldn't think so, but I could very well be missing something.
But if not, I think it makes more sense to just do a straight `==` comparison, rather than having an arbitrary epsilon.
|
|||||||
|
fcu_value = fcu_to_read.keyframe_points[0].co.y
|
||||||
|
if abs(old_value - fcu_value) < 0.0001:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Use co_ui to move the handles along with the value.
|
||||||
|
kp.co_ui.y = fcu_value
|
||||||
|
fcu_existing.update()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class POSELIB_OT_restore_previous_action(Operator):
|
class POSELIB_OT_restore_previous_action(Operator):
|
||||||
bl_idname = "poselib.restore_previous_action"
|
bl_idname = "poselib.restore_previous_action"
|
||||||
|
Loading…
Reference in New Issue
Block a user
Unimportant nit: it doesn't seem like the
old_value
temporary is necessary.(
fcu_value
, by contrast, I think aids readability, since it's short-handing something that's rather long to read.)Not a big deal either way, though.
It's indeed not really necessary, but to me it makes the
if old_value == fcu_value:
read nicer. That can be resolved by renamingkp
tokeyframe
and it overall being less cryptic.