parent
b05b0e2b6c
commit
29623d4c3b
|
@ -63,10 +63,10 @@ def anim_path_from_translate():
|
||||||
# use original action as ref
|
# use original action as ref
|
||||||
if '_expanded' in act.name:
|
if '_expanded' in act.name:
|
||||||
base_act_name = act.name.split('_expanded')[0]
|
base_act_name = act.name.split('_expanded')[0]
|
||||||
base_act = bpy.data.action.get(base_act_name)
|
base_act = bpy.data.actions.get(base_act_name)
|
||||||
if base_act:
|
if base_act:
|
||||||
act = base_act
|
act = base_act
|
||||||
print(f'Using for {base_act_name} as reference')
|
print(f'Using for action {base_act_name} as reference')
|
||||||
else:
|
else:
|
||||||
print(f'No base action found (searching for {base_act_name})')
|
print(f'No base action found (searching for {base_act_name})')
|
||||||
|
|
||||||
|
@ -157,26 +157,39 @@ def anim_path_from_translate():
|
||||||
|
|
||||||
frame_duration = int(steps * move_frame)
|
frame_duration = int(steps * move_frame)
|
||||||
|
|
||||||
## Clear 'eval_time' keyframe before creating new ones # curve.data.animation_data_clear() # too much.. delete only eval_time
|
|
||||||
if curve.data.animation_data and curve.data.animation_data.action:
|
const = root.constraints.get("Follow Path")
|
||||||
for fcu in curve.data.animation_data.action.fcurves:
|
if not const:
|
||||||
if fcu.data_path == 'eval_time':
|
return 'ERROR', f'No "Follow Path" constraint on bone "{root.name}"'
|
||||||
curve.data.animation_data.action.fcurves.remove(fcu)
|
|
||||||
break
|
offset_data_path = f'pose.bones["{root.name}"].constraints["{const.name}"].offset'
|
||||||
|
|
||||||
|
fcu = ob.animation_data.action.fcurves.find(offset_data_path)
|
||||||
|
if fcu:
|
||||||
|
ob.animation_data.action.fcurves.remove(fcu)
|
||||||
|
|
||||||
|
|
||||||
## add eval time animation on curve
|
## add eval time animation on curve
|
||||||
anim_frame = settings.start_frame
|
anim_frame = settings.start_frame
|
||||||
curve.data.path_duration = frame_duration
|
curve.data.path_duration = frame_duration
|
||||||
curve.data.eval_time = 0
|
|
||||||
curve.data.keyframe_insert('eval_time', frame=anim_frame) # , options={'INSERTKEY_AVAILABLE'}
|
|
||||||
|
|
||||||
curve.data.eval_time = frame_duration
|
# curve.data.eval_time = 0
|
||||||
curve.data.keyframe_insert('eval_time', frame=anim_frame + frame_duration)
|
# curve.data.keyframe_insert('eval_time', frame=anim_frame) # , options={'INSERTKEY_AVAILABLE'}
|
||||||
|
|
||||||
|
const.offset = 0
|
||||||
|
const.keyframe_insert('offset', frame=anim_frame)
|
||||||
|
|
||||||
|
# curve.data.eval_time = frame_duration
|
||||||
|
# curve.data.keyframe_insert('eval_time', frame=anim_frame + frame_duration)
|
||||||
|
|
||||||
|
const.offset = -frame_duration # negative (offset time rewinding so character move forward)
|
||||||
|
const.keyframe_insert('offset', frame=anim_frame + frame_duration)
|
||||||
|
|
||||||
## all to linear (will be set to CONSTANT at the moment of sampling)
|
## all to linear (will be set to CONSTANT at the moment of sampling)
|
||||||
for fcu in curve.data.animation_data.action.fcurves:
|
fcu = ob.animation_data.action.fcurves.find(offset_data_path)
|
||||||
if fcu.data_path == 'eval_time':
|
if fcu:
|
||||||
for k in fcu.keyframe_points:
|
for k in fcu.keyframe_points:
|
||||||
k.interpolation = 'LINEAR'
|
k.interpolation = 'LINEAR'
|
||||||
|
|
||||||
## set all to constant
|
## set all to constant
|
||||||
# for k in t_fcu.keyframe_points:
|
# for k in t_fcu.keyframe_points:
|
||||||
|
@ -219,44 +232,53 @@ class UAC_OT_adjust_animation_length(bpy.types.Operator):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.object and context.object.type in ('ARMATURE', 'CURVE')
|
return context.object and context.object.type == 'ARMATURE' # in ('ARMATURE', 'CURVE')
|
||||||
|
|
||||||
val : bpy.props.FloatProperty(name='End key value')
|
val : bpy.props.FloatProperty(name='End key value')
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
# check animation data of curve
|
# check animation data of curve
|
||||||
# self.pref = fn.get_addon_prefs()
|
# self.pref = fn.get_addon_prefs()
|
||||||
curve = bpy.context.scene.anim_cycle_settings.path_to_follow
|
|
||||||
if not curve:
|
|
||||||
if context.object.type != 'ARMATURE':
|
|
||||||
self.report({'ERROR'}, 'no curve targeted in "Path" field')
|
|
||||||
return {"CANCELLED"}
|
|
||||||
|
|
||||||
curve, _const = fn.get_follow_curve_from_armature(context.object)
|
ob = context.object
|
||||||
if isinstance(curve, str):
|
root_name = fn.get_root_name()
|
||||||
self.report({curve}, _const)
|
root = ob.pose.bones.get(root_name)
|
||||||
return {"CANCELLED"}
|
if not root:
|
||||||
|
self.report({'ERROR'}, f'no bone {root_name} found in {ob.name}')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
self.act = fn.get_obj_action(curve.data)
|
# TODO replace fcurve getter by fn.get_offset_fcu(root)
|
||||||
|
# self.fcu = fn.get_offset_fcu(root)
|
||||||
|
# if isinstance(self.fcu, str):
|
||||||
|
# self.report({'ERROR'}, self.fcu)
|
||||||
|
# return {"CANCELLED"}
|
||||||
|
|
||||||
|
|
||||||
|
self.act = fn.get_obj_action(ob)
|
||||||
if not self.act:
|
if not self.act:
|
||||||
self.report({'ERROR'}, f'No action on {curve.name} data')
|
self.report({'ERROR'}, f'No action on {ob.name} data')
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
# if '_expanded' in self.act.name:
|
# if '_expanded' in self.act.name:
|
||||||
# self.report({'WARNING'}, f'Action is expanded')
|
# self.report({'WARNING'}, f'Action is expanded')
|
||||||
|
|
||||||
self.fcu = None
|
const = root.constraints.get("Follow Path")
|
||||||
for fcu in self.act.fcurves:
|
if not const:
|
||||||
if fcu.data_path == 'eval_time':
|
self.report({'ERROR'}, f'No "Follow Path" constraint on bone "{root.name}"')
|
||||||
self.fcu = fcu
|
return {"CANCELLED"}
|
||||||
break
|
offset_data_path = f'pose.bones["{root.name}"].constraints["{const.name}"].offset'
|
||||||
|
|
||||||
if not self.fcu or not len(self.fcu.keyframe_points):
|
self.fcu = self.act.fcurves.find(offset_data_path)
|
||||||
self.report({'ERROR'}, f'No eval_time animated on {curve.name} data action (or no keys)')
|
if not self.fcu:
|
||||||
|
self.report({'ERROR'}, f'No fcurve at datapath: {offset_data_path}')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
if not len(self.fcu.keyframe_points):
|
||||||
|
self.report({'ERROR'}, f'No keys on fcurve {self.fcu.data_path}')
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
if len(self.fcu.keyframe_points) > 2:
|
if len(self.fcu.keyframe_points) > 2:
|
||||||
self.report({'WARNING'}, f'{curve.name} eval_time has {len(self.fcu.keyframe_points)} keyframe (should just have 2 to redefine speed)')
|
self.report({'WARNING'}, f'{ob.name} offset anim has {len(self.fcu.keyframe_points)} keyframe (should just have 2 to redefine speed)')
|
||||||
|
|
||||||
self.k = self.fcu.keyframe_points[-1]
|
self.k = self.fcu.keyframe_points[-1]
|
||||||
self.val = self.init_ky = self.k.co.y
|
self.val = self.init_ky = self.k.co.y
|
||||||
|
|
|
@ -76,22 +76,22 @@ def bake_cycle(on_selection=True):
|
||||||
current_offset = offset = last - first
|
current_offset = offset = last - first
|
||||||
|
|
||||||
keys_num = len(fcu_kfs)
|
keys_num = len(fcu_kfs)
|
||||||
if debug: print(keys_num)
|
if debug >= 2: print(keys_num)
|
||||||
|
|
||||||
if keys_num <= 1:
|
if keys_num <= 1:
|
||||||
if debug: print(b_name, f'{keys_num} key')
|
if debug >= 2: print(b_name, f'{keys_num} key')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
## ! important: delete last after computing offset IF cycle have first frame repeatead as last !
|
## ! important: delete last after computing offset IF cycle have first frame repeatead as last !
|
||||||
fcu_kfs.pop()
|
fcu_kfs.pop()
|
||||||
# print('offset: ', offset)
|
# print('offset: ', offset)
|
||||||
|
|
||||||
if debug: print('keys', len(fcu_kfs))
|
if debug >= 2: 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
|
||||||
if debug: print('iterations: ', iterations)
|
if debug >= 2: 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
|
||||||
|
@ -240,18 +240,21 @@ def pin_down_feets():
|
||||||
if obj.type != 'ARMATURE':
|
if obj.type != 'ARMATURE':
|
||||||
print('ERROR', 'active is not an armature type')
|
print('ERROR', 'active is not an armature type')
|
||||||
return
|
return
|
||||||
|
debug = fn.get_addon_prefs().debug
|
||||||
|
scn = bpy.context.scene
|
||||||
# Delete current action if its not the main one
|
# Delete current action if its not the main one
|
||||||
# create a new '_autogen' one
|
# create a new '_autogen' one
|
||||||
act = fn.set_generated_action(obj)
|
act = fn.set_generated_action(obj)
|
||||||
if not act:
|
if not act:
|
||||||
return ('ERROR', f'No action on {obj.name}')
|
return ('ERROR', f'No action on {obj.name}')
|
||||||
print('action', act.name)
|
|
||||||
|
if debug:
|
||||||
|
print('action', act.name)
|
||||||
|
|
||||||
act = obj.animation_data.action
|
act = obj.animation_data.action
|
||||||
|
|
||||||
to_change_list = [
|
to_change_list = [
|
||||||
(bpy.context.scene, 'frame_current', '_undefined'), # use '_undefined' when no value to assign for now
|
(bpy.context.scene, 'frame_current'),
|
||||||
(bpy.context.scene.render, 'use_simplify', True),
|
(bpy.context.scene.render, 'use_simplify', True),
|
||||||
(bpy.context.scene.render, 'simplify_subdivision', 0),
|
(bpy.context.scene.render, 'simplify_subdivision', 0),
|
||||||
]
|
]
|
||||||
|
@ -277,51 +280,81 @@ def pin_down_feets():
|
||||||
t0 = time()
|
t0 = time()
|
||||||
ct = 0
|
ct = 0
|
||||||
done = {}
|
done = {}
|
||||||
|
viewed_data_paths = []
|
||||||
for fcu in act.fcurves:
|
for fcu in act.fcurves:
|
||||||
|
|
||||||
# check only location
|
# check only location
|
||||||
if not fcu.data_path.endswith('.location'):
|
if not fcu.data_path.endswith('.location'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# prop = fcu.data_path.split('.')[-1]
|
|
||||||
|
|
||||||
b_name = fcu.data_path.split('"')[1]
|
# skip same data path with different array index to avoid multiple iteration
|
||||||
# print('b_name: ', b_name, fcu.is_valid)
|
if fcu.data_path in viewed_data_paths:
|
||||||
|
continue
|
||||||
|
|
||||||
|
viewed_data_paths.append(fcu.data_path)
|
||||||
|
|
||||||
|
prop = fcu.data_path.split('.')[-1]
|
||||||
|
|
||||||
|
b_name = fcu.data_path.split('"')[1] # print('b_name: ', b_name, fcu.is_valid)
|
||||||
|
|
||||||
|
## TEST : only foot bones
|
||||||
|
if not 'foot' in b_name:
|
||||||
|
continue
|
||||||
|
|
||||||
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')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
contact_ranges = []
|
||||||
start_contact = None
|
start_contact = None
|
||||||
|
prev = 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(int(k.co[0]))
|
prev = k
|
||||||
if start_contact is None:
|
if start_contact is None:
|
||||||
start_contact=k.co[0]
|
start_contact = int(k.co.x)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if start_contact is not None:
|
||||||
|
# print(f'contact range {start_contact} - {k.co.x:.0f}')
|
||||||
|
if prev:
|
||||||
|
contact_ranges.append((start_contact, int(prev.co.x)))
|
||||||
|
start_contact = None
|
||||||
|
prev = None
|
||||||
|
|
||||||
|
if not contact_ranges:
|
||||||
|
if debug >= 2: print(f'SKIP (no extreme keys): {b_name} > {prop}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if debug: print(f'fcurve: {b_name} > {prop}')
|
||||||
|
|
||||||
|
for r in reversed(contact_ranges): # iterate in reverse ranges (not really necessary)
|
||||||
|
print(f'range: {r}')
|
||||||
|
first = True
|
||||||
|
for i in range(r[0], r[1]+1)[::-1]: # start from the end of the range
|
||||||
|
# for i in range(r[0], r[1]+1):
|
||||||
|
bpy.context.scene.frame_set(i)
|
||||||
|
if first:
|
||||||
# record coordinate relative to referent object (or world coord)
|
# record coordinate relative to referent object (or world coord)
|
||||||
bone_mat = pb.matrix.copy()
|
bone_mat = pb.matrix.copy()
|
||||||
# bone_mat = obj.matrix_world @ pb.matrix.copy()
|
# bone_mat = obj.matrix_world @ pb.matrix.copy()
|
||||||
|
first = False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if b_name in done.keys():
|
# print(f'Apply on {b_name} at {i}')
|
||||||
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]]
|
|
||||||
|
|
||||||
#-# Insert keyframe to match Hold position
|
|
||||||
# print(f'Apply on {b_name} at {k.co[0]}')
|
|
||||||
|
|
||||||
#-# assign previous matrix
|
#-# assign previous matrix
|
||||||
pbl = pb.location.copy()
|
# pbl = pb.location.copy()
|
||||||
# l, _r, _s = bone_mat.decompose()
|
|
||||||
|
|
||||||
pb.matrix = bone_mat # Exact same position
|
pb.matrix = bone_mat # Exact same position
|
||||||
|
|
||||||
|
## maybe align on a specific axis
|
||||||
# pb.location.x = pbl.x # dont touch x either
|
# pb.location.x = pbl.x # dont touch x either
|
||||||
pb.location.z = pbl.z
|
|
||||||
|
#pb.location.z = pbl.z # Z is not necessarily up in local axis, need to check first
|
||||||
|
|
||||||
# pb.location.y = l.y (weirdly not working)
|
# pb.location.y = l.y (weirdly not working)
|
||||||
|
|
||||||
# bpy.context.view_layer.update()
|
# bpy.context.view_layer.update()
|
||||||
|
@ -331,33 +364,23 @@ def pin_down_feets():
|
||||||
|
|
||||||
## insert keyframe
|
## insert keyframe
|
||||||
pb.keyframe_insert('location')
|
pb.keyframe_insert('location')
|
||||||
# only touched Y location
|
|
||||||
# pb.keyframe_insert('rotation_euler')
|
|
||||||
|
|
||||||
k.type = 'JITTER' # 'BREAKDOWN' 'MOVING_HOLD' 'JITTER'
|
# only touched Y location
|
||||||
|
pb.keyframe_insert('rotation_euler')
|
||||||
|
|
||||||
|
# if i == r[1]+1: # (last key) in normal
|
||||||
|
# if i == r[0]: # (last key) in reverse
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# k.type = 'JITTER' # 'BREAKDOWN' 'MOVING_HOLD' 'JITTER'
|
||||||
ct += 1
|
ct += 1
|
||||||
|
|
||||||
else:
|
print(f'--\n{ct} keys changed/added in {time()-t0:.2f}s\n--') # fcurves treated in
|
||||||
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
|
## RESTORE
|
||||||
# without >> 433 keys changed in 29.15s
|
# without >> 433 keys changed in 29.15s
|
||||||
# with all collection excluded >> 433 keys changed in 25.00s
|
# with all collection excluded >> 433 keys changed in 25.00s
|
||||||
# with simplify >> 9.57s
|
# with simplify set to 0 >> 9.57s
|
||||||
|
|
||||||
tmp_col.objects.unlink(obj)
|
tmp_col.objects.unlink(obj)
|
||||||
bpy.data.collections.remove(tmp_col)
|
bpy.data.collections.remove(tmp_col)
|
||||||
|
|
39
README.md
39
README.md
|
@ -22,23 +22,44 @@ sequencial set of tools:
|
||||||
- Expand anim cycle and step curve animation
|
- Expand anim cycle and step curve animation
|
||||||
- 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
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
- pin feets:
|
||||||
|
- iterate in reverse in keys when pinning so last foot position is correct
|
||||||
|
- create intermediate keys (at each frame when necessary) to prevent lateral sliding on curved path
|
||||||
|
(maybe expose as an option... not needed if path is straight for example... or auto detect if path is full straight at the moment of pin ops)
|
||||||
|
|
||||||
|
- Expose methods to go back in action history
|
||||||
|
|
||||||
|
|
||||||
|
*things to consider*:
|
||||||
|
|
||||||
|
- Expose foot ?
|
||||||
|
- Store path animation on a separate action (but that mean NLA hadto be used every time)
|
||||||
|
|
||||||
|
Bonus:
|
||||||
|
- Use position A-B method to generate curve (with retimed animation to prioritize speed over fidelity)
|
||||||
|
- auto-determine foot bone to use for distance reference
|
||||||
|
- create nurb path instead of curve
|
||||||
|
- Smoothing keys after last freezed to avoid too much gap "pose click".
|
||||||
|
|
||||||
|
<!--
|
||||||
|
### DONE
|
||||||
|
- align curve to root
|
||||||
|
- Put curve forward motion on bones modifier's offset value as negative time offset (instead of using curve )
|
||||||
|
-->
|
||||||
---
|
---
|
||||||
|
|
||||||
## Changelog:
|
## Changelog:
|
||||||
|
|
||||||
|
0.5.0
|
||||||
|
|
||||||
|
- pin feet working
|
||||||
|
|
||||||
0.4.2
|
0.4.2
|
||||||
|
|
||||||
- context manager for `expand cycle step` store / restore
|
- context manager for `expand cycle step` store / restore
|
||||||
|
|
|
@ -2,7 +2,7 @@ bl_info = {
|
||||||
"name": "Unfold Anim Cycle",
|
"name": "Unfold Anim Cycle",
|
||||||
"description": "Anim tools to develop walk/run cycles along a curve",
|
"description": "Anim tools to develop walk/run cycles along a curve",
|
||||||
"author": "Samuel Bernou",
|
"author": "Samuel Bernou",
|
||||||
"version": (0, 4, 3),
|
"version": (0, 5, 0),
|
||||||
"blender": (3, 0, 0),
|
"blender": (3, 0, 0),
|
||||||
"location": "View3D",
|
"location": "View3D",
|
||||||
"warning": "WIP",
|
"warning": "WIP",
|
||||||
|
|
89
fn.py
89
fn.py
|
@ -1,4 +1,5 @@
|
||||||
import bpy
|
import bpy
|
||||||
|
import bpy_types
|
||||||
import re
|
import re
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from mathutils import Matrix, Vector, Color
|
from mathutils import Matrix, Vector, Color
|
||||||
|
@ -55,8 +56,7 @@ def get_gnd():
|
||||||
def get_follow_curve_from_armature(arm):
|
def get_follow_curve_from_armature(arm):
|
||||||
"""Return curve and constraint or a tuple of string ('error', 'message')
|
"""Return curve and constraint or a tuple of string ('error', 'message')
|
||||||
"""
|
"""
|
||||||
pref = get_addon_prefs()
|
name = get_root_name()
|
||||||
name = pref.tgt_bone
|
|
||||||
|
|
||||||
parents = []
|
parents = []
|
||||||
# root = b.id_data.pose.bones.get(name)
|
# root = b.id_data.pose.bones.get(name)
|
||||||
|
@ -312,22 +312,91 @@ def update_action(act):
|
||||||
if area.type == 'GRAPH_EDITOR':
|
if area.type == 'GRAPH_EDITOR':
|
||||||
area.tag_redraw()
|
area.tag_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
def get_offset_fcu(act=None):
|
||||||
|
'''Get an action, object, pose_bone or a bone constraint (if nothing is passed use active object)
|
||||||
|
return offset fcurve or a string describing error
|
||||||
|
'''
|
||||||
|
ob = None
|
||||||
|
bone_name = None
|
||||||
|
const_name = None
|
||||||
|
if act is None:
|
||||||
|
if not bpy.context.object:
|
||||||
|
return 'No active object'
|
||||||
|
act = bpy.context.object
|
||||||
|
|
||||||
|
if isinstance(act, bpy.types.FollowPathConstraint):
|
||||||
|
ob = act.id_data
|
||||||
|
const = act
|
||||||
|
bones = [b for b in ob.pose.bones for c in b.constraints if b.constraints if c == act]
|
||||||
|
if not bones:
|
||||||
|
return f'no bone found with constraint {ob.name}'
|
||||||
|
bone = bones[0]
|
||||||
|
bone_name = bone.name
|
||||||
|
const_name = const.name
|
||||||
|
|
||||||
|
if isinstance(act, bpy_types.PoseBone):
|
||||||
|
bone = act
|
||||||
|
bone_name = act.name
|
||||||
|
ob = act = act.id_data # fall_back to armature object
|
||||||
|
if not const_name:
|
||||||
|
consts = [c for c in bone.constraints if isinstance(c, bpy.types.FollowPathConstraint)]
|
||||||
|
if not consts:
|
||||||
|
return f'no follow path constraint on bone {bone_name}'
|
||||||
|
const_name = consts[0].name
|
||||||
|
|
||||||
|
if isinstance(act, bpy.types.Object):
|
||||||
|
ob = act
|
||||||
|
if not ob.animation_data:
|
||||||
|
return f'{ob.name} has no animation_data'
|
||||||
|
act = ob.animation_data
|
||||||
|
|
||||||
|
if isinstance(act, bpy.types.AnimData):
|
||||||
|
ob = act.id_data
|
||||||
|
if not act.action:
|
||||||
|
return f'{ob.name} has animation_data but no action'
|
||||||
|
act = act.action
|
||||||
|
|
||||||
|
|
||||||
|
if bone_name and const_name:
|
||||||
|
offset_data_path = f'pose.bones["{bone_name}"].constraints["{const_name}"].offset'
|
||||||
|
fcu = act.fcurves.find(offset_data_path)
|
||||||
|
if not fcu:
|
||||||
|
return f'No fcurve found with data_path {offset_data_path}'
|
||||||
|
return fcu
|
||||||
|
|
||||||
|
# bone_name = get_root_name()
|
||||||
|
|
||||||
|
# find from determined action
|
||||||
|
fcus = [fcu for fcu in act.fcurves if all(x in fcu.data_path for x in ('pose.bones', 'constraints', 'offset'))]
|
||||||
|
if not fcus:
|
||||||
|
return f'no offset fcurves found for: {act.name}'
|
||||||
|
|
||||||
|
if len(fcus) > 1:
|
||||||
|
print(f'/!\ multiple fcurves seem to have a follow path constraint')
|
||||||
|
|
||||||
|
return fcus[0]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### --- context manager - store / restore
|
### --- context manager - store / restore
|
||||||
|
|
||||||
|
|
||||||
class attr_set():
|
class attr_set():
|
||||||
'''Receive a list of tuple [(data_path:python_obj, "attribute":str, "wanted value":str)]
|
'''Receive a list of tuple [(data_path, "attribute" [, wanted value)] ]
|
||||||
before with statement : Store existing values, assign wanted value
|
entering with-statement : Store existing values, assign wanted value (if any)
|
||||||
after with statement: Restore values to their old values
|
exiting with-statement: Restore values to their old values
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, attrib_list):
|
def __init__(self, attrib_list):
|
||||||
self.store = []
|
self.store = []
|
||||||
for prop, attr, new_val in attrib_list:
|
# item = (prop, attr, [new_val])
|
||||||
|
for item in attrib_list:
|
||||||
|
prop, attr = item[:2]
|
||||||
self.store.append( (prop, attr, getattr(prop, attr)) )
|
self.store.append( (prop, attr, getattr(prop, attr)) )
|
||||||
if new_val == '_undefined': # None -> what if we want to apply None state ?
|
if len(item) >= 3:
|
||||||
continue
|
setattr(prop, attr, item[2])
|
||||||
setattr(prop, attr, new_val)
|
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
|
@ -6,10 +6,13 @@ class UAC_addon_prefs(bpy.types.AddonPreferences):
|
||||||
bl_idname = __name__.split('.')[0] # or with: os.path.splitext(__name__)[0]
|
bl_idname = __name__.split('.')[0] # or with: os.path.splitext(__name__)[0]
|
||||||
|
|
||||||
# some_bool_prop to display in the addon pref
|
# some_bool_prop to display in the addon pref
|
||||||
debug : bpy.props.BoolProperty(
|
debug : bpy.props.IntProperty(
|
||||||
name='Debug',
|
name='Debug',
|
||||||
description="Enable Debug prints",
|
description="Enable Debug prints\n\
|
||||||
default=False)
|
0 = no prints\n\
|
||||||
|
1 = basic\n\
|
||||||
|
2 = full prints",
|
||||||
|
default=1)
|
||||||
|
|
||||||
tgt_bone : bpy.props.StringProperty(
|
tgt_bone : bpy.props.StringProperty(
|
||||||
name="Constrained Pose bone name", default='world',
|
name="Constrained Pose bone name", default='world',
|
||||||
|
|
Loading…
Reference in New Issue