From 037f568a4be4d496ff552168ae432c414f5ca4a9 Mon Sep 17 00:00:00 2001 From: Pullusb Date: Thu, 7 Apr 2022 14:36:47 +0200 Subject: [PATCH] store / restore values with a context manager --- OP_animate_path.py | 3 +- OP_expand_cycle_step.py | 191 ++++++++++++++++++++-------------------- README.md | 4 + __init__.py | 2 +- fn.py | 23 +++++ 5 files changed, 124 insertions(+), 99 deletions(-) diff --git a/OP_animate_path.py b/OP_animate_path.py index d115743..553fdcf 100644 --- a/OP_animate_path.py +++ b/OP_animate_path.py @@ -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 diff --git a/OP_expand_cycle_step.py b/OP_expand_cycle_step.py index 52ad961..4163a91 100644 --- a/OP_expand_cycle_step.py +++ b/OP_expand_cycle_step.py @@ -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): diff --git a/README.md b/README.md index 8d6761b..40346f1 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/__init__.py b/__init__.py index ee15559..7df5f76 100644 --- a/__init__.py +++ b/__init__.py @@ -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", diff --git a/fn.py b/fn.py index b4acc4a..c93befb 100644 --- a/fn.py +++ b/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)