wip working on path animate
parent
62aeddfd49
commit
5a221aa0fe
|
@ -1,9 +1,31 @@
|
|||
import bpy
|
||||
from . import fn
|
||||
|
||||
from mathutils import Vector, Euler
|
||||
## step 2 : Auto animate the path (with feet selection) and modal to adjust speed
|
||||
|
||||
def anim_path_from_y_translate():
|
||||
def get_bone_transform_at_frame(b, act, frame):
|
||||
'''Find every loc, rot, scale values at given frame'''
|
||||
transform = {}
|
||||
for channel in ('location', 'rotation_euler', 'scale'):
|
||||
chan_list = []
|
||||
for i in range(3):
|
||||
f = act.fcurves.find(f'pose.bones["{b.name}"].{channel}', index=i)
|
||||
if not f:
|
||||
print(frame, channel, 'not animated ! using current value')
|
||||
chan_list.append(getattr(b, channel)) # get current value since not animated
|
||||
continue
|
||||
chan_list.append(f.evaluate(frame))
|
||||
|
||||
print(frame, b.name, channel, chan_list)
|
||||
if channel == 'rotation_euler':
|
||||
transform[channel] = Euler(chan_list)
|
||||
else:
|
||||
transform[channel] = Vector(chan_list)
|
||||
|
||||
return transform # loc, rot, scale
|
||||
|
||||
|
||||
def anim_path_from_translate():
|
||||
print(fn.helper())
|
||||
|
||||
obj = bpy.context.object
|
||||
|
@ -13,8 +35,8 @@ def anim_path_from_y_translate():
|
|||
# found curve through constraint
|
||||
b = bpy.context.active_pose_bone
|
||||
|
||||
if not 'shoe' in b.bone.name:
|
||||
return ('ERROR', 'No "shoe" in active bone name\n-> Select foot that has the most reliable contact')
|
||||
# if not 'foot' in b.bone.name:
|
||||
# return ('ERROR', 'No "foot" in active bone name\n-> Select foot that has the most reliable contact')
|
||||
|
||||
curve = None
|
||||
if bpy.context.scene.anim_cycle_settings.path_to_follow:
|
||||
|
@ -43,24 +65,87 @@ def anim_path_from_y_translate():
|
|||
# CHANGE - retiré le int de la frame
|
||||
# keyframes = [int(k.co[0]) for fcu in act.fcurves for k in fcu.keyframe_points]
|
||||
|
||||
## calculate offset from bones by evaluating distance at extreme distance
|
||||
|
||||
|
||||
# fcurve :
|
||||
# name : fcu.data_path.split('"')[1] (bone_name)
|
||||
# properties: fcu.data_path.split('.')[-1] ('location', rotation_euler)
|
||||
# axis : fcu.array_index (to get axis letter : {0:'X', 1:'Y', 2:'Z'}[fcu.array_index])
|
||||
|
||||
## get only fcurves relative to selected bone
|
||||
b_fcurves = [fcu for fcu in act.fcurves if fcu.data_path.split('"')[1] == b.bone.name]
|
||||
|
||||
|
||||
start_frame = end_frame = None
|
||||
|
||||
for fcu in b_fcurves:
|
||||
encountered_marks = False # flag to stop after last extreme of each fcu
|
||||
for k in fcu.keyframe_points:
|
||||
# if k.select_control_point: # based on selection ?
|
||||
if k.type == 'EXTREME':
|
||||
encountered_marks = True
|
||||
|
||||
f = k.co.x
|
||||
if start_frame is None:
|
||||
start_frame = f
|
||||
if start_frame > f:
|
||||
start_frame = f
|
||||
|
||||
if end_frame is None:
|
||||
end_frame = f
|
||||
if end_frame < f:
|
||||
end_frame = f
|
||||
|
||||
else:
|
||||
if encountered_marks:
|
||||
## means back to other frame type after passed breakdown we stop
|
||||
## (for this fcu)
|
||||
break
|
||||
|
||||
if start_frame is None or end_frame is None:
|
||||
return ('ERROR', f'No (or not enough) keyframe marked Extreme {obj.name} > {b.name}')
|
||||
if start_frame == end_frame:
|
||||
return ('ERROR', f'Only one key detected as extreme (at frame {start_frame}) !\nNeed at least two chained marked keys')
|
||||
|
||||
print(f'Offset from key range. start: {start_frame} - end: {end_frame}')
|
||||
move_frame = end_frame - start_frame
|
||||
|
||||
|
||||
|
||||
## Find move_val from diff position at start and end frame wihtin character forward axis
|
||||
|
||||
|
||||
start_transform = get_bone_transform_at_frame(b, act, start_frame)
|
||||
start_mat = fn.compose_matrix(start_transform['location'], start_transform['rotation_euler'], start_transform['scale'])
|
||||
|
||||
end_transform = get_bone_transform_at_frame(b, act, end_frame)
|
||||
end_mat = fn.compose_matrix(end_transform['location'], end_transform['rotation_euler'], end_transform['scale'])
|
||||
|
||||
# bpy.context.scene.cursor.location = (b.bone.matrix_local @ start_mat).to_translation() # obj.matrix_world @
|
||||
bpy.context.scene.cursor.location = (b.bone.matrix_local @ end_mat).to_translation() # obj.matrix_world @
|
||||
# bpy.context.scene.cursor.location = (b.matrix_basis.inverted() @ start_mat).to_translation() # obj.matrix_world @
|
||||
# bpy.context.scene.cursor.location = start_transform['location'] # obj.matrix_world @
|
||||
return # FIXME
|
||||
|
||||
### old method using directly one axis
|
||||
"""
|
||||
axis = {'X':0, 'Y':1, 'Z':2}
|
||||
## calculate offset from bones
|
||||
locy_fcu = None
|
||||
loc_fcu = None
|
||||
for fcu in act.fcurves:
|
||||
if fcu.data_path.split('"')[1] != b.bone.name:
|
||||
continue
|
||||
if fcu.data_path.split('.')[-1] == 'location' and fcu.array_index == 1:
|
||||
locy_fcu = fcu
|
||||
|
||||
if not locy_fcu:
|
||||
if fcu.data_path.split('.')[-1] == 'location' and fcu.array_index == axis['Y']:
|
||||
loc_fcu = fcu
|
||||
if not loc_fcu:
|
||||
return ('ERROR', 'Current bone, location.y animation not found')
|
||||
|
||||
start = None
|
||||
end = None
|
||||
|
||||
## based on extreme
|
||||
for k in locy_fcu.keyframe_points:
|
||||
for k in loc_fcu.keyframe_points:
|
||||
# if k.select_control_point: # based on selection
|
||||
if k.type == 'EXTREME': # using extreme keys.
|
||||
if start is None:
|
||||
|
@ -75,7 +160,7 @@ def anim_path_from_y_translate():
|
|||
print(f'Offset from key range. start: {start.co.x} - end: {end.co.x}')
|
||||
|
||||
if not start:
|
||||
return ('ERROR', f"No extreme marked frame was found on bone {b.bone.name}.{['x','y','z'][locy_fcu.array_index]}")
|
||||
return ('ERROR', f"No extreme marked frame was found on bone {b.bone.name}.{['x','y','z'][loc_fcu.array_index]}")
|
||||
if start == end:
|
||||
return ('ERROR', f'Only one key detected as extreme (at frame {start.co.x}) !\nNeed at least two chained marked keys')
|
||||
|
||||
|
@ -89,6 +174,9 @@ def anim_path_from_y_translate():
|
|||
|
||||
# Y positive value (forward) ->
|
||||
move_val = abs(start_val - end_val)
|
||||
"""
|
||||
|
||||
|
||||
print('move_val: ', move_val)
|
||||
|
||||
length = fn.get_curve_length(curve)
|
||||
|
@ -123,13 +211,16 @@ def anim_path_from_y_translate():
|
|||
## set all to constant
|
||||
# for k in t_fcu.keyframe_points:
|
||||
# k.interpolation = 'CONSTANT'
|
||||
|
||||
|
||||
print('end of set_follow_path_anim')
|
||||
|
||||
class UAC_OT_animate_path(bpy.types.Operator):
|
||||
bl_idname = "anim.animate_path"
|
||||
bl_label = "Animate Path"
|
||||
bl_description = "Select the most representative 'in contact' feet of the cycle"
|
||||
bl_description = "Select the most representative 'in contact' feet of the cycle\
|
||||
\nSelected bone should have two keyframe marked as type extreme (red):\
|
||||
\nA key for foot first ground contact and other key for foot last contact frame\
|
||||
\nSelect keyframe and use R > Extreme)"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
|
@ -142,7 +233,7 @@ class UAC_OT_animate_path(bpy.types.Operator):
|
|||
|
||||
def execute(self, context):
|
||||
# TODO clear previous animation (keys) if there is any
|
||||
err = anim_path_from_y_translate()
|
||||
err = anim_path_from_translate()
|
||||
if err:
|
||||
self.report({err[0]}, err[1])
|
||||
if err[0] == 'ERROR':
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import bpy
|
||||
from mathutils import Vector, Quaternion
|
||||
from math import sin, cos, radians
|
||||
import numpy as np
|
||||
|
||||
## all action needed to setup the walk
|
||||
|
||||
## Need to know what is the forward axis
|
||||
|
||||
|
||||
## https://www.meccanismocomplesso.org/en/3d-rotations-and-euler-angles-in-python/
|
||||
|
||||
class UAC_OT_autoset_axis(bpy.types.Operator):
|
||||
bl_idname = "uac.autoset_axis"
|
||||
bl_label = "Auto Set Axis"
|
||||
bl_description = "Define forward axis from armature"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'ARMATURE'
|
||||
|
||||
# def invoke(self, context, event):
|
||||
# self.shift = event.shift
|
||||
# return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
ob = context.object
|
||||
|
||||
## root need to be user defined to assume corner cases
|
||||
root = ob.pose.bones.get('world')
|
||||
if not root:
|
||||
print('No root found')
|
||||
return {"CANCELLED"}
|
||||
|
||||
root_pos = ob.matrix_world @ root.matrix.to_translation()
|
||||
|
||||
up = Vector((0,0,1))
|
||||
|
||||
# gather all .R suffixed bones
|
||||
|
||||
all_right_pos = [ob.matrix_world @ b.matrix.to_translation() for b in ob.pose.bones if b.bone.name.lower().endswith('.r')]
|
||||
|
||||
if not all_right_pos:
|
||||
print('No .r suffix found, need to adjust manually')
|
||||
return {"CANCELLED"}
|
||||
|
||||
# get median position
|
||||
median_r_pos = (sum(all_right_pos, Vector()) / len(all_right_pos)) - root_pos
|
||||
|
||||
median_r_vec = Vector((median_r_pos[0], median_r_pos[1], 0))
|
||||
median_r_vec.normalize()
|
||||
|
||||
# Determine forward direction by rotating 90 degrees CCW on up axis
|
||||
guessed_forward = Quaternion(Vector((0,0,1)), radians(90)) @ median_r_vec
|
||||
|
||||
## TODO determine what axis of root align bests with guessed_forward (minimum 3D angle)
|
||||
|
||||
axises = {'FORWARD_Y':(0,1,0), 'TRACK_NEGATIVE_Y':(0,-1,0), 'FORWARD_Z':(0,0,1), 'TRACK_NEGATIVE_Z':(0,0,-1)} # 'FORWARD_X':(1,0,0), 'TRACK_NEGATIVE_X':(-1,0,0)
|
||||
ref_angle = 10
|
||||
best_axis = False
|
||||
for axis, vec in axises.items(): # (1,0,0), (-1,0,0)
|
||||
angle = guessed_forward.angle(ob.matrix_world @ root.matrix @ Vector(vec))
|
||||
print(axis, 'angle: ', angle)
|
||||
if angle < ref_angle:
|
||||
ref_angle = angle
|
||||
best_axis = axis
|
||||
|
||||
if not best_axis:
|
||||
print('No axis found')
|
||||
return {"CANCELLED"}
|
||||
|
||||
context.scene.anim_cycle_settings.forward_axis = best_axis
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(UAC_OT_autoset_axis)
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(UAC_OT_autoset_axis)
|
||||
|
|
@ -5,8 +5,8 @@ from . import fn
|
|||
|
||||
|
||||
def create_follow_path_constraint(ob, curve):
|
||||
pref = fn.get_addon_prefs()
|
||||
bone_name = pref.tgt_bone
|
||||
prefs = fn.get_addon_prefs()
|
||||
bone_name = prefs.tgt_bone
|
||||
root = ob.pose.bones.get(bone_name)
|
||||
if not root:
|
||||
return ('ERROR', f'posebone {bone_name} not found in armature {ob.name} check addon preferences to change name')
|
||||
|
@ -123,20 +123,22 @@ class UAC_OT_create_curve_path(bpy.types.Operator):
|
|||
|
||||
def execute(self, context):
|
||||
# use root (or other specified bone) to find where to put the curve
|
||||
pref = fn.get_addon_prefs()
|
||||
prefs = fn.get_addon_prefs()
|
||||
ob = context.object
|
||||
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
bone_name = pref.tgt_bone
|
||||
bone_name = prefs.tgt_bone
|
||||
root = ob.pose.bones.get(bone_name)
|
||||
if not root:
|
||||
self.report({'ERROR'}, f'posebone {bone_name} not found in armature {ob.name} check addon preferences to change name')
|
||||
return {"CANCELLED"}
|
||||
|
||||
## create curve at bone position
|
||||
loc = ob.matrix_world @ root.matrix.to_translation()
|
||||
|
||||
bpy.ops.curve.primitive_bezier_curve_add(radius=1, enter_editmode=True, align='WORLD', location=loc, scale=(1, 1, 1))
|
||||
|
||||
# TODO propose nurbs instead of curve
|
||||
# TODO mode elegantly create the curve using data...
|
||||
bpy.ops.curve.primitive_bezier_curve_add(radius=1, enter_editmode=True, align='WORLD', location=loc, scale=(1, 1, 1))
|
||||
|
||||
# fast straighten
|
||||
print('context.object: ', context.object.name)
|
||||
curve = context.object
|
||||
|
@ -144,7 +146,8 @@ class UAC_OT_create_curve_path(bpy.types.Operator):
|
|||
curve.show_in_front = True
|
||||
bpy.ops.curve.handle_type_set(type='VECTOR')
|
||||
bpy.ops.curve.handle_type_set(type='ALIGNED')
|
||||
# offset to have start
|
||||
|
||||
# offset to have start aligned
|
||||
bpy.ops.transform.translate(value=(1, 0, 0), orient_type='LOCAL',
|
||||
orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='LOCAL',
|
||||
constraint_axis=(True, False, False), mirror=True, use_proportional_edit=False)
|
||||
|
@ -157,8 +160,9 @@ class UAC_OT_create_curve_path(bpy.types.Operator):
|
|||
# bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
|
||||
## TODO replace this part with >> create_follow_path_constraint(ob, curve)
|
||||
|
||||
if root.name == 'root' and root.location != (0,0,0):
|
||||
|
||||
## reset location to avoid double transform offset from root position
|
||||
if root.name.lower() in ('world', 'root') and root.location != (0,0,0):
|
||||
old_loc = root.location
|
||||
root.location = (0,0,0)
|
||||
print(f'root moved from {old_loc} to (0,0,0) to counter curve offset')
|
||||
|
@ -167,7 +171,7 @@ class UAC_OT_create_curve_path(bpy.types.Operator):
|
|||
const = root.constraints.new('FOLLOW_PATH')
|
||||
const.target = curve
|
||||
# axis only in this case, should be in addon to prefs
|
||||
const.forward_axis = 'FORWARD_X' # 'TRACK_NEGATIVE_Y'
|
||||
const.forward_axis = prefs.default_forward_axis # 'FORWARD_X', 'TRACK_NEGATIVE_Y'
|
||||
const.use_curve_follow = True
|
||||
|
||||
return {"FINISHED"}
|
||||
|
|
|
@ -39,6 +39,10 @@ Sidebar > Anim > unfold anim cycle
|
|||
|
||||
## Changelog:
|
||||
|
||||
0.3.3
|
||||
|
||||
- total wip
|
||||
|
||||
0.3.0
|
||||
|
||||
- Rework interface
|
||||
|
|
107
__init__.py
107
__init__.py
|
@ -1,9 +1,9 @@
|
|||
bl_info = {
|
||||
"name": "Unfold Anim Cycle",
|
||||
"description": "Anim utility to develop walk/run cycles along a curve",
|
||||
"description": "Anim tools to develop walk/run cycles along a curve",
|
||||
"author": "Samuel Bernou",
|
||||
"version": (0, 3, 2),
|
||||
"blender": (2, 92, 0),
|
||||
"version": (0, 3, 3),
|
||||
"blender": (3, 0, 0),
|
||||
"location": "View3D",
|
||||
"warning": "WIP",
|
||||
"doc_url": "https://gitlab.com/autour-de-minuit/blender/unfold_anim_cycle",
|
||||
|
@ -13,6 +13,9 @@ bl_info = {
|
|||
|
||||
if 'bpy' in locals():
|
||||
import importlib as imp
|
||||
imp.reload(properties)
|
||||
imp.reload(preferences)
|
||||
imp.reload(OP_setup)
|
||||
imp.reload(OP_setup_curve_path)
|
||||
imp.reload(OP_animate_path)
|
||||
imp.reload(OP_expand_cycle_step)
|
||||
|
@ -20,6 +23,9 @@ if 'bpy' in locals():
|
|||
imp.reload(OP_world_copy_paste)
|
||||
imp.reload(panels)
|
||||
else:
|
||||
from . import properties
|
||||
from . import preferences
|
||||
from . import OP_setup
|
||||
from . import OP_setup_curve_path
|
||||
from . import OP_animate_path
|
||||
from . import OP_expand_cycle_step
|
||||
|
@ -28,89 +34,30 @@ else:
|
|||
from . import panels
|
||||
|
||||
import bpy
|
||||
class UAC_PGT_settings(bpy.types.PropertyGroup) :
|
||||
## HIDDEN to hide the animatable dot thing
|
||||
path_to_follow : bpy.props.PointerProperty(type=bpy.types.Object,
|
||||
name="Path", description="Curve object used")
|
||||
|
||||
gnd : bpy.props.PointerProperty(type=bpy.types.Object,
|
||||
name="Ground", description="Choose the ground object to use")
|
||||
|
||||
expand_on_selected_bones : bpy.props.BoolProperty(
|
||||
name="On selected", description="Expand on selected bones",
|
||||
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(
|
||||
name="Start Frame", description="Starting frame for animation path",
|
||||
default=100,
|
||||
min=0, max=2**31-1, soft_min=0, soft_max=2**31-1, step=1, options={'HIDDEN'})#, subtype='PIXEL'
|
||||
|
||||
|
||||
class UAC_addon_prefs(bpy.types.AddonPreferences):
|
||||
## can be just __name__ if prefs are in the __init__ mainfile
|
||||
# Else need the splitext '__name__ = addonname.subfile' (or use a static name)
|
||||
bl_idname = __name__.split('.')[0] # or with: os.path.splitext(__name__)[0]
|
||||
|
||||
# some_bool_prop to display in the addon pref
|
||||
debug : bpy.props.BoolProperty(
|
||||
name='Debug',
|
||||
description="Enable Debug prints",
|
||||
default=False)
|
||||
|
||||
tgt_bone : bpy.props.StringProperty(
|
||||
name="Constrained Pose bone name", default='root',
|
||||
description="name of the bone that suppose to hold the constraint")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
## some 2.80 UI options
|
||||
# layout.use_property_split = True
|
||||
# flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
||||
# layout = flow.column()
|
||||
|
||||
layout.label(text='Create')
|
||||
layout.prop(self, "tgt_bone")
|
||||
|
||||
layout.prop(self, "debug")
|
||||
|
||||
### --- REGISTER ---
|
||||
|
||||
classes=(
|
||||
UAC_PGT_settings,
|
||||
UAC_addon_prefs,
|
||||
mods = (
|
||||
properties,
|
||||
preferences,
|
||||
OP_setup,
|
||||
OP_setup_curve_path,
|
||||
OP_animate_path,
|
||||
OP_expand_cycle_step,
|
||||
OP_snap_contact,
|
||||
OP_world_copy_paste,
|
||||
panels,
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
OP_setup_curve_path.register()
|
||||
OP_animate_path.register()
|
||||
OP_expand_cycle_step.register()
|
||||
OP_snap_contact.register()
|
||||
OP_world_copy_paste.register()
|
||||
panels.register()
|
||||
|
||||
# if not bpy.app.background:
|
||||
#register_keymaps()
|
||||
bpy.types.Scene.anim_cycle_settings = bpy.props.PointerProperty(type = UAC_PGT_settings)
|
||||
if bpy.app.background:
|
||||
return
|
||||
for module in mods:
|
||||
module.register()
|
||||
|
||||
def unregister():
|
||||
# if not bpy.app.background:
|
||||
#unregister_keymaps()
|
||||
panels.unregister()
|
||||
OP_world_copy_paste.unregister()
|
||||
OP_snap_contact.unregister()
|
||||
OP_expand_cycle_step.unregister()
|
||||
OP_animate_path.unregister()
|
||||
OP_setup_curve_path.unregister()
|
||||
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
del bpy.types.Scene.anim_cycle_settings
|
||||
if bpy.app.background:
|
||||
return
|
||||
for module in reversed(mods):
|
||||
module.unregister()
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
|
23
fn.py
23
fn.py
|
@ -1,6 +1,7 @@
|
|||
import bpy
|
||||
import re
|
||||
import numpy as np
|
||||
from mathutils import Matrix, Vector, Color
|
||||
|
||||
def get_addon_prefs():
|
||||
'''
|
||||
|
@ -28,7 +29,7 @@ def helper(name: str = '') -> str:
|
|||
|
||||
def convertAttr(Attr):
|
||||
'''Convert given value to a Json serializable format'''
|
||||
if type(Attr) in [type(mathutils.Vector()),type(mathutils.Color())]:
|
||||
if type(Attr) in [type(Vector()),type(Color())]:
|
||||
if len(Attr) == 3:
|
||||
return([Attr[0],Attr[1],Attr[2]])
|
||||
elif len(Attr) == 2:
|
||||
|
@ -37,7 +38,7 @@ def convertAttr(Attr):
|
|||
return([Attr[0]])
|
||||
elif len(Attr) == 4:
|
||||
return([Attr[0],Attr[1],Attr[2],Attr[3]])
|
||||
elif type(Attr) == type(mathutils.Matrix()):
|
||||
elif type(Attr) == type(Matrix()):
|
||||
return (np.matrix(Attr).tolist())
|
||||
else:
|
||||
return(Attr)
|
||||
|
@ -162,6 +163,7 @@ def set_expanded_action(obj):
|
|||
|
||||
|
||||
def get_curve_length(ob):
|
||||
'''Get a curve object, return a float representing world space length'''
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
obeval = ob.evaluated_get(dg)#.copy()
|
||||
baked_me = obeval.to_mesh(preserve_all_data_layers=False, depsgraph=dg)
|
||||
|
@ -173,4 +175,19 @@ def get_curve_length(ob):
|
|||
total_length += ((mat @ baked_me.vertices[i-1].co) - (mat @ baked_me.vertices[i].co)).length
|
||||
|
||||
print("total_length", total_length)#Dbg
|
||||
return total_length
|
||||
return total_length
|
||||
|
||||
|
||||
def scale_matrix_from_vector(scale):
|
||||
# recreate a neutral mat scale
|
||||
matscale_x = Matrix.Scale(scale[0], 4,(1,0,0))
|
||||
matscale_y = Matrix.Scale(scale[1], 4,(0,1,0))
|
||||
matscale_z = Matrix.Scale(scale[2], 4,(0,0,1))
|
||||
matscale = matscale_x @ matscale_y @ matscale_z
|
||||
return matscale
|
||||
|
||||
def compose_matrix(loc, rot, scale):
|
||||
loc_mat = Matrix.Translation(loc)
|
||||
rot_mat = rot.to_matrix().to_4x4()
|
||||
scale_mat = scale_matrix_from_vector(scale)
|
||||
return loc_mat @ rot_mat @ scale_mat
|
||||
|
|
29
panels.py
29
panels.py
|
@ -5,15 +5,22 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
|
|||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Anim"
|
||||
bl_label = "Walk Cycle anim"
|
||||
bl_label = "Walk Cycle Anim"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
if not context.scene.anim_cycle_settings.path_to_follow:
|
||||
settings = context.scene.anim_cycle_settings
|
||||
if not settings.path_to_follow:
|
||||
layout.operator('anim.create_curve_path', text='Create Curve at Root Position', icon='CURVE_BEZCURVE')
|
||||
|
||||
# need to know root orientation forward)
|
||||
## know direction to evaluate feet moves
|
||||
## Define Constraint axis (depend on root orientation)
|
||||
layout.prop(settings, "forward_axis")
|
||||
layout.operator("uac.autoset_axis", text='Auto-Set Axis')
|
||||
#-# path and ground objects
|
||||
layout.prop_search(context.scene.anim_cycle_settings, "path_to_follow", context.scene, "objects")
|
||||
layout.prop_search(context.scene.anim_cycle_settings, "gnd", context.scene, "objects")
|
||||
layout.prop_search(settings, "path_to_follow", context.scene, "objects")
|
||||
layout.prop_search(settings, "gnd", context.scene, "objects")
|
||||
|
||||
|
||||
prefs = fn.get_addon_prefs()
|
||||
|
@ -28,6 +35,7 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
|
|||
if follow and follow.target:
|
||||
layout.label(text=f'{pb.name} -> {follow.target.name}', icon='CON_FOLLOWPATH')
|
||||
constrained = True
|
||||
## Put this in a setting popup or submenu
|
||||
|
||||
if not constrained:
|
||||
## Créer automatiquement le follow path TODO et l'anim de base
|
||||
|
@ -36,18 +44,19 @@ 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')
|
||||
|
||||
|
||||
# row=layout.row()
|
||||
layout.prop(context.scene.anim_cycle_settings, "start_frame", text='Start')
|
||||
layout.operator('anim.animate_path', text='Animate Path (select foot)', icon='ANIM')
|
||||
col=layout.column()
|
||||
col.prop(settings, "start_frame", text='Start')
|
||||
# col.prop(settings, "foot_axis", text='Foot Axis')
|
||||
col.operator('anim.animate_path', text='Animate Path (select foot)', icon='ANIM')
|
||||
|
||||
|
||||
row=layout.row()
|
||||
row.operator('anim.adjust_animation_length', icon='MOD_TIME')
|
||||
|
||||
## Bake cycle (on selected)
|
||||
row=layout.row()
|
||||
row.prop(context.scene.anim_cycle_settings, "linear", text='Linear')
|
||||
row.prop(context.scene.anim_cycle_settings, "expand_on_selected_bones")
|
||||
row.prop(settings, "linear", text='Linear')
|
||||
row.prop(settings, "expand_on_selected_bones")
|
||||
layout.operator('anim.bake_cycle_and_step', text='Bake key and step path', icon='SHAPEKEY_DATA')
|
||||
|
||||
# Pin feet
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import bpy
|
||||
|
||||
class UAC_addon_prefs(bpy.types.AddonPreferences):
|
||||
## can be just __name__ if prefs are in the __init__ mainfile
|
||||
# Else need the splitext '__name__ = addonname.subfile' (or use a static name)
|
||||
bl_idname = __name__.split('.')[0] # or with: os.path.splitext(__name__)[0]
|
||||
|
||||
# some_bool_prop to display in the addon pref
|
||||
debug : bpy.props.BoolProperty(
|
||||
name='Debug',
|
||||
description="Enable Debug prints",
|
||||
default=False)
|
||||
|
||||
tgt_bone : bpy.props.StringProperty(
|
||||
name="Constrained Pose bone name", default='world',
|
||||
description="name of the bone that suppose to hold the constraint")
|
||||
|
||||
""" default_forward_axis : bpy.props.EnumProperty(
|
||||
name='Forward Axis',
|
||||
default='TRACK_NEGATIVE_Y', # Modifier default is FORWARD_X
|
||||
description='Axis that points forward along the path',
|
||||
items=(
|
||||
('FORWARD_X', 'X', ''),
|
||||
('FORWARD_Y', 'Y', ''),
|
||||
('FORWARD_Z', 'Z', ''),
|
||||
('TRACK_NEGATIVE_X', '-X', ''),
|
||||
('TRACK_NEGATIVE_Y', '-Y', ''),
|
||||
('TRACK_NEGATIVE_Z', '-Z', ''),
|
||||
),
|
||||
)
|
||||
"""
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
|
||||
box = layout.box()
|
||||
box.label(text='Curve creation parameters:')
|
||||
box.prop(self, "tgt_bone")
|
||||
# box.prop(self, "default_forward_axis", text='Default Forward Axis')
|
||||
|
||||
layout.prop(self, "debug")
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(UAC_addon_prefs)
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(UAC_addon_prefs)
|
|
@ -0,0 +1,83 @@
|
|||
import bpy
|
||||
from bpy.props import (
|
||||
IntProperty,
|
||||
BoolProperty,
|
||||
StringProperty,
|
||||
FloatProperty,
|
||||
EnumProperty,
|
||||
)
|
||||
|
||||
class UAC_PG_settings(bpy.types.PropertyGroup) :
|
||||
## HIDDEN to hide the animatable dot thing
|
||||
path_to_follow : bpy.props.PointerProperty(type=bpy.types.Object,
|
||||
name="Path", description="Curve object used")
|
||||
|
||||
gnd : bpy.props.PointerProperty(type=bpy.types.Object,
|
||||
name="Ground", description="Choose the ground object to use")
|
||||
|
||||
expand_on_selected_bones : bpy.props.BoolProperty(
|
||||
name="On selected", description="Expand on selected bones",
|
||||
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(
|
||||
name="Start Frame", description="Starting frame for animation path",
|
||||
default=100,
|
||||
min=0, max=2**31-1, soft_min=0, soft_max=2**31-1, step=1, options={'HIDDEN'})#, subtype='PIXEL'
|
||||
|
||||
forward_axis : bpy.props.EnumProperty(
|
||||
name='Forward Axis',
|
||||
default='TRACK_NEGATIVE_Y', # Modifier default is FORWARD_X
|
||||
description='Axis of the root bone that point forward',
|
||||
items=(
|
||||
('FORWARD_X', 'X', ''),
|
||||
('FORWARD_Y', 'Y', ''),
|
||||
('FORWARD_Z', 'Z', ''),
|
||||
('TRACK_NEGATIVE_X', '-X', ''),
|
||||
('TRACK_NEGATIVE_Y', '-Y', ''),
|
||||
('TRACK_NEGATIVE_Z', '-Z', ''),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
"""
|
||||
## foot axis not needed (not always aligned with character direction)
|
||||
foot_axis : EnumProperty(
|
||||
name="Foot Axis", description="Foot forward axis",
|
||||
default='Y', options={'HIDDEN', 'SKIP_SAVE'},
|
||||
items=(
|
||||
('X', 'X', '', 0),
|
||||
('Y', 'Y', '', '', 1),
|
||||
('Z', 'Z', '', '', 2),
|
||||
# ('-X', '-X', '', 3),
|
||||
# ('-Y', '-Y', '', '', 4),
|
||||
# ('-Z', '-Z', '', '', 5),
|
||||
))
|
||||
"""
|
||||
# someBool : BoolProperty(
|
||||
# name="", description="",
|
||||
# default=True,
|
||||
# options={'HIDDEN'})
|
||||
|
||||
# keyframe_type : EnumProperty(
|
||||
# name="Keyframe Filter", description="Only jump to defined keyframe type",
|
||||
# default='ALL', options={'HIDDEN', 'SKIP_SAVE'},
|
||||
# items=(
|
||||
# ('ALL', 'All', '', 0), # 'KEYFRAME'
|
||||
# ('KEYFRAME', 'Keyframe', '', 'KEYTYPE_KEYFRAME_VEC', 1),
|
||||
# ('BREAKDOWN', 'Breakdown', '', 'KEYTYPE_BREAKDOWN_VEC', 2),
|
||||
# ('MOVING_HOLD', 'Moving Hold', '', 'KEYTYPE_MOVING_HOLD_VEC', 3),
|
||||
# ('EXTREME', 'Extreme', '', 'KEYTYPE_EXTREME_VEC', 4),
|
||||
# ('JITTER', 'Jitter', '', 'KEYTYPE_JITTER_VEC', 5),
|
||||
# ))
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(UAC_PG_settings)
|
||||
bpy.types.Scene.anim_cycle_settings = bpy.props.PointerProperty(type = UAC_PG_settings)
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(UAC_PG_settings)
|
||||
del bpy.types.Scene.anim_cycle_settings
|
Loading…
Reference in New Issue