wip working on path animate

master
Pullusb 2022-03-29 18:46:33 +02:00
parent 62aeddfd49
commit 5a221aa0fe
9 changed files with 402 additions and 117 deletions

View File

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

82
OP_setup.py Normal file
View File

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

View File

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

View File

@ -39,6 +39,10 @@ Sidebar > Anim > unfold anim cycle
## Changelog:
0.3.3
- total wip
0.3.0
- Rework interface

View File

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

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

View File

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

48
preferences.py Normal file
View File

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

83
properties.py Normal file
View File

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