2022-05-06 16:30:20 +02:00
|
|
|
from numpy import dstack
|
2021-04-08 19:25:05 +02:00
|
|
|
import bpy
|
|
|
|
from . import fn
|
|
|
|
|
|
|
|
|
2022-04-20 12:02:19 +02:00
|
|
|
class AW_OT_world_space_copy(bpy.types.Operator):
|
2022-04-27 12:01:39 +02:00
|
|
|
bl_idname = "pose.world_space_copy"
|
2021-04-08 19:25:05 +02:00
|
|
|
bl_label = "World Copy"
|
|
|
|
bl_description = "Copy world space transforms. Store active bone matrix"
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
|
|
|
return context.object and context.mode == 'POSE' and context.active_pose_bone
|
|
|
|
|
|
|
|
def execute(self, context):
|
2022-05-06 16:30:20 +02:00
|
|
|
### only active pose bone
|
|
|
|
# bpy.types.ViewLayer.world_space_store = context.object.matrix_world @ context.active_pose_bone.matrix
|
|
|
|
|
|
|
|
if len(context.selected_pose_bones) == 1:
|
|
|
|
bpy.types.ViewLayer.world_space_store = context.object.matrix_world @ context.active_pose_bone.matrix
|
|
|
|
self.report({'INFO'}, 'Copied active pose bone matrix')
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
pose_bones = [b for b in context.selected_pose_bones]
|
|
|
|
armatures = set([b.id_data for b in pose_bones])
|
|
|
|
|
|
|
|
pose_l = [] # pose_d = {}
|
|
|
|
if len(armatures) == 1:
|
|
|
|
# only relate to bone name
|
|
|
|
for b in context.selected_pose_bones:
|
|
|
|
# pose_d[b.name] = context.object.matrix_world @ b.matrix
|
|
|
|
pose_l.append([b.name, b.id_data.matrix_world @ b.matrix])
|
|
|
|
self.report({'INFO'}, f'Copied {len(pose_l)} pose bones matrices')
|
|
|
|
|
|
|
|
else:
|
|
|
|
# for obj in context.objects_in_mode_unique_data:
|
|
|
|
for b in context.selected_pose_bones:
|
|
|
|
# pose_d[f'{b.id_data.name}/{b.name}'] = b.id_data.matrix_world @ b.matrix
|
|
|
|
pose_l.append([b.name, b.id_data.matrix_world @ b.matrix, b.id_data.name])
|
|
|
|
self.report({'INFO'}, f'Copied {len(pose_l)} pose bones matrices (multi-armatures)')
|
|
|
|
|
|
|
|
bpy.types.ViewLayer.world_space_store = pose_l
|
2021-04-08 19:25:05 +02:00
|
|
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
2022-05-06 16:30:20 +02:00
|
|
|
def set_matrix_and_key(pose, context=None, key=True):
|
|
|
|
context = context or bpy.context
|
|
|
|
ct = 0
|
|
|
|
if isinstance(context.view_layer.world_space_store, list):
|
|
|
|
## list structure : [0 bone_name, 1 matrix, 2[:armature_obj_name]]
|
|
|
|
# Paste in parental hierarchical relation (seems right order by default but need update between bones !)
|
|
|
|
for pb in context.selected_pose_bones:
|
|
|
|
for blist in pose:
|
|
|
|
|
|
|
|
if pb.name != blist[0]:
|
|
|
|
continue
|
|
|
|
|
|
|
|
# skip if there is an object name and obj name
|
|
|
|
if len(blist) > 2 and blist[2] != pb.id_data.name:
|
|
|
|
continue
|
|
|
|
|
|
|
|
print(f'Paste "{pb.name}" position')
|
|
|
|
pb.matrix = pb.id_data.matrix_world.inverted() @ blist[1]
|
|
|
|
ct += 1
|
|
|
|
|
|
|
|
context.evaluated_depsgraph_get() # Refresh depsgraph so children position is placed according to parent position
|
|
|
|
break # go to next pose bone
|
|
|
|
else:
|
|
|
|
context.active_pose_bone.matrix = context.object.matrix_world.inverted() @ pose
|
|
|
|
ct = 1
|
|
|
|
|
|
|
|
if not ct:
|
|
|
|
return
|
|
|
|
|
|
|
|
if key:
|
|
|
|
bpy.ops.anim.keyframe_insert(type='LocRotScale')
|
|
|
|
|
|
|
|
return ct
|
|
|
|
|
|
|
|
|
2022-04-20 12:02:19 +02:00
|
|
|
class AW_OT_world_space_paste(bpy.types.Operator):
|
2022-04-27 12:01:39 +02:00
|
|
|
bl_idname = "pose.world_space_paste"
|
2021-04-08 19:25:05 +02:00
|
|
|
bl_label = "World Paste"
|
2022-05-05 15:39:23 +02:00
|
|
|
bl_description = "Paste world space transforms. Apply stored matrix to active bone and key LocRotScale"
|
2021-04-08 19:25:05 +02:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
2022-04-27 12:01:39 +02:00
|
|
|
if not hasattr(bpy.context.view_layer, 'world_space_store'):
|
|
|
|
cls.poll_message_set("Nothing To Paste")
|
|
|
|
return False
|
2021-04-08 19:25:05 +02:00
|
|
|
return context.object and context.mode == 'POSE' and context.active_pose_bone
|
|
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
2022-05-06 16:30:20 +02:00
|
|
|
ct = set_matrix_and_key(context.view_layer.world_space_store, context=context)
|
|
|
|
if not ct:
|
|
|
|
self.report({'ERROR'}, 'No pose pasted')
|
|
|
|
return {"CANCELLED"}
|
|
|
|
if ct != len(context.selected_pose_bones):
|
|
|
|
self.report({'WARNING'}, f'{ct}/{len(context.selected_pose_bones)} bone position pasted')
|
|
|
|
else:
|
|
|
|
self.report({'INFO'}, f'{ct} bone position pasted')
|
|
|
|
|
2022-05-05 15:39:23 +02:00
|
|
|
## only if autokey is On
|
|
|
|
# if context.scene.tool_settings.use_keyframe_insert_auto:
|
|
|
|
# bpy.ops.anim.keyframe_insert_menu(type='LocRotScale') # Available
|
|
|
|
|
2021-04-08 19:25:05 +02:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
2022-04-20 12:02:19 +02:00
|
|
|
class AW_OT_world_space_paste_next(bpy.types.Operator):
|
2022-04-27 12:01:39 +02:00
|
|
|
bl_idname = "pose.world_space_paste_next"
|
2021-04-08 19:25:05 +02:00
|
|
|
bl_label = "World Paste Jump"
|
2022-05-05 15:39:23 +02:00
|
|
|
bl_description = "Paste world space transforms and keyframe available chanels\
|
2022-05-06 16:30:20 +02:00
|
|
|
\nThen jump to prev/next key (on active pose bone)\
|
2022-05-05 15:39:23 +02:00
|
|
|
\nKey Loc rot scale only"
|
2021-04-08 19:25:05 +02:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
2022-04-27 12:01:39 +02:00
|
|
|
if not hasattr(bpy.context.view_layer, 'world_space_store'):
|
|
|
|
cls.poll_message_set("Nothing To Paste")
|
|
|
|
return False
|
2021-04-08 19:25:05 +02:00
|
|
|
return context.object and context.mode == 'POSE' and context.active_pose_bone
|
|
|
|
|
|
|
|
prev: bpy.props.BoolProperty()
|
|
|
|
|
|
|
|
def execute(self, context):
|
2022-05-06 16:30:20 +02:00
|
|
|
ct = set_matrix_and_key(context.view_layer.world_space_store, context=context)
|
|
|
|
if not ct:
|
|
|
|
self.report({'ERROR'}, 'No pose pasted')
|
|
|
|
return {"CANCELLED"}
|
|
|
|
|
2021-04-08 19:25:05 +02:00
|
|
|
# jump to next key
|
|
|
|
act = fn.get_obj_action(context.object)
|
|
|
|
if not act:
|
|
|
|
self.report({'ERROR'}, 'No action on armature')
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
kx = [k.co.x for fcu in act.fcurves
|
|
|
|
if fcu.data_path.split('"')[1] == context.active_pose_bone.bone.name
|
|
|
|
for k in fcu.keyframe_points]
|
|
|
|
|
|
|
|
if not kx:
|
|
|
|
self.report({'ERROR'}, 'No keys on action available (no keyframe added)')
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
# for fcu in act.fcurves:
|
|
|
|
# if fcu.data_path.split('"')[1] == context.active_pose_bone.bone.name:
|
|
|
|
|
|
|
|
if self.prev:
|
|
|
|
new_frame = next((k for k in reversed(kx) if k < context.scene.frame_current), None)
|
|
|
|
else:
|
|
|
|
new_frame = next((k for k in kx if k > context.scene.frame_current), None)
|
|
|
|
|
|
|
|
if not new_frame:
|
|
|
|
self.report({'WARNING'}, 'No next frame to jump on')
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
2022-04-21 14:25:43 +02:00
|
|
|
context.scene.frame_current = int(new_frame)
|
2021-04-08 19:25:05 +02:00
|
|
|
return {"FINISHED"}
|
|
|
|
|
2022-04-20 12:02:19 +02:00
|
|
|
class AW_OT_world_space_paste_next_frame(bpy.types.Operator):
|
2022-04-27 12:01:39 +02:00
|
|
|
bl_idname = "pose.world_space_paste_next_frame"
|
2022-04-11 11:41:56 +02:00
|
|
|
bl_label = "World Paste Jump Frame"
|
2022-05-05 15:39:23 +02:00
|
|
|
bl_description = "Paste world space transforms and keyframe available chanels\
|
|
|
|
\nThen jump to prev/next frame\
|
|
|
|
\nKey Loc rot scale only"
|
2022-04-11 11:41:56 +02:00
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
2022-04-27 12:01:39 +02:00
|
|
|
if not hasattr(bpy.context.view_layer, 'world_space_store'):
|
|
|
|
cls.poll_message_set("Nothing To Paste")
|
|
|
|
return False
|
2022-04-11 11:41:56 +02:00
|
|
|
return context.object and context.mode == 'POSE' and context.active_pose_bone
|
|
|
|
|
|
|
|
prev: bpy.props.BoolProperty()
|
|
|
|
|
|
|
|
def execute(self, context):
|
2022-05-06 16:30:20 +02:00
|
|
|
ct = set_matrix_and_key(context.view_layer.world_space_store, context=context)
|
|
|
|
if not ct:
|
|
|
|
self.report({'ERROR'}, 'No pose pasted')
|
|
|
|
return {"CANCELLED"}
|
|
|
|
if ct != len(context.selected_pose_bones):
|
|
|
|
self.report({'WARNING'}, f'{ct}/{len(context.selected_pose_bones)} bone position pasted')
|
|
|
|
else:
|
|
|
|
self.report({'INFO'}, f'{ct} bone position pasted')
|
2022-05-05 15:39:23 +02:00
|
|
|
|
2022-04-11 11:41:56 +02:00
|
|
|
# context.object.keyframe_insert(data_path, index=-1, frame=bpy.context.scene.frame_current, group="", options={'INSERTKEY_AVAILABLE'})
|
2022-05-05 15:39:23 +02:00
|
|
|
|
|
|
|
## bpy.ops.anim.keyframe_insert(type='DEFAULT')
|
|
|
|
|
|
|
|
## possible type :
|
|
|
|
# 'Location', 'Rotation', 'Scaling', 'BUILTIN_KSI_LocRot', 'LocRotScale', 'LocRotScaleCProp',
|
|
|
|
# 'BUILTIN_KSI_LocScale', 'BUILTIN_KSI_RotScale',
|
|
|
|
# 'BUILTIN_KSI_DeltaLocation', 'BUILTIN_KSI_DeltaRotation', 'BUILTIN_KSI_DeltaScale',
|
|
|
|
# 'BUILTIN_KSI_VisualLoc', 'BUILTIN_KSI_VisualRot', 'BUILTIN_KSI_VisualScaling',
|
|
|
|
# 'BUILTIN_KSI_VisualLocRot', 'BUILTIN_KSI_VisualLocRotScale', 'BUILTIN_KSI_VisualLocScale', 'BUILTIN_KSI_VisualRotScale'
|
|
|
|
|
|
|
|
## insert keyframe at value
|
|
|
|
## Insert Keyframes for specified Keying Set, with menu of available Keying Sets if undefined
|
|
|
|
# bpy.ops.anim.keyframe_insert_menu(type='Available', always_prompt=False)
|
|
|
|
|
|
|
|
## Insert keyframes on the current frame for all properties in the specified Keying Set
|
2022-04-11 11:41:56 +02:00
|
|
|
|
|
|
|
# jump to next frame
|
|
|
|
offset = -1 if self.prev else 1
|
|
|
|
new_frame = context.scene.frame_current + offset
|
|
|
|
|
|
|
|
context.scene.frame_current = new_frame
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
2021-04-08 19:25:05 +02:00
|
|
|
|
|
|
|
classes=(
|
2022-04-20 12:02:19 +02:00
|
|
|
AW_OT_world_space_copy,
|
|
|
|
AW_OT_world_space_paste,
|
|
|
|
AW_OT_world_space_paste_next,
|
|
|
|
AW_OT_world_space_paste_next_frame,
|
2021-04-08 19:25:05 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
def register():
|
|
|
|
for cls in classes:
|
|
|
|
bpy.utils.register_class(cls)
|
|
|
|
|
|
|
|
def unregister():
|
|
|
|
for cls in reversed(classes):
|
|
|
|
bpy.utils.unregister_class(cls)
|