multiple bones world space copy paste

1.6.0

- added: World space copy/paste from `Pin tool` panel can now copy/paste multiple selected posebones positions
    - paste according to names
    - aware of names and armatures if multiple armatures
    - should take parenting into account
master
Pullusb 2022-05-06 16:30:20 +02:00
parent 0d465c84d4
commit b13c5204b8
3 changed files with 93 additions and 24 deletions

View File

@ -1,5 +1,11 @@
# Changelog
1.6.0
- added: World space copy/paste from `Pin tool` panel can now copy/paste multiple selected posebones positions
- paste according to names
- aware of names and armatures if multiple armatures
- should take parenting into account
1.5.2

View File

@ -1,3 +1,4 @@
from numpy import dstack
import bpy
from . import fn
@ -13,10 +14,71 @@ class AW_OT_world_space_copy(bpy.types.Operator):
return context.object and context.mode == 'POSE' and context.active_pose_bone
def execute(self, context):
bpy.types.ViewLayer.world_space_store = context.object.matrix_world @ context.active_pose_bone.matrix
### 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
return {"FINISHED"}
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
class AW_OT_world_space_paste(bpy.types.Operator):
bl_idname = "pose.world_space_paste"
bl_label = "World Paste"
@ -32,23 +94,26 @@ class AW_OT_world_space_paste(bpy.types.Operator):
def execute(self, context):
context.active_pose_bone.matrix = context.object.matrix_world.inverted() @ bpy.context.view_layer.world_space_store
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')
## only if autokey is On
# if context.scene.tool_settings.use_keyframe_insert_auto:
# bpy.ops.anim.keyframe_insert_menu(type='LocRotScale') # Available
## always paste location rotation scale
bpy.ops.anim.keyframe_insert(type='LocRotScale')
return {"FINISHED"}
class AW_OT_world_space_paste_next(bpy.types.Operator):
bl_idname = "pose.world_space_paste_next"
bl_label = "World Paste Jump"
bl_description = "Paste world space transforms and keyframe available chanels\
\nThen jump to prev/next key\
\nThen jump to prev/next key (on active pose bone)\
\nKey Loc rot scale only"
bl_options = {"REGISTER", "UNDO"}
@ -62,12 +127,11 @@ class AW_OT_world_space_paste_next(bpy.types.Operator):
prev: bpy.props.BoolProperty()
def execute(self, context):
# apply matrix
context.active_pose_bone.matrix = context.object.matrix_world.inverted() @ bpy.context.view_layer.world_space_store
# insert keyframe at value
bpy.ops.anim.keyframe_insert(type='LocRotScale') # delete args to use default
ct = set_matrix_and_key(context.view_layer.world_space_store, context=context)
if not ct:
self.report({'ERROR'}, 'No pose pasted')
return {"CANCELLED"}
# jump to next key
act = fn.get_obj_action(context.object)
if not act:
@ -115,8 +179,14 @@ class AW_OT_world_space_paste_next_frame(bpy.types.Operator):
prev: bpy.props.BoolProperty()
def execute(self, context):
# apply matrix
context.active_pose_bone.matrix = context.object.matrix_world.inverted() @ bpy.context.view_layer.world_space_store
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')
# context.object.keyframe_insert(data_path, index=-1, frame=bpy.context.scene.frame_current, group="", options={'INSERTKEY_AVAILABLE'})
@ -129,20 +199,13 @@ class AW_OT_world_space_paste_next_frame(bpy.types.Operator):
# '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
bpy.ops.anim.keyframe_insert(type='LocRotScale')
# jump to next frame
act = fn.get_obj_action(context.object)
if not act:
self.report({'ERROR'}, 'No action on armature')
return {'CANCELLED'}
offset = -1 if self.prev else 1
new_frame = context.scene.frame_current + offset

View File

@ -4,7 +4,7 @@ bl_info = {
"name": "Auto Walk",
"description": "Develop a walk/run cycles along a curve and pin feets",
"author": "Samuel Bernou",
"version": (1, 5, 2),
"version": (1, 6, 0),
"blender": (3, 0, 0),
"location": "View3D",
"warning": "",