auto detect foot and curve edit switch
0.7.0 - auto-detect foot to use for path animation - button to go back and forth between curve edit and armature pose mode - UI revamp showing better separation of tool categoriesmaster
parent
c6a75f25f8
commit
57aae8af75
|
@ -27,17 +27,119 @@ def get_bone_transform_at_frame(b, act, frame):
|
|||
return transform # loc, rot, scale
|
||||
|
||||
|
||||
def has_extremes(b, act=None):
|
||||
'''tell if a bone has extreme marked '''
|
||||
if not act:
|
||||
act = fn.get_obj_action(b.id_data)
|
||||
if not act:
|
||||
return False
|
||||
|
||||
for fcu in act.fcurves:
|
||||
if fcu.data_path.split('"')[1] == b.name: # name of the bone
|
||||
if [k for k in fcu.keyframe_points if k.type == 'EXTREME']:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_extreme_range(ob, act, b):
|
||||
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
|
||||
return end_frame - start_frame
|
||||
|
||||
def find_best_foot(ob):
|
||||
'''Get an armature object and do some wizardry to return best match for foot bone'''
|
||||
|
||||
b = bpy.context.active_pose_bone
|
||||
|
||||
act = fn.get_obj_action(ob)
|
||||
if not act:
|
||||
return ('ERROR', f'No action active on {ob.name}')
|
||||
|
||||
# use original action as ref
|
||||
if '_expanded' in act.name:
|
||||
base_act_name = act.name.split('_expanded')[0]
|
||||
base_act = bpy.data.actions.get(base_act_name)
|
||||
if base_act:
|
||||
act = base_act
|
||||
print(f'Using for action {base_act_name} as reference')
|
||||
else:
|
||||
print(f'No base action found (searching for {base_act_name})')
|
||||
|
||||
if 'foot' in b.name.lower():
|
||||
# if best is selected
|
||||
return b
|
||||
|
||||
## auto detect reference foot
|
||||
ref = 'foot.R'
|
||||
target_bones = [pb for pb in ob.pose.bones if not b.name.lower().startswith(('mch', 'org', 'def'))\
|
||||
and has_extremes(pb, act=act)]
|
||||
|
||||
if not target_bones:
|
||||
return ('ERROR', f'No keys in action "{act.name}" are marked as Extreme (red keys)')
|
||||
|
||||
target_bones.sort(key=lambda x: fn.fuzzy_match_ratio(ref, x.name))
|
||||
# print('target_bones: ', [b.name for b in target_bones]) #Dbg
|
||||
|
||||
# analyse best bone for contact length
|
||||
b = target_bones[0]
|
||||
|
||||
print(f'auto-detected best bone as {b.name}')
|
||||
|
||||
# determine contact chain length
|
||||
|
||||
flip_name = fn.get_flipped_name(b.name)
|
||||
flipped = next((b for b in target_bones if b.name == flip_name), None)
|
||||
|
||||
if not flipped:
|
||||
print(f'No flipped name found (using "{flip_name}")')
|
||||
return b
|
||||
|
||||
flipped_contact_range = get_extreme_range(ob, act, flipped)
|
||||
if not flipped_contact_range:
|
||||
return b
|
||||
|
||||
bone_contact_range = get_extreme_range(ob, act, b)
|
||||
if bone_contact_range:
|
||||
if bone_contact_range < flipped_contact_range:
|
||||
return flipped
|
||||
else:
|
||||
return ('ERROR', f'No Extreme (red keys) on bone "{b.name}" for action "{act.name}"')
|
||||
|
||||
return b
|
||||
|
||||
|
||||
def anim_path_from_translate():
|
||||
'''Calculate step size from selected foot and forward axis and animate curve'''
|
||||
print(fn.helper())
|
||||
|
||||
ob = bpy.context.object
|
||||
|
||||
# found curve through constraint
|
||||
b = bpy.context.active_pose_bone
|
||||
|
||||
# if not 'foot' in b.bone.name:
|
||||
# return ('ERROR', 'No "foot" in active bone name\n-> Select foot that has the most reliable contact')
|
||||
settings = bpy.context.scene.anim_cycle_settings
|
||||
axis = settings.forward_axis
|
||||
|
||||
|
@ -68,12 +170,16 @@ def anim_path_from_translate():
|
|||
else:
|
||||
print(f'No base action found (searching for {base_act_name})')
|
||||
|
||||
# CHANGE - retiré le int de la frame
|
||||
# keyframes = [int(k.co[0]) for fcu in act.fcurves for k in fcu.keyframe_points]
|
||||
# b = bpy.context.active_pose_bone
|
||||
b = find_best_foot(ob)
|
||||
if isinstance(b, tuple):
|
||||
return b # return error and message
|
||||
print('best: ', b.name)
|
||||
|
||||
# if not 'foot' in b.bone.name:
|
||||
# return ('ERROR', 'No "foot" in active bone name\n-> Select foot that has the most reliable contact')
|
||||
|
||||
## calculate offset from bones by evaluating distance at extremes
|
||||
|
||||
|
||||
# fcurve parsing:
|
||||
# name : fcu.data_path.split('"')[1] (bone_name)
|
||||
# properties: fcu.data_path.split('.')[-1] ('location', rotation_euler)
|
||||
|
@ -81,7 +187,6 @@ def anim_path_from_translate():
|
|||
|
||||
## 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
|
||||
|
||||
|
@ -143,7 +248,6 @@ def anim_path_from_translate():
|
|||
|
||||
print('root vec : ', root_axis_vec)
|
||||
print('start loc: ', start_loc)
|
||||
|
||||
|
||||
## get distance on forward axis
|
||||
move_val = (intersect_line_plane(start_loc, start_loc + root_axis_vec, end_loc, root_axis_vec) - start_loc).length
|
||||
|
@ -198,9 +302,9 @@ def anim_path_from_translate():
|
|||
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 = "Use most representative 'in contact' feet of the cycle\
|
||||
\nSelect foot bone to use as reference\
|
||||
\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"}
|
||||
|
||||
|
|
|
@ -194,10 +194,112 @@ class UAC_OT_snap_curve_to_ground(bpy.types.Operator):
|
|||
return {"CANCELLED"}
|
||||
return {"FINISHED"}
|
||||
|
||||
class UAC_OT_edit_curve(bpy.types.Operator):
|
||||
bl_idname = "uac.edit_curve"
|
||||
bl_label = "Edit Curve"
|
||||
bl_description = "Edit curve used as constraint for foot"
|
||||
bl_options = {"REGISTER", "INTERNAL", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'ARMATURE'
|
||||
|
||||
def execute(self, context):
|
||||
b = context.active_pose_bone
|
||||
curve = None
|
||||
|
||||
# test with selected bone
|
||||
if b and b.constraints:
|
||||
curve = next((c.target for c in b.constraints if c.type == 'FOLLOW_PATH' and c.target), None)
|
||||
|
||||
# get from 'root' bone
|
||||
if not curve:
|
||||
curve, _const = fn.get_follow_curve_from_armature(context.object)
|
||||
if isinstance(curve, str):
|
||||
self.report({curve}, _const)
|
||||
if curve == 'ERROR':
|
||||
return {"CANCELLED"}
|
||||
|
||||
# set mode to object set curve as active and go Edit
|
||||
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
curve.select_set(True)
|
||||
context.view_layer.objects.active = curve
|
||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False) # EDIT_CURVE
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
class UAC_OT_set_choice_id(bpy.types.Operator):
|
||||
bl_idname = "uac.set_choice_id"
|
||||
bl_label = "Chosen ID"
|
||||
bl_description = "Set passed id to a custom prop in window manager"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
idx : bpy.props.IntProperty(default=0, options={'SKIP_SAVE'})
|
||||
|
||||
def execute(self, context):
|
||||
context.window_manager['back_to_armature_idx_prop'] = self.idx
|
||||
return {"FINISHED"}
|
||||
|
||||
class UAC_OT_object_from_curve(bpy.types.Operator):
|
||||
bl_idname = "uac.object_from_curve"
|
||||
bl_label = "Back To Armature"
|
||||
bl_description = "Go in armature pose mode from current curve"
|
||||
bl_options = {"REGISTER", "INTERNAL", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'CURVE'
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.armatures = []
|
||||
curve = context.object
|
||||
for o in context.scene.objects:
|
||||
if o.type != 'ARMATURE':
|
||||
continue
|
||||
for pb in o.pose.bones:
|
||||
for c in pb.constraints:
|
||||
if c.type == 'FOLLOW_PATH' and c.target and c.target == curve:
|
||||
self.armatures.append((o, pb))
|
||||
break
|
||||
|
||||
if not self.armatures:
|
||||
self.report({'ERROR'}, 'No armature using this curve found')
|
||||
return {"CANCELLED"}
|
||||
|
||||
if len(self.armatures) > 1:
|
||||
# context.window_manager['back_to_armature_idx_prop']
|
||||
# return context.window_manager.invoke_props_dialog(self, width=450) # execute on ok
|
||||
return context.window_manager.invoke_props_popup(self, event) # execute on change
|
||||
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
for i, item in enumerate(self.armatures):
|
||||
arm, pb = item
|
||||
layout.operator('uac.set_choice_id', text=f'{arm.name} > {pb.name}', icon='OUTLINER_OB_ARMATURE').idx = i
|
||||
|
||||
def execute(self, context):
|
||||
if len(self.armatures) > 1:
|
||||
# use user chosen index
|
||||
obj, pb = self.armatures[context.window_manager['back_to_armature_idx_prop']]
|
||||
else:
|
||||
obj, pb = self.armatures[0]
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
obj.select_set(True)
|
||||
context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='POSE', toggle=False)
|
||||
self.report({'INFO'}, f'Back to pose mode {obj.name} (constraint on {pb.name})')
|
||||
return {"FINISHED"}
|
||||
|
||||
classes=(
|
||||
UAC_OT_create_curve_path,
|
||||
UAC_OT_create_follow_path,
|
||||
UAC_OT_snap_curve_to_ground,
|
||||
UAC_OT_edit_curve,
|
||||
UAC_OT_set_choice_id,
|
||||
UAC_OT_object_from_curve, # use set_choice_id is used to set an index in object_from_curve pop up menu
|
||||
)
|
||||
|
||||
def register():
|
||||
|
|
|
@ -56,6 +56,11 @@ Bonus:
|
|||
|
||||
## Changelog:
|
||||
|
||||
0.7.0
|
||||
|
||||
- auto-detect foot to use for path animation
|
||||
- button to go back and forth between curve edit and armature pose mode
|
||||
- UI revamp showing better separation of tool categories
|
||||
|
||||
0.6.0
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
bl_info = {
|
||||
"name": "Unfold Anim Cycle",
|
||||
"description": "Anim tools to develop walk/run cycles along a curve",
|
||||
"author": "Samuel Bernou",
|
||||
"version": (0, 6, 0),
|
||||
"version": (0, 7, 0),
|
||||
"blender": (3, 0, 0),
|
||||
"location": "View3D",
|
||||
"warning": "WIP",
|
||||
"doc_url": "https://gitlab.com/autour-de-minuit/blender/unfold_anim_cycle",
|
||||
"category": "Object"}
|
||||
|
||||
# from . import other_file
|
||||
|
||||
if 'bpy' in locals():
|
||||
import importlib as imp
|
||||
imp.reload(properties)
|
||||
|
|
63
fn.py
63
fn.py
|
@ -54,17 +54,21 @@ def get_gnd():
|
|||
|
||||
|
||||
def get_follow_curve_from_armature(arm):
|
||||
"""Return curve and constraint or a tuple of string ('error', 'message')
|
||||
"""
|
||||
name = get_root_name()
|
||||
"""Return curve and constraint or a tuple of string ('error', 'message')"""
|
||||
|
||||
name = get_root_name()
|
||||
|
||||
parents = []
|
||||
const = False
|
||||
const = None
|
||||
# root = b.id_data.pose.bones.get(name)
|
||||
root = arm.pose.bones.get(name)
|
||||
if not root:
|
||||
return ('ERROR', f'No bone named {name}')
|
||||
|
||||
for c in root.constraints:
|
||||
if c.type == 'FOLLOW_PATH':
|
||||
const = c
|
||||
""" # old method compatible with child of (using animation on parented object)
|
||||
if c.type == 'CHILD_OF':
|
||||
print(f'found child-of on {name}')
|
||||
if c.target:
|
||||
|
@ -77,7 +81,7 @@ def get_follow_curve_from_armature(arm):
|
|||
print('INFO', f'follow_path found on "{p.name}" parent object')
|
||||
const = c
|
||||
break
|
||||
|
||||
"""
|
||||
if not const:
|
||||
return ('ERROR', 'No constraints founds')
|
||||
|
||||
|
@ -379,8 +383,56 @@ def get_offset_fcu(act=None):
|
|||
return fcus[0]
|
||||
|
||||
|
||||
def fuzzy_match(s1, s2, tol=0.8, case_sensitive=False):
|
||||
'''Tell if two strings are similar using a similarity ratio (0 to 1) value passed as third arg'''
|
||||
from difflib import SequenceMatcher
|
||||
# can also use difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)
|
||||
if case_sensitive:
|
||||
similarity = SequenceMatcher(None, s1, s2)
|
||||
else:
|
||||
similarity = SequenceMatcher(None, s1.lower(), s2.lower())
|
||||
return similarity.ratio() > tol
|
||||
|
||||
def fuzzy_match_ratio(s1, s2, case_sensitive=False):
|
||||
'''Tell how much two passed strings are similar 1.0 being exactly similar'''
|
||||
from difflib import SequenceMatcher
|
||||
if case_sensitive:
|
||||
similarity = SequenceMatcher(None, s1, s2)
|
||||
else:
|
||||
similarity = SequenceMatcher(None, s1.lower(), s2.lower())
|
||||
return similarity.ratio()
|
||||
|
||||
def flip_suffix_side_name(name):
|
||||
return re.sub(r'([-._])(R|L)', lambda x: x.group(1) + ('L' if x.group(2) == 'R' else 'R'), name)
|
||||
|
||||
def get_flipped_name(name):
|
||||
import re
|
||||
|
||||
def flip(match, start=False):
|
||||
if not match.group(1) or not match.group(2):
|
||||
return
|
||||
|
||||
sides = {
|
||||
'R' : 'L',
|
||||
'r' : 'l',
|
||||
'L' : 'R',
|
||||
'l' : 'r',
|
||||
}
|
||||
|
||||
if start:
|
||||
side, sep = match.groups()
|
||||
return sides[side] + sep
|
||||
else:
|
||||
sep, side, num = match.groups()
|
||||
return sep + sides[side] + (num or '')
|
||||
|
||||
start_reg = re.compile(r'^(l|r)([-_.])', flags=re.I)
|
||||
|
||||
if start_reg.match(name):
|
||||
return start_reg.sub(lambda x: flip(x, True), name)
|
||||
|
||||
else:
|
||||
return re.sub(r'([-_.])(l|r)(\.\d+)?$', flip, name, flags=re.I)
|
||||
|
||||
### --- context manager - store / restore
|
||||
|
||||
|
@ -405,3 +457,4 @@ class attr_set():
|
|||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
for prop, attr, old_val in self.store:
|
||||
setattr(prop, attr, old_val)
|
||||
|
||||
|
|
86
panels.py
86
panels.py
|
@ -9,7 +9,11 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
|
|||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
prefs = fn.get_addon_prefs()
|
||||
ob = context.object
|
||||
|
||||
settings = context.scene.anim_cycle_settings
|
||||
tweak = settings.tweak
|
||||
# need to know root orientation forward)
|
||||
## know direction to evaluate feet moves
|
||||
## Define Constraint axis (depend on root orientation)
|
||||
|
@ -20,57 +24,67 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
|
|||
row.prop(settings, "forward_axis", text='')
|
||||
layout.operator("uac.autoset_axis", text='Auto-Set Axis')
|
||||
|
||||
box = layout.box()
|
||||
if not settings.path_to_follow:
|
||||
box.operator('anim.create_curve_path', text='Create Curve at Root Position', icon='CURVE_BEZCURVE')
|
||||
layout.operator('anim.create_curve_path', text='Create Curve at Root Position', icon='CURVE_BEZCURVE')
|
||||
|
||||
#-# path and ground objects
|
||||
box.prop_search(settings, "path_to_follow", context.scene, "objects")
|
||||
box.prop_search(settings, "gnd", context.scene, "objects")
|
||||
box = layout.box()
|
||||
expand_icon = 'TRIA_DOWN' if tweak else 'TRIA_RIGHT'
|
||||
box.prop(settings, 'tweak', text='Curve Options', icon=expand_icon)
|
||||
if tweak:
|
||||
#-# path and ground objects
|
||||
box.prop_search(settings, "path_to_follow", context.scene, "objects")
|
||||
box.prop_search(settings, "gnd", context.scene, "objects")
|
||||
|
||||
row = box.row()
|
||||
row.operator('anim.snap_curve_to_ground', text='Snap curve to ground', icon='SNAP_ON')
|
||||
row.active = bool(settings.gnd)
|
||||
|
||||
prefs = fn.get_addon_prefs()
|
||||
ob = context.object
|
||||
|
||||
# Determine if already has a constraint (a bit too much condition in a panel...)
|
||||
constrained = False
|
||||
if ob and ob.type == 'ARMATURE':
|
||||
pb = ob.pose.bones.get(prefs.tgt_bone)
|
||||
if pb:
|
||||
follow = pb.constraints.get('Follow Path')
|
||||
if follow and follow.target:
|
||||
box.label(text=f'{pb.name} -> {follow.target.name}', icon='CON_FOLLOWPATH')
|
||||
constrained = True
|
||||
## Put this in a setting popup or submenu
|
||||
row = box.row()
|
||||
row.operator('anim.snap_curve_to_ground', text='Snap curve to ground', icon='SNAP_ON')
|
||||
row.active = bool(settings.gnd)
|
||||
|
||||
|
||||
# Determine if already has a constraint (a bit too much condition in a panel...)
|
||||
constrained = False
|
||||
if ob:
|
||||
if ob.type == 'ARMATURE':
|
||||
pb = ob.pose.bones.get(prefs.tgt_bone)
|
||||
if pb:
|
||||
follow = pb.constraints.get('Follow Path')
|
||||
if follow and follow.target:
|
||||
box.label(text=f'{pb.name} -> {follow.target.name}', icon='CON_FOLLOWPATH')
|
||||
constrained = True
|
||||
## Put this in a setting popup or submenu
|
||||
# if context.mode == 'POSE':
|
||||
if not constrained:
|
||||
box.operator('anim.create_follow_path', text='Add follow path constraint', icon='CON_FOLLOWPATH')
|
||||
box.operator('uac.edit_curve', text='Edit Curve', icon='OUTLINER_DATA_CURVE') # FORCE_CURVE
|
||||
|
||||
elif ob.type == 'CURVE':
|
||||
if context.mode in ('OBJECT', 'EDIT_CURVE') \
|
||||
and settings.path_to_follow \
|
||||
and ob == settings.path_to_follow:
|
||||
box.operator('uac.object_from_curve', text='Back To Object', icon='LOOP_BACK')
|
||||
|
||||
if not constrained:
|
||||
## Créer automatiquement le follow path TODO et l'anim de base
|
||||
box.operator('anim.create_follow_path', text='Add follow path constraint', icon='CON_FOLLOWPATH')
|
||||
|
||||
|
||||
|
||||
col=layout.column()
|
||||
box = layout.box()
|
||||
col=box.column()
|
||||
col.label(text='Motion:')
|
||||
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')
|
||||
|
||||
col.operator('anim.animate_path', text='Animate Forward Motion', icon='ANIM')
|
||||
|
||||
row=layout.row()
|
||||
row.operator('anim.adjust_animation_length', icon='MOD_TIME')
|
||||
row=col.row()
|
||||
row.operator('anim.adjust_animation_length', text='Adjust Forward Speed', icon='MOD_TIME')
|
||||
|
||||
## Bake cycle (on selected)
|
||||
row=layout.row()
|
||||
box = layout.box()
|
||||
col=box.column()
|
||||
col.label(text='Keys:')
|
||||
row=col.row()
|
||||
row.prop(settings, "linear", text='Linear')
|
||||
row.prop(settings, "expand_on_selected_bones")
|
||||
|
||||
txt = 'Bake keys' if settings.linear else 'Bake keys and step path'
|
||||
layout.operator('anim.bake_cycle_and_step', text=txt, icon='SHAPEKEY_DATA')
|
||||
col.operator('anim.bake_cycle_and_step', text=txt, icon='SHAPEKEY_DATA')
|
||||
|
||||
# Pin feet
|
||||
layout.operator('anim.pin_feets', text='Pin feets', icon='PINNED')
|
||||
col.operator('anim.pin_feets', text='Pin feets', icon='PINNED')
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class UAC_addon_prefs(bpy.types.AddonPreferences):
|
|||
0 = no prints\n\
|
||||
1 = basic\n\
|
||||
2 = full prints",
|
||||
default=1)
|
||||
default=0)
|
||||
|
||||
tgt_bone : bpy.props.StringProperty(
|
||||
name="Constrained Pose bone name", default='world',
|
||||
|
|
|
@ -9,6 +9,11 @@ from bpy.props import (
|
|||
|
||||
class UAC_PG_settings(bpy.types.PropertyGroup) :
|
||||
## HIDDEN to hide the animatable dot thing
|
||||
|
||||
tweak : bpy.props.BoolProperty(
|
||||
name="Tweak", description="Show Tweaking options",
|
||||
default=False, options={'HIDDEN'})
|
||||
|
||||
path_to_follow : bpy.props.PointerProperty(type=bpy.types.Object,
|
||||
name="Path", description="Curve object used")
|
||||
|
||||
|
@ -26,13 +31,13 @@ class UAC_PG_settings(bpy.types.PropertyGroup) :
|
|||
|
||||
start_frame : bpy.props.IntProperty(
|
||||
name="Start Frame", description="Starting frame for animation path",
|
||||
default=100,
|
||||
default=101,
|
||||
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='FORWARD_Z', # Modifier default is FORWARD_X (should be TRACK_NEGATIVE_Y for a good rig)
|
||||
description='Local axis of the "root" bone that point forward',
|
||||
description='Local axis of the "root" bone that point forward in rest pose',
|
||||
items=(
|
||||
('FORWARD_X', 'X', ''),
|
||||
('FORWARD_Y', 'Y', ''),
|
||||
|
|
Loading…
Reference in New Issue