diff --git a/OP_animate_path.py b/OP_animate_path.py index ffe6547..c6c8db6 100644 --- a/OP_animate_path.py +++ b/OP_animate_path.py @@ -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': diff --git a/OP_setup.py b/OP_setup.py new file mode 100644 index 0000000..01cd1d7 --- /dev/null +++ b/OP_setup.py @@ -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) + diff --git a/OP_setup_curve_path.py b/OP_setup_curve_path.py index d67186d..4148e52 100644 --- a/OP_setup_curve_path.py +++ b/OP_setup_curve_path.py @@ -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"} diff --git a/README.md b/README.md index a471c98..84f5737 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ Sidebar > Anim > unfold anim cycle ## Changelog: +0.3.3 + +- total wip + 0.3.0 - Rework interface diff --git a/__init__.py b/__init__.py index 0954a21..30fe315 100644 --- a/__init__.py +++ b/__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() diff --git a/fn.py b/fn.py index 102be19..ef7009f 100644 --- a/fn.py +++ b/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 \ No newline at end of file + 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 diff --git a/panels.py b/panels.py index 220d86e..e2a6091 100644 --- a/panels.py +++ b/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 diff --git a/preferences.py b/preferences.py new file mode 100644 index 0000000..bd56c27 --- /dev/null +++ b/preferences.py @@ -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) \ No newline at end of file diff --git a/properties.py b/properties.py new file mode 100644 index 0000000..62297fb --- /dev/null +++ b/properties.py @@ -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 \ No newline at end of file