big fixes
parent
477646129b
commit
7b95b9e8a5
|
@ -1,12 +1,14 @@
|
||||||
import bpy
|
import bpy, re
|
||||||
from . import fn
|
from . import fn
|
||||||
|
from time import time
|
||||||
## step 3
|
## step 3
|
||||||
# - Bake cycle modifier keys -chained with- step the animation path
|
# - Bake cycle modifier keys -chained with- step the animation path
|
||||||
# - Pin the feet (separated ops)
|
# - Pin the feet (separated ops)
|
||||||
|
|
||||||
def bake_cycle(on_selection=True):
|
def bake_cycle(on_selection=True):
|
||||||
print(fn.helper())
|
print(fn.helper())
|
||||||
|
debug = fn.get_addon_prefs().debug
|
||||||
|
print('debug: ', debug)
|
||||||
obj = bpy.context.object
|
obj = bpy.context.object
|
||||||
if obj.type != 'ARMATURE':
|
if obj.type != 'ARMATURE':
|
||||||
print('ERROR', 'active is not an armature type')
|
print('ERROR', 'active is not an armature type')
|
||||||
|
@ -15,17 +17,23 @@ def bake_cycle(on_selection=True):
|
||||||
act = fn.set_expanded_action(obj)
|
act = fn.set_expanded_action(obj)
|
||||||
if not act:
|
if not act:
|
||||||
return
|
return
|
||||||
print('action', act.name)
|
|
||||||
|
if debug: print('action', act.name)
|
||||||
|
|
||||||
act = obj.animation_data.action
|
# obj.animation_data.action = act
|
||||||
|
|
||||||
|
ct_fcu = len(act.fcurves)
|
||||||
ct = 0
|
ct = 0
|
||||||
|
ct_no_cycle = 0
|
||||||
|
|
||||||
for fcu in act.fcurves:
|
for fcu in act.fcurves:
|
||||||
|
|
||||||
## if a curve is not cycled don't touch
|
## if a curve is not cycled don't touch
|
||||||
if not [m for m in fcu.modifiers if m.type == 'CYCLES']:
|
if not [m for m in fcu.modifiers if m.type == 'CYCLES']:
|
||||||
|
ct_no_cycle += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if debug: print(fcu.data_path, 'has cycle')
|
||||||
#-# only on location :
|
#-# only on location :
|
||||||
# if not fcu.data_path.endswith('.location'):
|
# if not fcu.data_path.endswith('.location'):
|
||||||
# continue
|
# continue
|
||||||
|
@ -33,6 +41,7 @@ def bake_cycle(on_selection=True):
|
||||||
# prop = fcu.data_path.split('.')[-1]
|
# prop = fcu.data_path.split('.')[-1]
|
||||||
|
|
||||||
b_name = fcu.data_path.split('"')[1]
|
b_name = fcu.data_path.split('"')[1]
|
||||||
|
if debug: print(b_name, 'has cycle')
|
||||||
pb = obj.pose.bones.get(b_name)
|
pb = obj.pose.bones.get(b_name)
|
||||||
if not pb:
|
if not pb:
|
||||||
print(f'{b_name} is invalid')
|
print(f'{b_name} is invalid')
|
||||||
|
@ -41,11 +50,14 @@ def bake_cycle(on_selection=True):
|
||||||
#-# limit on selection if passed
|
#-# limit on selection if passed
|
||||||
if on_selection and not pb.bone.select:
|
if on_selection and not pb.bone.select:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if debug: print(b_name, 'seems ok')
|
||||||
|
|
||||||
#-# only on selected and visible curve
|
#-# only on selected and visible curve
|
||||||
# if not fcu.select or fcu.hide:
|
# if not fcu.select or fcu.hide:
|
||||||
# continue
|
# continue
|
||||||
|
|
||||||
|
|
||||||
fcu_kfs = []
|
fcu_kfs = []
|
||||||
for k in fcu.keyframe_points:
|
for k in fcu.keyframe_points:
|
||||||
k_dic = {}
|
k_dic = {}
|
||||||
|
@ -62,13 +74,24 @@ def bake_cycle(on_selection=True):
|
||||||
# first_offset = second - first
|
# first_offset = second - first
|
||||||
|
|
||||||
current_offset = offset = last - first
|
current_offset = offset = last - first
|
||||||
print('offset: ', offset)
|
|
||||||
|
|
||||||
|
keys_num = len(fcu_kfs)
|
||||||
|
if debug: print(keys_num)
|
||||||
|
|
||||||
|
if keys_num <= 1:
|
||||||
|
if debug: print(b_name, f'{keys_num} key')
|
||||||
|
continue
|
||||||
|
|
||||||
|
## ! important: delete last after computing offset IF cycle have first frame repeatead as last !
|
||||||
|
fcu_kfs.pop()
|
||||||
|
# print('offset: ', offset)
|
||||||
|
|
||||||
|
if debug: print('keys', len(fcu_kfs))
|
||||||
## expand to end frame
|
## expand to end frame
|
||||||
|
|
||||||
end = bpy.context.scene.frame_end # maybe add possibility define target manually
|
end = bpy.context.scene.frame_end # maybe add possibility define target manually
|
||||||
iterations = ((end - last) // offset) + 1
|
iterations = ((end - last) // offset) + 1
|
||||||
print('iterations: ', iterations)
|
if debug: print('iterations: ', iterations)
|
||||||
for _i in range(int(iterations)):
|
for _i in range(int(iterations)):
|
||||||
for kf in fcu_kfs:
|
for kf in fcu_kfs:
|
||||||
# create a new key, adding offset to keys
|
# create a new key, adding offset to keys
|
||||||
|
@ -80,9 +103,20 @@ def bake_cycle(on_selection=True):
|
||||||
else:
|
else:
|
||||||
setattr(new, att, val)
|
setattr(new, att, val)
|
||||||
current_offset += offset
|
current_offset += offset
|
||||||
|
|
||||||
ct += 1
|
ct += 1
|
||||||
|
|
||||||
|
if ct_fcu == ct_no_cycle: # skipped because no cycle exists
|
||||||
|
rexpand = re.compile(r'_expanded\.?\d{0,3}$')
|
||||||
|
if rexpand.search(act.name):
|
||||||
|
# is an autogenerated one
|
||||||
|
org_action_name = rexpand.sub('', act.name)
|
||||||
|
org_action = bpy.data.actions.get(org_action_name)
|
||||||
|
if not org_action:
|
||||||
|
return ('ERROR', 'No fcurve with anim cycle found (on expanded action)')
|
||||||
|
obj.animation_data.action = org_action
|
||||||
|
return ('ERROR', 'No fcurve with anim cycle found (back to unexpanded)')
|
||||||
|
|
||||||
if not ct:
|
if not ct:
|
||||||
return ('ERROR', 'No fcurve treated (! action duplicated to _expand !)')
|
return ('ERROR', 'No fcurve treated (! action duplicated to _expand !)')
|
||||||
|
|
||||||
|
@ -162,12 +196,31 @@ class UAC_OT_bake_cycle_and_step(bpy.types.Operator):
|
||||||
if err[0] == 'ERROR':
|
if err[0] == 'ERROR':
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
# CHAINED ACTION : step the path of the curve path
|
if not context.scene.anim_cycle_settings.linear:
|
||||||
err = step_path()
|
# CHAINED ACTION : step the path of the curve path
|
||||||
if err:
|
err = step_path()
|
||||||
self.report({err[0]}, err[1])
|
if err:
|
||||||
if err[0] == 'ERROR':
|
self.report({err[0]}, err[1])
|
||||||
return {"CANCELLED"}
|
if err[0] == 'ERROR':
|
||||||
|
return {"CANCELLED"}
|
||||||
|
else:
|
||||||
|
# Delete points in curve action between first and last and go LINEAR
|
||||||
|
curve = context.scene.anim_cycle_settings.path_to_follow
|
||||||
|
if curve:
|
||||||
|
act = fn.get_obj_action(curve.data)
|
||||||
|
if act:
|
||||||
|
timef = next((fcu for fcu in act.fcurves if fcu.data_path == 'eval_time'), None)
|
||||||
|
if timef:
|
||||||
|
keys_ct = len(timef.keyframe_points)
|
||||||
|
if keys_ct > 2:
|
||||||
|
for k in reversed(timef.keyframe_points[1:-2]):
|
||||||
|
timef.keyframe_points.remove(k)
|
||||||
|
for k in timef.keyframe_points:
|
||||||
|
k.interpolation = 'LINEAR'
|
||||||
|
print(f'Anim path to linear : Deleted all keys ({keys_ct - 2}) on anim path except first and last')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# CHAINED ACTION pin feet ?? : Step the path of the curve path
|
# CHAINED ACTION pin feet ?? : Step the path of the curve path
|
||||||
|
|
||||||
|
@ -198,6 +251,28 @@ def pin_down_feets():
|
||||||
|
|
||||||
# TODO autodetect contact frame ?
|
# TODO autodetect contact frame ?
|
||||||
|
|
||||||
|
## Link armature in a new collection and exclude all the others
|
||||||
|
|
||||||
|
## STORE AND MODIFY /-
|
||||||
|
tmp_col = bpy.data.collections.new('TMP_COLLECTION_PINNING')
|
||||||
|
bpy.context.scene.collection.children.link(tmp_col)
|
||||||
|
tmp_col.objects.link(obj)
|
||||||
|
|
||||||
|
simplify = bpy.context.scene.render.use_simplify
|
||||||
|
simplify_subdiv = bpy.context.scene.render.simplify_subdivision
|
||||||
|
bpy.context.scene.render.use_simplify = True
|
||||||
|
bpy.context.scene.render.simplify_subdivision = 0
|
||||||
|
|
||||||
|
showed_col = []
|
||||||
|
for vlc in bpy.context.view_layer.layer_collection.children:
|
||||||
|
if vlc.collection == tmp_col:
|
||||||
|
continue
|
||||||
|
showed_col.append([vlc, vlc.exclude])
|
||||||
|
vlc.exclude = True
|
||||||
|
#-/
|
||||||
|
|
||||||
|
t0 = time()
|
||||||
|
ct = 0
|
||||||
done = {}
|
done = {}
|
||||||
for fcu in act.fcurves:
|
for fcu in act.fcurves:
|
||||||
|
|
||||||
|
@ -217,7 +292,7 @@ def pin_down_feets():
|
||||||
|
|
||||||
start_contact = None
|
start_contact = None
|
||||||
for k in fcu.keyframe_points:
|
for k in fcu.keyframe_points:
|
||||||
|
|
||||||
if k.type == 'EXTREME':
|
if k.type == 'EXTREME':
|
||||||
bpy.context.scene.frame_set(k.co[0])
|
bpy.context.scene.frame_set(k.co[0])
|
||||||
if start_contact is None:
|
if start_contact is None:
|
||||||
|
@ -257,6 +332,7 @@ def pin_down_feets():
|
||||||
# pb.keyframe_insert('rotation_euler')
|
# pb.keyframe_insert('rotation_euler')
|
||||||
|
|
||||||
k.type = 'JITTER' # 'BREAKDOWN' 'MOVING_HOLD' 'JITTER'
|
k.type = 'JITTER' # 'BREAKDOWN' 'MOVING_HOLD' 'JITTER'
|
||||||
|
ct += 1
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if start_contact is not None:
|
if start_contact is not None:
|
||||||
|
@ -273,11 +349,25 @@ def pin_down_feets():
|
||||||
#k.handle_left_type = 'AUTO_CLAMPED'
|
#k.handle_left_type = 'AUTO_CLAMPED'
|
||||||
#k.handle_right_type = 'AUTO_CLAMPED'
|
#k.handle_right_type = 'AUTO_CLAMPED'
|
||||||
|
|
||||||
|
print(f'--\n{ct} keys changed in {time()-t0:.2f}s\n--') # fcurves treated in
|
||||||
|
|
||||||
|
## RESTORE
|
||||||
|
# without >> 433 keys changed in 29.15s
|
||||||
|
# with all collection excluded >> 433 keys changed in 25.00s
|
||||||
|
# with simplify >> 9.57s
|
||||||
|
|
||||||
|
bpy.context.scene.render.use_simplify = simplify
|
||||||
|
bpy.context.scene.render.simplify_subdivision = simplify_subdiv
|
||||||
|
|
||||||
|
for exclude_pairs in showed_col:
|
||||||
|
exclude_pairs[0].exclude = exclude_pairs[1]
|
||||||
|
tmp_col.objects.unlink(obj)
|
||||||
|
bpy.data.collections.remove(tmp_col)
|
||||||
|
|
||||||
bpy.context.scene.frame_current = org_frame
|
bpy.context.scene.frame_current = org_frame
|
||||||
# detect last key in contact
|
# detect last key in contact
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UAC_OT_pin_feets(bpy.types.Operator):
|
class UAC_OT_pin_feets(bpy.types.Operator):
|
||||||
bl_idname = "anim.pin_feets"
|
bl_idname = "anim.pin_feets"
|
||||||
bl_label = "Pin Feets"
|
bl_label = "Pin Feets"
|
||||||
|
|
|
@ -55,6 +55,8 @@ def snap_curve():
|
||||||
if not gnd:
|
if not gnd:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
curve_act = None
|
||||||
|
anim_frame = None
|
||||||
# if it's on a snap curve, fetch original
|
# if it's on a snap curve, fetch original
|
||||||
if '_snap' in curve.name:
|
if '_snap' in curve.name:
|
||||||
org_name = re.sub(r'_snap\.?\d{0,3}$', '', curve.name)
|
org_name = re.sub(r'_snap\.?\d{0,3}$', '', curve.name)
|
||||||
|
@ -62,6 +64,11 @@ def snap_curve():
|
||||||
if org_curve:
|
if org_curve:
|
||||||
const.target = org_curve
|
const.target = org_curve
|
||||||
|
|
||||||
|
# keep action
|
||||||
|
if curve.data.animation_data and curve.data.animation_data.action:
|
||||||
|
curve_act = curve.data.animation_data.action
|
||||||
|
|
||||||
|
anim_frame = curve.data.path_duration
|
||||||
# delete old snap
|
# delete old snap
|
||||||
bpy.data.objects.remove(curve)
|
bpy.data.objects.remove(curve)
|
||||||
# assign old curve as main one
|
# assign old curve as main one
|
||||||
|
@ -73,6 +80,11 @@ def snap_curve():
|
||||||
nc.name = name
|
nc.name = name
|
||||||
nc.data = curve.data.copy()
|
nc.data = curve.data.copy()
|
||||||
nc.data.name = name + '_data'
|
nc.data.name = name + '_data'
|
||||||
|
if curve_act:
|
||||||
|
nc.data.animation_data_create()
|
||||||
|
nc.data.animation_data.action = curve_act
|
||||||
|
if anim_frame:
|
||||||
|
nc.data.path_duration = anim_frame
|
||||||
|
|
||||||
curve.users_collection[0].objects.link(nc)
|
curve.users_collection[0].objects.link(nc)
|
||||||
|
|
||||||
|
@ -86,7 +98,12 @@ def snap_curve():
|
||||||
|
|
||||||
# shrinkwrap or cast on ground
|
# shrinkwrap or cast on ground
|
||||||
mod = nc.modifiers.new('Shrinkwrap', 'SHRINKWRAP')
|
mod = nc.modifiers.new('Shrinkwrap', 'SHRINKWRAP')
|
||||||
mod.wrap_method = 'TARGET_PROJECT'
|
# mod.wrap_method = 'TARGET_PROJECT'
|
||||||
|
mod.wrap_method = 'PROJECT'
|
||||||
|
mod.wrap_mode = 'ON_SURFACE'
|
||||||
|
mod.use_project_z = True
|
||||||
|
mod.use_negative_direction = True
|
||||||
|
mod.use_positive_direction = True
|
||||||
mod.target = gnd
|
mod.target = gnd
|
||||||
|
|
||||||
# Apply and decimate
|
# Apply and decimate
|
||||||
|
|
|
@ -125,16 +125,16 @@ def snap_foot(pb, gnd):
|
||||||
m.show_viewport = False
|
m.show_viewport = False
|
||||||
|
|
||||||
dg = bpy.context.evaluated_depsgraph_get()
|
dg = bpy.context.evaluated_depsgraph_get()
|
||||||
obeval = ob.evaluated_get(dg).copy()
|
obeval = ob.evaluated_get(dg) #.copy()
|
||||||
print('object: ', ob.name)
|
print('object: ', ob.name)
|
||||||
|
|
||||||
## bpy.context.object.proxy_collection.instance_collection.all_objects['body_deform']
|
## bpy.context.object.proxy_collection.instance_collection.all_objects['body_deform']
|
||||||
## bpy.context.object.proxy_collection.instance_collection.all_objects['body']
|
## bpy.context.object.proxy_collection.instance_collection.all_objects['body']
|
||||||
|
|
||||||
## Hide modifier
|
## Hide modifier
|
||||||
for m in obeval.modifiers:
|
# for m in obeval.modifiers:
|
||||||
if m.type == 'SUBSURF':
|
# if m.type == 'SUBSURF':
|
||||||
m.show_viewport = False # m.levels = 0
|
# m.show_viewport = False # m.levels = 0
|
||||||
|
|
||||||
bake_mesh = obeval.to_mesh(preserve_all_data_layers=True, depsgraph=dg)
|
bake_mesh = obeval.to_mesh(preserve_all_data_layers=True, depsgraph=dg)
|
||||||
|
|
||||||
|
@ -207,7 +207,9 @@ def snap_foot(pb, gnd):
|
||||||
if m.type == 'SUBSURF':
|
if m.type == 'SUBSURF':
|
||||||
# if m.type in ('SUBSURF', 'TRIANGULATE'):
|
# if m.type in ('SUBSURF', 'TRIANGULATE'):
|
||||||
m.show_viewport = True
|
m.show_viewport = True
|
||||||
# obeval.to_mesh_clear()
|
|
||||||
|
obeval.to_mesh_clear()
|
||||||
|
|
||||||
|
|
||||||
def snap_feet():
|
def snap_feet():
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
import bpy
|
||||||
|
from . import fn
|
||||||
|
|
||||||
|
|
||||||
|
class UAC_OT_world_space_copy(bpy.types.Operator):
|
||||||
|
bl_idname = "anim.world_space_copy"
|
||||||
|
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):
|
||||||
|
bpy.types.ViewLayer.world_space_store = context.object.matrix_world @ context.active_pose_bone.matrix
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class UAC_OT_world_space_paste(bpy.types.Operator):
|
||||||
|
bl_idname = "anim.world_space_paste"
|
||||||
|
bl_label = "World Paste"
|
||||||
|
bl_description = "Paste world space transforms. Apply stored matrix to active bone"
|
||||||
|
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):
|
||||||
|
context.active_pose_bone.matrix = context.object.matrix_world.inverted() @ bpy.context.view_layer.world_space_store
|
||||||
|
|
||||||
|
if context.scene.tool_settings.use_keyframe_insert_auto:
|
||||||
|
bpy.ops.anim.keyframe_insert_menu(type='Available')
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class UAC_OT_world_space_paste_next(bpy.types.Operator):
|
||||||
|
bl_idname = "anim.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"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object and context.mode == 'POSE' and context.active_pose_bone
|
||||||
|
|
||||||
|
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
|
||||||
|
# context.object.keyframe_insert(data_path, index=-1, frame=bpy.context.scene.frame_current, group="", options={'INSERTKEY_AVAILABLE'})
|
||||||
|
bpy.ops.anim.keyframe_insert_menu(type='Available')
|
||||||
|
|
||||||
|
# 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'}
|
||||||
|
|
||||||
|
context.scene.frame_current = new_frame
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
classes=(
|
||||||
|
UAC_OT_world_space_copy,
|
||||||
|
UAC_OT_world_space_paste,
|
||||||
|
UAC_OT_world_space_paste_next,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for cls in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cls)
|
23
README.md
23
README.md
|
@ -23,6 +23,14 @@ sequencial set of tools:
|
||||||
- Pin feet on ground (use contact keys marked as 'EXTREME' for each feets)
|
- Pin feet on ground (use contact keys marked as 'EXTREME' for each feets)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO :
|
||||||
|
|
||||||
|
- create nurb path instead of curve
|
||||||
|
- align curve to root ?
|
||||||
|
- Smoothing keys after last freezed to avoid too much gap "pose click".
|
||||||
|
|
||||||
|
|
||||||
### Where ?
|
### Where ?
|
||||||
|
|
||||||
Sidebar > Anim > unfold anim cycle
|
Sidebar > Anim > unfold anim cycle
|
||||||
|
@ -31,6 +39,21 @@ Sidebar > Anim > unfold anim cycle
|
||||||
|
|
||||||
## Changelog:
|
## Changelog:
|
||||||
|
|
||||||
|
0.3.0
|
||||||
|
|
||||||
|
- Rework interface
|
||||||
|
- Add manual bone pinning: simple world space copy/paste + pose and jump prev/next
|
||||||
|
- Faster pin feets
|
||||||
|
- bool prop to disable end/curve stepping (interpolation as linear if needed)
|
||||||
|
- Switch action back to new curve when re-snapping to ground
|
||||||
|
- fix shrinkwrap
|
||||||
|
- fix rebake with linear
|
||||||
|
- error message when no fcurve has anim cycle
|
||||||
|
|
||||||
|
0.2.0:
|
||||||
|
|
||||||
|
- first working version
|
||||||
|
|
||||||
0.1.0:
|
0.1.0:
|
||||||
|
|
||||||
- initial commit, halfway there
|
- initial commit, halfway there
|
25
__init__.py
25
__init__.py
|
@ -2,7 +2,7 @@ bl_info = {
|
||||||
"name": "Unfold Anim Cycle",
|
"name": "Unfold Anim Cycle",
|
||||||
"description": "Anim utility to develop walk/run cycles along a curve",
|
"description": "Anim utility to develop walk/run cycles along a curve",
|
||||||
"author": "Samuel Bernou",
|
"author": "Samuel Bernou",
|
||||||
"version": (0, 2, 0),
|
"version": (0, 3, 0),
|
||||||
"blender": (2, 92, 0),
|
"blender": (2, 92, 0),
|
||||||
"location": "View3D",
|
"location": "View3D",
|
||||||
"warning": "WIP",
|
"warning": "WIP",
|
||||||
|
@ -13,12 +13,22 @@ bl_info = {
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
|
import importlib as imp
|
||||||
from . import OP_setup_curve_path
|
from . import OP_setup_curve_path
|
||||||
from . import OP_animate_path
|
from . import OP_animate_path
|
||||||
from . import OP_expand_cycle_step
|
from . import OP_expand_cycle_step
|
||||||
from . import OP_snap_contact
|
from . import OP_snap_contact
|
||||||
|
from . import OP_world_copy_paste
|
||||||
from . import panels
|
from . import panels
|
||||||
|
|
||||||
|
if 'bpy' in locals():
|
||||||
|
imp.reload(OP_setup_curve_path)
|
||||||
|
imp.reload(OP_animate_path)
|
||||||
|
imp.reload(OP_expand_cycle_step)
|
||||||
|
imp.reload(OP_snap_contact)
|
||||||
|
imp.reload(OP_world_copy_paste)
|
||||||
|
imp.reload(panels)
|
||||||
|
|
||||||
class UAC_PGT_settings(bpy.types.PropertyGroup) :
|
class UAC_PGT_settings(bpy.types.PropertyGroup) :
|
||||||
## HIDDEN to hide the animatable dot thing
|
## HIDDEN to hide the animatable dot thing
|
||||||
path_to_follow : bpy.props.PointerProperty(type=bpy.types.Object,
|
path_to_follow : bpy.props.PointerProperty(type=bpy.types.Object,
|
||||||
|
@ -27,16 +37,13 @@ class UAC_PGT_settings(bpy.types.PropertyGroup) :
|
||||||
gnd : bpy.props.PointerProperty(type=bpy.types.Object,
|
gnd : bpy.props.PointerProperty(type=bpy.types.Object,
|
||||||
name="Ground", description="Choose the ground object to use")
|
name="Ground", description="Choose the ground object to use")
|
||||||
|
|
||||||
## As strings
|
|
||||||
# path_to_follow : bpy.props.StringProperty(
|
|
||||||
# name="Path to follow", description="Curve object used")
|
|
||||||
|
|
||||||
# gnd : bpy.props.StringProperty(
|
|
||||||
# name="Ground object", description="Choose the ground object to use")
|
|
||||||
|
|
||||||
expand_on_selected_bones : bpy.props.BoolProperty(
|
expand_on_selected_bones : bpy.props.BoolProperty(
|
||||||
name="On selected", description="Expand on selected bones",
|
name="On selected", description="Expand on selected bones",
|
||||||
default=True, options={'HIDDEN'})
|
default=True, options={'HIDDEN'})
|
||||||
|
|
||||||
|
linear : bpy.props.BoolProperty(
|
||||||
|
name="Linear", description="keep the animation path linear (Else step the path usings cycle keys)",
|
||||||
|
default=False, options={'HIDDEN'})
|
||||||
|
|
||||||
start_frame : bpy.props.IntProperty(
|
start_frame : bpy.props.IntProperty(
|
||||||
name="Start Frame", description="Starting frame for animation path",
|
name="Start Frame", description="Starting frame for animation path",
|
||||||
|
@ -85,6 +92,7 @@ def register():
|
||||||
OP_animate_path.register()
|
OP_animate_path.register()
|
||||||
OP_expand_cycle_step.register()
|
OP_expand_cycle_step.register()
|
||||||
OP_snap_contact.register()
|
OP_snap_contact.register()
|
||||||
|
OP_world_copy_paste.register()
|
||||||
panels.register()
|
panels.register()
|
||||||
|
|
||||||
# if not bpy.app.background:
|
# if not bpy.app.background:
|
||||||
|
@ -95,6 +103,7 @@ def unregister():
|
||||||
# if not bpy.app.background:
|
# if not bpy.app.background:
|
||||||
#unregister_keymaps()
|
#unregister_keymaps()
|
||||||
panels.unregister()
|
panels.unregister()
|
||||||
|
OP_world_copy_paste.unregister()
|
||||||
OP_snap_contact.unregister()
|
OP_snap_contact.unregister()
|
||||||
OP_expand_cycle_step.unregister()
|
OP_expand_cycle_step.unregister()
|
||||||
OP_animate_path.unregister()
|
OP_animate_path.unregister()
|
||||||
|
|
17
fn.py
17
fn.py
|
@ -1,5 +1,6 @@
|
||||||
import bpy
|
import bpy
|
||||||
import re
|
import re
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
def get_addon_prefs():
|
def get_addon_prefs():
|
||||||
'''
|
'''
|
||||||
|
@ -25,6 +26,22 @@ def helper(name: str = '') -> str:
|
||||||
arguments = ', '.join([f'{k}={v}' for k, v in args.items()])
|
arguments = ', '.join([f'{k}={v}' for k, v in args.items()])
|
||||||
return(f'{name}({arguments})')
|
return(f'{name}({arguments})')
|
||||||
|
|
||||||
|
def convertAttr(Attr):
|
||||||
|
'''Convert given value to a Json serializable format'''
|
||||||
|
if type(Attr) in [type(mathutils.Vector()),type(mathutils.Color())]:
|
||||||
|
if len(Attr) == 3:
|
||||||
|
return([Attr[0],Attr[1],Attr[2]])
|
||||||
|
elif len(Attr) == 2:
|
||||||
|
return([Attr[0],Attr[1]])
|
||||||
|
elif len(Attr) == 1:
|
||||||
|
return([Attr[0]])
|
||||||
|
elif len(Attr) == 4:
|
||||||
|
return([Attr[0],Attr[1],Attr[2],Attr[3]])
|
||||||
|
elif type(Attr) == type(mathutils.Matrix()):
|
||||||
|
return (np.matrix(Attr).tolist())
|
||||||
|
else:
|
||||||
|
return(Attr)
|
||||||
|
|
||||||
|
|
||||||
def get_gnd():
|
def get_gnd():
|
||||||
for o in bpy.context.scene.objects:
|
for o in bpy.context.scene.objects:
|
||||||
|
|
18
panels.py
18
panels.py
|
@ -37,17 +37,18 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
|
||||||
layout.operator('anim.snap_curve_to_ground', text='Snap curve to ground', icon='SNAP_ON')
|
layout.operator('anim.snap_curve_to_ground', text='Snap curve to ground', icon='SNAP_ON')
|
||||||
|
|
||||||
|
|
||||||
row=layout.row()
|
# row=layout.row()
|
||||||
row.operator('anim.animate_path', text='Animate Path (select foot)', icon='ANIM')
|
layout.prop(context.scene.anim_cycle_settings, "start_frame", text='Start')
|
||||||
row.prop(context.scene.anim_cycle_settings, "start_frame", text='start')
|
layout.operator('anim.animate_path', text='Animate Path (select foot)', icon='ANIM')
|
||||||
|
|
||||||
row=layout.row()
|
row=layout.row()
|
||||||
row.operator('anim.adjust_animation_length', icon='MOD_TIME')
|
row.operator('anim.adjust_animation_length', icon='MOD_TIME')
|
||||||
|
|
||||||
## Bake cycle (on selected)
|
## Bake cycle (on selected)
|
||||||
row=layout.row()
|
row=layout.row()
|
||||||
row.operator('anim.bake_cycle_and_step', text='Bake key and step path', icon='SHAPEKEY_DATA')
|
row.prop(context.scene.anim_cycle_settings, "linear", text='Linear')
|
||||||
row.prop(context.scene.anim_cycle_settings, "expand_on_selected_bones")
|
row.prop(context.scene.anim_cycle_settings, "expand_on_selected_bones")
|
||||||
|
layout.operator('anim.bake_cycle_and_step', text='Bake key and step path', icon='SHAPEKEY_DATA')
|
||||||
|
|
||||||
# Pin feet
|
# Pin feet
|
||||||
layout.operator('anim.pin_feets', text='Pin feets', icon='PINNED')
|
layout.operator('anim.pin_feets', text='Pin feets', icon='PINNED')
|
||||||
|
@ -58,11 +59,18 @@ class UAC_PT_anim_tools_panel(bpy.types.Panel):
|
||||||
bl_space_type = "VIEW_3D"
|
bl_space_type = "VIEW_3D"
|
||||||
bl_region_type = "UI"
|
bl_region_type = "UI"
|
||||||
bl_category = "Anim"
|
bl_category = "Anim"
|
||||||
bl_label = "Unicorn Tools"
|
bl_label = "Tools"
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.operator('anim.contact_to_ground', text='Ground selected feet', icon='SNAP_OFF')
|
layout.operator('anim.contact_to_ground', text='Ground selected feet', icon='SNAP_OFF')
|
||||||
|
row = layout.row()
|
||||||
|
row.operator('anim.world_space_copy', text='Copy Pose', icon='COPYDOWN')
|
||||||
|
row.operator('anim.world_space_paste', text='Paste', icon='PASTEDOWN')
|
||||||
|
row = layout.row()
|
||||||
|
row.operator('anim.world_space_paste_next', text='Paste Prev', icon='PASTEDOWN').prev = True
|
||||||
|
row.operator('anim.world_space_paste_next', text='Paste Next', icon='PASTEDOWN').prev = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
classes=(
|
classes=(
|
||||||
|
|
Loading…
Reference in New Issue