store / restore values with a context manager
parent
d77ab84c06
commit
037f568a4b
|
@ -2,6 +2,7 @@ import bpy
|
|||
from . import fn
|
||||
from mathutils import Vector, Euler
|
||||
from mathutils.geometry import intersect_line_plane
|
||||
|
||||
## step 2 : Auto animate the path (with feet selection) and modal to adjust speed
|
||||
|
||||
def get_bone_transform_at_frame(b, act, frame):
|
||||
|
@ -41,6 +42,7 @@ def anim_path_from_translate():
|
|||
# return ('ERROR', 'No "foot" in active bone name\n-> Select foot that has the most reliable contact')
|
||||
settings = bpy.context.scene.anim_cycle_settings
|
||||
axis = settings.forward_axis
|
||||
|
||||
curve = None
|
||||
if settings.path_to_follow:
|
||||
curve = settings.path_to_follow
|
||||
|
@ -161,7 +163,6 @@ def anim_path_from_translate():
|
|||
if fcu.data_path == 'eval_time':
|
||||
curve.data.animation_data.action.fcurves.remove(fcu)
|
||||
break
|
||||
|
||||
## add eval time animation on curve
|
||||
anim_frame = settings.start_frame
|
||||
curve.data.path_duration = frame_duration
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import bpy, re
|
||||
from . import fn
|
||||
from time import time
|
||||
|
||||
## step 3
|
||||
# - Bake cycle modifier keys -chained with- step the animation path
|
||||
# - Pin the feet (separated ops)
|
||||
|
@ -227,6 +228,12 @@ class UAC_OT_bake_cycle_and_step(bpy.types.Operator):
|
|||
return {"FINISHED"}
|
||||
|
||||
|
||||
# detect contact key
|
||||
# [ 'array_index', 'auto_smoothing', 'bl_rna', 'color', 'color_mode', 'convert_to_keyframes', 'convert_to_samples', 'data_path',
|
||||
# 'driver', 'evaluate', 'extrapolation', 'group', 'hide', 'is_empty', 'is_valid', 'keyframe_points', 'lock', 'modifiers', 'mute',
|
||||
# 'range', 'rna_type', 'sampled_points', 'select', 'update', 'update_autoflags']
|
||||
|
||||
|
||||
def pin_down_feets():
|
||||
print(fn.helper())
|
||||
obj = bpy.context.object
|
||||
|
@ -241,131 +248,121 @@ def pin_down_feets():
|
|||
return ('ERROR', f'No action on {obj.name}')
|
||||
print('action', act.name)
|
||||
|
||||
# detect contact key
|
||||
# [ 'array_index', 'auto_smoothing', 'bl_rna', 'color', 'color_mode', 'convert_to_keyframes', 'convert_to_samples', 'data_path',
|
||||
# 'driver', 'evaluate', 'extrapolation', 'group', 'hide', 'is_empty', 'is_valid', 'keyframe_points', 'lock', 'modifiers', 'mute',
|
||||
# 'range', 'rna_type', 'sampled_points', 'select', 'update', 'update_autoflags']
|
||||
|
||||
act = obj.animation_data.action
|
||||
org_frame = bpy.context.scene.frame_current
|
||||
|
||||
# TODO autodetect contact frame ?
|
||||
|
||||
to_change_list = [
|
||||
(bpy.context.scene, 'frame_current', '_undefined'), # use '_undefined' when no value to assign for now
|
||||
(bpy.context.scene.render, 'use_simplify', True),
|
||||
(bpy.context.scene.render, 'simplify_subdivision', 0),
|
||||
]
|
||||
## 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)
|
||||
## STORE for context manager store/restore /-
|
||||
tmp_col = bpy.data.collections.get('TMP_COLLECTION_PINNING')
|
||||
if not tmp_col:
|
||||
tmp_col = bpy.data.collections.new('TMP_COLLECTION_PINNING')
|
||||
if tmp_col.name not in bpy.context.scene.collection.children:
|
||||
bpy.context.scene.collection.children.link(tmp_col)
|
||||
if obj not in tmp_col.objects[:]:
|
||||
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
|
||||
to_change_list.append((vlc, 'exclude', True))
|
||||
|
||||
#-/
|
||||
|
||||
t0 = time()
|
||||
ct = 0
|
||||
done = {}
|
||||
for fcu in act.fcurves:
|
||||
with fn.attr_set(to_change_list):
|
||||
t0 = time()
|
||||
ct = 0
|
||||
done = {}
|
||||
for fcu in act.fcurves:
|
||||
|
||||
# check only location
|
||||
if not fcu.data_path.endswith('.location'):
|
||||
continue
|
||||
# check only location
|
||||
if not fcu.data_path.endswith('.location'):
|
||||
continue
|
||||
|
||||
# prop = fcu.data_path.split('.')[-1]
|
||||
# prop = fcu.data_path.split('.')[-1]
|
||||
|
||||
b_name = fcu.data_path.split('"')[1]
|
||||
# print('b_name: ', b_name, fcu.is_valid)
|
||||
b_name = fcu.data_path.split('"')[1]
|
||||
# print('b_name: ', b_name, fcu.is_valid)
|
||||
|
||||
pb = obj.pose.bones.get(b_name)
|
||||
if not pb:
|
||||
print(f'{b_name} is invalid')
|
||||
continue
|
||||
pb = obj.pose.bones.get(b_name)
|
||||
if not pb:
|
||||
print(f'{b_name} is invalid')
|
||||
continue
|
||||
|
||||
start_contact = None
|
||||
for k in fcu.keyframe_points:
|
||||
start_contact = None
|
||||
for k in fcu.keyframe_points:
|
||||
|
||||
if k.type == 'EXTREME':
|
||||
bpy.context.scene.frame_set(k.co[0])
|
||||
if start_contact is None:
|
||||
start_contact=k.co[0]
|
||||
# record coordinate relative to referent object (or world coord)
|
||||
bone_mat = pb.matrix.copy()
|
||||
# bone_mat = obj.matrix_world @ pb.matrix.copy()
|
||||
continue
|
||||
|
||||
if b_name in done.keys():
|
||||
if k.co[0] in done[b_name]:
|
||||
if k.type == 'EXTREME':
|
||||
bpy.context.scene.frame_set(int(k.co[0]))
|
||||
if start_contact is None:
|
||||
start_contact=k.co[0]
|
||||
# record coordinate relative to referent object (or world coord)
|
||||
bone_mat = pb.matrix.copy()
|
||||
# bone_mat = obj.matrix_world @ pb.matrix.copy()
|
||||
continue
|
||||
else:
|
||||
# mark as treated (all curve of this bone at this time)
|
||||
done[b_name] = [k.co[0]]
|
||||
|
||||
#-# Insert keyframe to match Hold position
|
||||
# print(f'Apply on {b_name} at {k.co[0]}')
|
||||
|
||||
#-# assign previous matrix
|
||||
pbl = pb.location.copy()
|
||||
# l, _r, _s = bone_mat.decompose()
|
||||
|
||||
pb.matrix = bone_mat # Exact same position
|
||||
# pb.location.x = pbl.x # dont touch x either
|
||||
pb.location.z = pbl.z
|
||||
# pb.location.y = l.y (weirdly not working)
|
||||
|
||||
# bpy.context.view_layer.update()
|
||||
|
||||
#-# moyenne des 2 ?
|
||||
# pb.location, pb.rotation_euler, pb.scale = average_two_matrix(pb.matrix, bone_mat) ## marche pas du tout !
|
||||
|
||||
## insert keyframe
|
||||
pb.keyframe_insert('location')
|
||||
# only touched Y location
|
||||
# pb.keyframe_insert('rotation_euler')
|
||||
|
||||
k.type = 'JITTER' # 'BREAKDOWN' 'MOVING_HOLD' 'JITTER'
|
||||
ct += 1
|
||||
|
||||
else:
|
||||
if start_contact is not None:
|
||||
# print('fcu.data_path: ', fcu.data_path, fcu.array_index)
|
||||
# print(f'{b_name} contact range {start_contact} - {k.co[0]}')
|
||||
start_contact = None
|
||||
|
||||
# print(i, fcu.data_path, fcu.array_index)
|
||||
# print('time', k.co[0], '- value', k.co[1])
|
||||
|
||||
#k.handle_left
|
||||
#k.handle_right
|
||||
##change handler type ([‘FREE’, ‘VECTOR’, ‘ALIGNED’, ‘AUTO’, ‘AUTO_CLAMPED’], default ‘FREE’)
|
||||
#k.handle_left_type = 'AUTO_CLAMPED'
|
||||
#k.handle_right_type = 'AUTO_CLAMPED'
|
||||
if b_name in done.keys():
|
||||
if k.co[0] in done[b_name]:
|
||||
continue
|
||||
else:
|
||||
# mark as treated (all curve of this bone at this time)
|
||||
done[b_name] = [k.co[0]]
|
||||
|
||||
print(f'--\n{ct} keys changed in {time()-t0:.2f}s\n--') # fcurves treated in
|
||||
#-# Insert keyframe to match Hold position
|
||||
# print(f'Apply on {b_name} at {k.co[0]}')
|
||||
|
||||
#-# assign previous matrix
|
||||
pbl = pb.location.copy()
|
||||
# l, _r, _s = bone_mat.decompose()
|
||||
|
||||
pb.matrix = bone_mat # Exact same position
|
||||
# pb.location.x = pbl.x # dont touch x either
|
||||
pb.location.z = pbl.z
|
||||
# pb.location.y = l.y (weirdly not working)
|
||||
|
||||
# bpy.context.view_layer.update()
|
||||
|
||||
#-# moyenne des 2 ?
|
||||
# pb.location, pb.rotation_euler, pb.scale = average_two_matrix(pb.matrix, bone_mat) ## marche pas du tout !
|
||||
|
||||
## insert keyframe
|
||||
pb.keyframe_insert('location')
|
||||
# only touched Y location
|
||||
# pb.keyframe_insert('rotation_euler')
|
||||
|
||||
k.type = 'JITTER' # 'BREAKDOWN' 'MOVING_HOLD' 'JITTER'
|
||||
ct += 1
|
||||
|
||||
else:
|
||||
if start_contact is not None:
|
||||
# print('fcu.data_path: ', fcu.data_path, fcu.array_index)
|
||||
# print(f'{b_name} contact range {start_contact} - {k.co[0]}')
|
||||
start_contact = None
|
||||
|
||||
# print(i, fcu.data_path, fcu.array_index)
|
||||
# print('time', k.co[0], '- value', k.co[1])
|
||||
|
||||
#k.handle_left
|
||||
#k.handle_right
|
||||
##change handler type ([‘FREE’, ‘VECTOR’, ‘ALIGNED’, ‘AUTO’, ‘AUTO_CLAMPED’], default ‘FREE’)
|
||||
#k.handle_left_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
|
||||
# detect last key in contact
|
||||
|
||||
|
||||
|
||||
class UAC_OT_pin_feets(bpy.types.Operator):
|
||||
|
|
|
@ -39,6 +39,10 @@ Sidebar > Anim > unfold anim cycle
|
|||
|
||||
## Changelog:
|
||||
|
||||
0.4.2
|
||||
|
||||
- context manager for `expand cycle step` store / restore
|
||||
|
||||
0.4.1
|
||||
|
||||
- update fcurve after bake
|
||||
|
|
|
@ -2,7 +2,7 @@ bl_info = {
|
|||
"name": "Unfold Anim Cycle",
|
||||
"description": "Anim tools to develop walk/run cycles along a curve",
|
||||
"author": "Samuel Bernou",
|
||||
"version": (0, 4, 1),
|
||||
"version": (0, 4, 2),
|
||||
"blender": (3, 0, 0),
|
||||
"location": "View3D",
|
||||
"warning": "WIP",
|
||||
|
|
23
fn.py
23
fn.py
|
@ -312,3 +312,26 @@ def update_action(act):
|
|||
if area.type == 'GRAPH_EDITOR':
|
||||
area.tag_redraw()
|
||||
|
||||
### --- context manager - store / restore
|
||||
|
||||
|
||||
class attr_set():
|
||||
'''Receive a list of tuple [(data_path:python_obj, "attribute":str, "wanted value":str)]
|
||||
before with statement : Store existing values, assign wanted value
|
||||
after with statement: Restore values to their old values
|
||||
'''
|
||||
|
||||
def __init__(self, attrib_list):
|
||||
self.store = []
|
||||
for prop, attr, new_val in attrib_list:
|
||||
self.store.append( (prop, attr, getattr(prop, attr)) )
|
||||
if new_val == '_undefined': # None -> what if we want to apply None state ?
|
||||
continue
|
||||
setattr(prop, attr, new_val)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
for prop, attr, old_val in self.store:
|
||||
setattr(prop, attr, old_val)
|
||||
|
|
Loading…
Reference in New Issue