store / restore values with a context manager

master
Pullusb 2022-04-07 14:36:47 +02:00
parent d77ab84c06
commit 037f568a4b
5 changed files with 124 additions and 99 deletions

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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
View File

@ -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)