fix errors with translation calc

0.9.0

- fix problem with translation calculation when all keys are marked
- add button to create cycle un tested
- added addon pref button
master
Pullusb 2022-04-13 18:38:03 +02:00
parent b983183504
commit 7031e60376
11 changed files with 289 additions and 58 deletions

View File

@ -1,5 +1,11 @@
# Changelog # Changelog
0.9.0
- fix problem with translation calculation when all keys are marked
- add button to create cycle un tested
- added addon pref button
0.8.0 0.8.0
- Easy jump to previous action - Easy jump to previous action
@ -9,6 +15,7 @@
0.7.1 0.7.1
- customizable panel category name - customizable panel category name
- changed tab category name to `Walk`
- Change generated action name: - Change generated action name:
- `expanded` -> `baked` - `expanded` -> `baked`
- `autogen` -> `pinned` - `autogen` -> `pinned`

View File

@ -139,7 +139,7 @@ def anim_path_from_translate():
ob = bpy.context.object ob = bpy.context.object
debug = fn.get_addon_prefs().debug
settings = bpy.context.scene.anim_cycle_settings settings = bpy.context.scene.anim_cycle_settings
axis = settings.forward_axis axis = settings.forward_axis
@ -187,10 +187,20 @@ def anim_path_from_translate():
## get only fcurves relative to selected bone ## get only fcurves relative to selected bone
b_fcurves = [fcu for fcu in act.fcurves if fcu.data_path.split('"')[1] == b.bone.name] b_fcurves = [fcu for fcu in act.fcurves if fcu.data_path.split('"')[1] == b.bone.name]
print('b_fcurves: ', len(b_fcurves))
start_frame = end_frame = None start_frame = end_frame = None
for fcu in b_fcurves: for fcu in b_fcurves:
# skip problematic keys
if not len(fcu.keyframe_points):
if debug: print(fcu.data_path, fcu.array_index, '>> no keys !')
continue
if all(k.type == 'EXTREME' for k in fcu.keyframe_points):
# True if all are extreme or no keyframe in fcu
if debug: print(fcu.data_path, fcu.array_index, '>> all keys are marked as extremes !')
continue
encountered_marks = False # flag to stop after last extreme of each fcu encountered_marks = False # flag to stop after last extreme of each fcu
for k in fcu.keyframe_points: for k in fcu.keyframe_points:
# if k.select_control_point: # based on selection ? # if k.select_control_point: # based on selection ?
@ -215,7 +225,7 @@ def anim_path_from_translate():
break break
if start_frame is None or end_frame is None: if start_frame is None or end_frame is None:
return ('ERROR', f'No (or not enough) keyframe marked Extreme {ob.name} > {b.name}') return ('ERROR', f'No / All / not enough keyframe marked Extreme {ob.name} > {b.name}')
if start_frame == end_frame: 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') return ('ERROR', f'Only one key detected as extreme (at frame {start_frame}) !\nNeed at least two chained marked keys')
@ -224,6 +234,9 @@ def anim_path_from_translate():
## Find move_val from diff position at start and end frame wihtin character forward axis ## Find move_val from diff position at start and end frame wihtin character forward axis
## FIXME: problem when cycle axis is not Forward compare to character
## apply rotations in real world ? quat_diff = b.matrix_basis.to_quaternion().rotation_difference(b.matrix.to_quaternion())
start_transform = get_bone_transform_at_frame(b, act, start_frame) 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']) start_mat = fn.compose_matrix(start_transform['location'], start_transform['rotation_euler'], start_transform['scale'])
@ -246,12 +259,14 @@ def anim_path_from_translate():
end_loc = (b.bone.matrix_local @ end_mat).to_translation() end_loc = (b.bone.matrix_local @ end_mat).to_translation()
# bpy.context.scene.cursor.location = start_loc # Dbg foot start position # bpy.context.scene.cursor.location = start_loc # Dbg foot start position
if debug:
print('root vec : ', root_axis_vec) print('root vec : ', root_axis_vec)
print('start loc: ', start_loc) print('start loc: ', start_loc)
print('end loc: ', end_loc)
## get distance on forward axis ## 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 move_val = (intersect_line_plane(start_loc, start_loc + root_axis_vec, end_loc, root_axis_vec) - start_loc).length
print('move_val: ', move_val) print('Detected move value: ', move_val)
length = fn.get_curve_length(curve) length = fn.get_curve_length(curve)
@ -297,7 +312,7 @@ def anim_path_from_translate():
# for k in t_fcu.keyframe_points: # for k in t_fcu.keyframe_points:
# k.interpolation = 'CONSTANT' # k.interpolation = 'CONSTANT'
print('end of set_follow_path_anim') if debug: print('end of set_follow_path_anim')
class UAC_OT_animate_path(bpy.types.Operator): class UAC_OT_animate_path(bpy.types.Operator):
bl_idname = "anim.animate_path" bl_idname = "anim.animate_path"
@ -318,6 +333,9 @@ class UAC_OT_animate_path(bpy.types.Operator):
def execute(self, context): def execute(self, context):
# TODO clear previous animation (keys) if there is any # TODO clear previous animation (keys) if there is any
if context.mode == 'OBJECT':
# Go in pose mode
bpy.ops.object.mode_set(mode='POSE')
err = anim_path_from_translate() err = anim_path_from_translate()
if err: if err:

View File

@ -89,7 +89,8 @@ def bake_cycle(on_selection=True):
if debug >= 2: print('keys', len(fcu_kfs)) if debug >= 2: print('keys', len(fcu_kfs))
## expand to end frame ## expand to end frame
end = bpy.context.scene.frame_end # maybe add possibility define target manually # maybe add possibility define target manually ?
end = bpy.context.scene.frame_end + 10 # add a margin
iterations = ((end - last) // offset) + 1 iterations = ((end - last) // offset) + 1
if debug >= 2: print('iterations: ', iterations) if debug >= 2: print('iterations: ', iterations)
for _i in range(int(iterations)): for _i in range(int(iterations)):
@ -185,7 +186,8 @@ def step_path():
class UAC_OT_bake_cycle_and_step(bpy.types.Operator): class UAC_OT_bake_cycle_and_step(bpy.types.Operator):
bl_idname = "anim.bake_cycle_and_step" bl_idname = "anim.bake_cycle_and_step"
bl_label = "Bake key and step path " bl_label = "Bake key and step path "
bl_description = "Bake the key and step the animation path according to those key" bl_description = "Bake the key and step the animation path according to those key\
\n(duplicate to a new 'baked' action)"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@classmethod @classmethod
@ -258,6 +260,8 @@ def pin_down_feets():
to_change_list = [ to_change_list = [
(bpy.context.scene, 'frame_current'), (bpy.context.scene, 'frame_current'),
(bpy.context.scene, 'frame_start', bpy.context.scene.frame_start-100),
(bpy.context.scene, 'frame_end', bpy.context.scene.frame_end+100),
(bpy.context.scene.render, 'use_simplify', True), (bpy.context.scene.render, 'use_simplify', True),
(bpy.context.scene.render, 'simplify_subdivision', 0), (bpy.context.scene.render, 'simplify_subdivision', 0),
] ]
@ -333,7 +337,8 @@ def pin_down_feets():
if debug: print(f'fcurve: {b_name} > {prop}') if debug: print(f'fcurve: {b_name} > {prop}')
for r in reversed(contact_ranges): # iterate in reverse ranges (not really necessary) # iterate in reverse ranges (not really necessary)
for r in reversed(contact_ranges):
print(f'range: {r}') print(f'range: {r}')
first = True first = True
for i in range(r[0], r[1]+1)[::-1]: # start from the end of the range for i in range(r[0], r[1]+1)[::-1]: # start from the end of the range
@ -392,7 +397,7 @@ def pin_down_feets():
class UAC_OT_pin_feets(bpy.types.Operator): class UAC_OT_pin_feets(bpy.types.Operator):
bl_idname = "anim.pin_feets" bl_idname = "anim.pin_feets"
bl_label = "Pin Feets" bl_label = "Pin Feets"
bl_description = "Pin feets on keys marked as extreme" bl_description = "Pin feets on keys marked as extreme\n(duplicate to a new 'pinned' action)"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@classmethod @classmethod
@ -410,6 +415,8 @@ class UAC_OT_pin_feets(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
# --- Quick action management
class UAC_OT_set_action(bpy.types.Operator): class UAC_OT_set_action(bpy.types.Operator):
bl_idname = "uac.set_action" bl_idname = "uac.set_action"
bl_label = "Set action by name" bl_label = "Set action by name"
@ -430,7 +437,7 @@ class UAC_OT_set_action(bpy.types.Operator):
class UAC_OT_step_back_actions(bpy.types.Operator): class UAC_OT_step_back_actions(bpy.types.Operator):
bl_idname = "uac.step_back_actions" bl_idname = "uac.step_back_actions"
bl_label = "Actions Step Back" bl_label = "Actions Step Back"
bl_description = "Step back to a previous action when 'baked' or 'pinned' action are not ok" bl_description = "Step back to a previous action if 'baked' or 'pinned' action are not ok"
bl_options = {"REGISTER", "INTERNAL", "UNDO"} bl_options = {"REGISTER", "INTERNAL", "UNDO"}
@classmethod @classmethod
@ -438,6 +445,10 @@ class UAC_OT_step_back_actions(bpy.types.Operator):
return context.object and context.object.type == 'ARMATURE' return context.object and context.object.type == 'ARMATURE'
def invoke(self, context, event): def invoke(self, context, event):
if context.object.animation_data.use_tweak_mode:
self.report({'ERROR'}, f'Cannot access animation in NLA')
return {"CANCELLED"}
act = context.object.animation_data.action act = context.object.animation_data.action
base_name = act.name.replace('_baked', '').replace('_pinned', '') base_name = act.name.replace('_baked', '').replace('_pinned', '')
base_name = re.sub(r'\.\d{3}', '', base_name) # remove duplicate to search everything that has the same base base_name = re.sub(r'\.\d{3}', '', base_name) # remove duplicate to search everything that has the same base

74
OP_nla_tweak.py Normal file
View File

@ -0,0 +1,74 @@
import bpy
from . import fn
def get_active_nla_strip(all_nla=False):
'''Return object active strip
:all_nla: return first active strip found on all objects > NLA tracks
'''
if all_nla:
objs = [o for o in bpy.data.objects if ob.animation_data]
else:
if not bpy.context.object:
return
objs = [bpy.context.object]
for ob in objs:
if not (anim := ob.animation_data):
continue
for nla in anim.nla_tracks:
for strip in nla.strips:
if strip.active:
print(f'{strip.name} on Track {nla.name}')
return strip
class UAC_OT_nla_key_speed(bpy.types.Operator):
bl_idname = "anim.nla_key_speed"
bl_label = "NLA Key Speed"
bl_description = "Activate animate strip time and Keyframe linear for first and last animation frame"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'ARMATURE'
def execute(self, context):
nla_strip = get_active_nla_strip()
if not nla_strip:
self.report({'ERROR'}, 'no active NLA strip')
return {"CANCELLED"}
# Clear if exists (or just move first and last point ?)
fcu = nla_strip.fcurves.find('strip_time')
if fcu:
for k in reversed(fcu.keyframe_points):
fcu.keyframe_points.remove(k)
nla_strip.use_animated_time = True
nla_strip.strip_time = nla_strip.action_frame_start
nla_strip.keyframe_insert('strip_time', frame=nla_strip.frame_start)
nla_strip.strip_time = nla_strip.action_frame_end
nla_strip.keyframe_insert('strip_time', frame=nla_strip.frame_end)
fcu = nla_strip.fcurves.find('strip_time')
if not fcu:
return {"CANCELLED"}
# Go linear
for k in fcu.keyframe_points:
k.interpolation = 'LINEAR'
return {"FINISHED"}
classes=(
UAC_OT_nla_key_speed,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

View File

@ -2,6 +2,7 @@ import bpy
from mathutils import Vector, Quaternion from mathutils import Vector, Quaternion
from math import sin, cos, radians from math import sin, cos, radians
import numpy as np import numpy as np
from . import fn
## all action needed to setup the walk ## all action needed to setup the walk
@ -73,10 +74,31 @@ class UAC_OT_autoset_axis(bpy.types.Operator):
context.scene.anim_cycle_settings.forward_axis = best_axis context.scene.anim_cycle_settings.forward_axis = best_axis
return {"FINISHED"} return {"FINISHED"}
class UAC_OT_create_cycles_modifiers(bpy.types.Operator):
bl_idname = "uac.create_cycles_modifiers"
bl_label = "Add Cycles Modifiers"
bl_description = "Add cycles modifier on all bones not starting with [mch, org, def]\
\nand that are non-deforming"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'ARMATURE'
def execute(self, context):
fn.create_cycle_modifiers(context.object)
return {"FINISHED"}
classes=(
UAC_OT_autoset_axis,
UAC_OT_create_cycles_modifiers,
)
def register(): def register():
bpy.utils.register_class(UAC_OT_autoset_axis) for cls in classes:
bpy.utils.register_class(cls)
def unregister(): def unregister():
bpy.utils.unregister_class(UAC_OT_autoset_axis) for cls in reversed(classes):
bpy.utils.unregister_class(cls)

View File

@ -178,8 +178,8 @@ class UAC_OT_create_follow_path(bpy.types.Operator):
class UAC_OT_snap_curve_to_ground(bpy.types.Operator): class UAC_OT_snap_curve_to_ground(bpy.types.Operator):
bl_idname = "anim.snap_curve_to_ground" bl_idname = "anim.snap_curve_to_ground"
bl_label = "snap_curve_to_ground" bl_label = "Snap Curve"
bl_description = "Snap Curve" bl_description = "snap curve to ground determine in field"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@classmethod @classmethod

View File

@ -4,7 +4,7 @@ bl_info = {
"name": "Unfold Anim Cycle", "name": "Unfold Anim Cycle",
"description": "Anim tools to develop walk/run cycles along a curve", "description": "Anim tools to develop walk/run cycles along a curve",
"author": "Samuel Bernou", "author": "Samuel Bernou",
"version": (0, 8, 0), "version": (0, 9, 0),
"blender": (3, 0, 0), "blender": (3, 0, 0),
"location": "View3D", "location": "View3D",
"warning": "WIP", "warning": "WIP",
@ -21,6 +21,7 @@ if 'bpy' in locals():
imp.reload(OP_expand_cycle_step) imp.reload(OP_expand_cycle_step)
imp.reload(OP_snap_contact) imp.reload(OP_snap_contact)
imp.reload(OP_world_copy_paste) imp.reload(OP_world_copy_paste)
imp.reload(OP_nla_tweak)
imp.reload(panels) imp.reload(panels)
else: else:
from . import properties from . import properties
@ -31,6 +32,7 @@ else:
from . import OP_expand_cycle_step from . import OP_expand_cycle_step
from . import OP_snap_contact from . import OP_snap_contact
from . import OP_world_copy_paste from . import OP_world_copy_paste
from . import OP_nla_tweak
from . import panels from . import panels
import bpy import bpy
@ -45,22 +47,40 @@ mods = (
OP_expand_cycle_step, OP_expand_cycle_step,
OP_snap_contact, OP_snap_contact,
OP_world_copy_paste, OP_world_copy_paste,
OP_nla_tweak,
panels, panels,
) )
from bpy.app.handlers import persistent
## Not
# @persistent
# def set_target_bone(scene):
# # prefill constrained bone field
# settings = bpy.context.scene.anim_cycle_settings
# if not settings.tgt_bone:
# settings.tgt_bone = fn.get_addon_prefs().tgt_bone
def register(): def register():
if bpy.app.background: if bpy.app.background:
return return
for module in mods: for module in mods:
module.register() module.register()
panels.update_panel(fn.get_addon_prefs(), bpy.context) prefs = fn.get_addon_prefs()
panels.update_panel(prefs, bpy.context)
# bpy.app.handlers.load_post.append(set_target_bone)
def unregister(): def unregister():
if bpy.app.background: if bpy.app.background:
return return
# bpy.app.handlers.load_post.remove(set_target_bone)
for module in reversed(mods): for module in reversed(mods):
module.unregister() module.unregister()

64
fn.py
View File

@ -15,6 +15,18 @@ def get_addon_prefs():
addon_prefs = preferences.addons[addon_name].preferences addon_prefs = preferences.addons[addon_name].preferences
return (addon_prefs) return (addon_prefs)
def open_addon_prefs():
'''Open addon prefs windows with focus on current addon'''
from .__init__ import bl_info
wm = bpy.context.window_manager
wm.addon_filter = 'All'
if not 'COMMUNITY' in wm.addon_support: # reactivate community
wm.addon_support = set([i for i in wm.addon_support] + ['COMMUNITY'])
wm.addon_search = bl_info['name']
bpy.context.preferences.active_section = 'ADDONS'
bpy.ops.preferences.addon_expand(module=__package__)
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
def helper(name: str = '') -> str: def helper(name: str = '') -> str:
'''Return name and arguments from calling obj as str '''Return name and arguments from calling obj as str
:name: - replace definition name by your own str :name: - replace definition name by your own str
@ -232,16 +244,19 @@ def orentation_track_from_vector(input_vector) -> str:
def get_root_name(context=None): def get_root_name(context=None):
'''return name of rig root name''' '''return name of rig root name'''
## auto-detect ?
## TODO root need to be user defined (or at least quickly adjustable)
## need to expose a scene prop from anim_cycle_settings
if context is None: if context is None:
context = bpy.context context = bpy.context
# settings = context.scene.anim_cycle_settings
## maybe use a field on interface
prefs = get_addon_prefs() prefs = get_addon_prefs()
return prefs.tgt_bone
# Auto set bone with pref default value if nothing entered
settings = context.scene.anim_cycle_settings
if not settings.tgt_bone:
settings.tgt_bone = prefs.tgt_bone
## auto-detect mode ?
return settings.tgt_bone
def generate_curve(location=(0,0,0), direction=(1,0,0), name='curve_path', enter_edit=True, context=None): def generate_curve(location=(0,0,0), direction=(1,0,0), name='curve_path', enter_edit=True, context=None):
'''Create curve at provided location and direction vector''' '''Create curve at provided location and direction vector'''
@ -458,3 +473,38 @@ class attr_set():
for prop, attr, old_val in self.store: for prop, attr, old_val in self.store:
setattr(prop, attr, old_val) setattr(prop, attr, old_val)
# fcurve modifiers
def remove_all_cycles_modifier():
for fc in bpy.context.object.animation_data.action.fcurves:
# if fc.data_path.split('"')[1] in selected_names:
for m in reversed(fc.modifiers):
if m.type == 'CYCLES':
fc.modifiers.remove(m)
def create_cycle_modifiers(ob=None):
if not ob:
ob = bpy.context.object
# skip bones that are on protected layers ?
# protected = [i for i, l in enumerate(ob.data.layers_protected) if l]
# for b in ob.data.bones:
# if b.use_deform: # don't affect deform bones
# continue
## b_layers = [i for i, l in enumerate(b.layers) if l]
name_list = [b.name for b in ob.data.bones] # if not b.use_deform (too limiting)
for fc in ob.animation_data.action.fcurves:
if [m for m in fc.modifiers if m.type == 'CYCLES']:
# skip already existing modifier
continue
if not '"' in fc.data_path:
continue
b_name = fc.data_path.split('"')[1]
if b_name.lower().startswith(('mch', 'def', 'org')):
continue
if b_name not in name_list:
continue
print(f'Adding cycle modifier {fc.data_path}')
_m = fc.modifiers.new(type='CYCLES')

View File

@ -19,10 +19,13 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
## Define Constraint axis (depend on root orientation) ## Define Constraint axis (depend on root orientation)
# layout.prop(settings, "forward_axis") # plain prop # layout.prop(settings, "forward_axis") # plain prop
row = layout.row() col = layout.column()
row = col.row()
row.label(text='Forward Axis') row.label(text='Forward Axis')
row.prop(settings, "forward_axis", text='') row.prop(settings, "forward_axis", text='')
layout.operator("uac.autoset_axis", text='Auto-Set Axis') row.operator('uac.open_addon_prefs', icon='PREFERENCES', text='')
col.operator("uac.autoset_axis", text='Auto-Set Axis') # maybe check for fcruve cycle at the end of autoset axis ? (like a check)
col.operator("uac.create_cycles_modifiers", text='Add Cycles Modifiers', icon='GRAPH')
pb = None pb = None
constrained = False constrained = False
@ -47,8 +50,10 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
box = layout.box() box = layout.box()
expand_icon = 'TRIA_DOWN' if tweak else 'TRIA_RIGHT' expand_icon = 'TRIA_DOWN' if tweak else 'TRIA_RIGHT'
box.prop(settings, 'tweak', text='Curve Options', icon=expand_icon) box.prop(settings, 'tweak', text='Curve Options', icon=expand_icon)
if tweak: if tweak:
#-# path and ground objects #-# path and ground objects
box.prop(settings, "tgt_bone", text='Bone')
box.prop_search(settings, "path_to_follow", context.scene, "objects") box.prop_search(settings, "path_to_follow", context.scene, "objects")
box.prop_search(settings, "gnd", context.scene, "objects") box.prop_search(settings, "gnd", context.scene, "objects")
@ -86,7 +91,8 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
## Bake cycle (on selected) ## Bake cycle (on selected)
box = layout.box() box = layout.box()
col=box.column() col=box.column()
col.label(text='Actions:') col.label(text='Action:')
row=col.row() row=col.row()
row.prop(settings, "linear", text='Linear') row.prop(settings, "linear", text='Linear')
row.prop(settings, "expand_on_selected_bones") row.prop(settings, "expand_on_selected_bones")
@ -99,13 +105,13 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
## show a dropdown allowing to go back to unpinned, unbaked version of the animation ## show a dropdown allowing to go back to unpinned, unbaked version of the animation
if ob and ob.type == 'ARMATURE': if ob and ob.type == 'ARMATURE':
if ob.animation_data and ob.animation_data.action: anim = ob.animation_data
if 'baked' in ob.animation_data.action.name or 'pinned' in ob.animation_data.action.name: if anim and anim.action and not anim.use_tweak_mode:
# skipped if in NLA tweak mode because anim.is_property_readonly('action') = True
if 'baked' in anim.action.name or 'pinned' in anim.action.name:
col=box.column()
col.operator('uac.step_back_actions', text='Use Previous Actions', icon= 'ACTION') col.operator('uac.step_back_actions', text='Use Previous Actions', icon= 'ACTION')
class UAC_PT_anim_tools_panel(bpy.types.Panel): class UAC_PT_anim_tools_panel(bpy.types.Panel):
bl_space_type = "VIEW_3D" bl_space_type = "VIEW_3D"
bl_region_type = "UI" bl_region_type = "UI"
@ -137,15 +143,33 @@ class UAC_PT_anim_tools_panel(bpy.types.Panel):
row.operator('anim.world_space_paste_next_frame', text='', icon='FRAME_NEXT').prev = False row.operator('anim.world_space_paste_next_frame', text='', icon='FRAME_NEXT').prev = False
row.scale_x = 2 row.scale_x = 2
class UAC_PT_nla_tools_panel(bpy.types.Panel):
bl_space_type = "NLA_EDITOR"
bl_region_type = "UI"
bl_category = "Strip"
bl_label = "Walk Tools"
def draw(self, context):
layout = self.layout
# layout.label(text='Retime Tools')
layout.operator('anim.nla_key_speed', text='Set Time Keys', icon='TIME')
classes=( classes=(
UAC_PT_walk_cycle_anim_panel, UAC_PT_walk_cycle_anim_panel,
UAC_PT_anim_tools_panel, UAC_PT_anim_tools_panel,
UAC_PT_nla_tools_panel,
)
classes_override_category =(
UAC_PT_walk_cycle_anim_panel,
UAC_PT_anim_tools_panel,
) )
## Addons Preferences Update Panel ## Addons Preferences Update Panel
def update_panel(self, context): def update_panel(self, context):
for cls in classes: for cls in classes_override_category: # classes
try: try:
bpy.utils.unregister_class(cls) bpy.utils.unregister_class(cls)
except: except:

View File

@ -1,6 +1,15 @@
import bpy import bpy
from .panels import update_panel from .panels import update_panel
from . import fn
class UAC_OT_open_addon_prefs(bpy.types.Operator):
bl_idname = "uac.open_addon_prefs"
bl_label = "Open Addon Prefs"
bl_description = "Open user preferences window in addon tab and prefill the search with addon name"
bl_options = {"REGISTER", "INTERNAL"}
def execute(self, context):
fn.open_addon_prefs()
return {'FINISHED'}
class UAC_addon_prefs(bpy.types.AddonPreferences): class UAC_addon_prefs(bpy.types.AddonPreferences):
## can be just __name__ if prefs are in the __init__ mainfile ## can be just __name__ if prefs are in the __init__ mainfile
# Else need the splitext '__name__ = addonname.subfile' (or use a static name) # Else need the splitext '__name__ = addonname.subfile' (or use a static name)
@ -15,16 +24,18 @@ class UAC_addon_prefs(bpy.types.AddonPreferences):
# some_bool_prop to display in the addon pref # some_bool_prop to display in the addon pref
debug : bpy.props.IntProperty( debug : bpy.props.IntProperty(
name='Debug', name='Debug',
default=0,
description="Enable Debug prints\n\ description="Enable Debug prints\n\
0 = no prints\n\ 0 = no prints\n\
1 = basic\n\ 1 = basic\n\
2 = full prints", 2 = full prints",
default=0) )
tgt_bone : bpy.props.StringProperty( tgt_bone : bpy.props.StringProperty(
name="Constrained Pose bone name", default='world', name="Constrained Pose bone name", default='world',
description="name of the bone that suppose to hold the constraint") description="name of the bone that suppose to hold the constraint")
""" default_forward_axis : bpy.props.EnumProperty( """ default_forward_axis : bpy.props.EnumProperty(
name='Forward Axis', name='Forward Axis',
default='TRACK_NEGATIVE_Y', # Modifier default is FORWARD_X default='TRACK_NEGATIVE_Y', # Modifier default is FORWARD_X
@ -54,8 +65,15 @@ class UAC_addon_prefs(bpy.types.AddonPreferences):
layout.prop(self, "debug") layout.prop(self, "debug")
classes = (
UAC_addon_prefs,
UAC_OT_open_addon_prefs,
)
def register(): def register():
bpy.utils.register_class(UAC_addon_prefs) for cls in classes:
bpy.utils.register_class(cls)
def unregister(): def unregister():
bpy.utils.unregister_class(UAC_addon_prefs) for cls in reversed(classes):
bpy.utils.unregister_class(cls)

View File

@ -14,6 +14,10 @@ class UAC_PG_settings(bpy.types.PropertyGroup) :
name="Tweak", description="Show Tweaking options", name="Tweak", description="Show Tweaking options",
default=False, options={'HIDDEN'}) default=False, options={'HIDDEN'})
tgt_bone : bpy.props.StringProperty(
name="Bone to constrain", default='',
description="Name of the bone to constrain with follow path (usually the root bone)")
path_to_follow : bpy.props.PointerProperty(type=bpy.types.Object, path_to_follow : bpy.props.PointerProperty(type=bpy.types.Object,
name="Path", description="Curve object used") name="Path", description="Curve object used")
@ -48,7 +52,6 @@ class UAC_PG_settings(bpy.types.PropertyGroup) :
), ),
) )
""" """
## foot axis not needed (not always aligned with character direction) ## foot axis not needed (not always aligned with character direction)
foot_axis : EnumProperty( foot_axis : EnumProperty(
@ -63,22 +66,6 @@ class UAC_PG_settings(bpy.types.PropertyGroup) :
# ('-Z', '-Z', '', '', 5), # ('-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(): def register():
bpy.utils.register_class(UAC_PG_settings) bpy.utils.register_class(UAC_PG_settings)