Files
2026-03-17 15:25:32 -06:00

575 lines
37 KiB
Python

# ***** BEGIN GPL LICENSE BLOCK *****
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ***** END GPL LICENCE BLOCK *****
bl_info = {
"name": "AnimToolBox",
"author": "Tal Hershkovich",
"version" : (0, 2, 3),
"blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel",
"description": "A set of animation tools",
"wiki_url": "",
"category": "Animation"}
if "bpy" in locals():
import importlib
if "Rigger_Toolbox" in locals():
importlib.reload(Rigger_Toolbox)
if "TempCtrls" in locals():
importlib.reload(TempCtrls)
if "Tools" in locals():
importlib.reload(Tools)
if "Display" in locals():
importlib.reload(Display)
if "emp" in locals():
importlib.reload(emp)
if "multikey" in locals():
importlib.reload(multikey)
if "Rigger_Toolbox" in locals():
importlib.reload(Rigger_Toolbox)
if "ui" in locals():
importlib.reload(ui)
if "addon_updater_ops" in locals():
importlib.reload(addon_updater_ops)
import bpy
from . import addon_updater_ops
from . import TempCtrls
from . import Rigger_Toolbox
from . import Tools
from . import Display
from . import emp
from . import ui
from . import multikey
from . import Rigger_Toolbox
from pathlib import Path
from bpy.utils import register_class
from bpy.utils import unregister_class
from bpy.app.handlers import persistent
import os
class TempCtrlsItems(bpy.types.PropertyGroup):
#located at context.scene.btc.ctrl_items
controlled: bpy.props.PointerProperty(name = "controlled object", description = "rigs and objects that are being controlled", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
controller: bpy.props.PointerProperty(name = "controller object", description = "rigs and objects that are controling", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
class TempCtrlsSceneSettings(bpy.types.PropertyGroup):
#located at context.scene.btc
root: bpy.props.BoolProperty(name = "Root Empty", description = "Add a root to the empties ", default = False, override = {'LIBRARY_OVERRIDABLE'}, update = TempCtrls.root_prop)
root_bone: bpy.props.StringProperty(name = "Root bone", description = "Root empty as a root bone ", override = {'LIBRARY_OVERRIDABLE'}, update = TempCtrls.root_update)
root_object: bpy.props.PointerProperty(name = "Root object", description = "Root empty as a root object ", update = TempCtrls.root_update, type = bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
ctrl_type: bpy.props.EnumProperty(name = 'Controllers', description="Select empties or a bone with a new rig to bake to", items = [('BONE', 'Bone','Bake to bones','BONE_DATA', 0), ('EMPTY', 'Empty', 'Bake to empties', 'EMPTY_ARROWS', 1)])
ctrl_items: bpy.props.CollectionProperty(type = TempCtrlsItems, override = {'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})
bake_range_type: bpy.props.EnumProperty(name = 'Bake Range', description="Use either scene, actions length or custom frame range", default = 'KEYFRAMES', update= TempCtrls.update_range_type,
items = [('SCENE', 'Scene Range', 'Bake to the scene range'), ('KEYFRAMES', 'Keyframes Range', 'Bake all the keyframes in the layers'), ('CUSTOM', 'Custom', 'Enter a custom frame range')], override = {'LIBRARY_OVERRIDABLE'})
bake_range: bpy.props.IntVectorProperty(name='Frame Range', description='Bake to a custom frame range', size = 2, update= TempCtrls.update_bake_range)
bake_layers: bpy.props.BoolProperty(name = "Bake Layers", description = "Use keyframes from all the layers to include in the bake", default = False)
target: bpy.props.EnumProperty(name = 'Affect', description="Cleanup created constraints and empties", default = 1,
items = [('ALL', 'All Ctrl Rigs','Bake to all Ctrl Rigs', 0),
('SELECTED', 'Selected Chains','Bake to only selected chain controlls', 1),
('RELATIVE', 'Relative Ctrls Rig','Bake to the Relative Control rigs', 2)])
# ('CONSTRAINTS', 'Constraints', 'Clean all the bone constraints', 3),
# ('CONTROLLERS', 'Controllers', 'Remove all the baked empties', 4)])
selection: bpy.props.EnumProperty(name = 'Select', description="Select all controls, original bones or their relative", default = 'CONTROLLERS',
items = [('RELATIVE_CTRLS', 'Relative Ctrls','Select the Relative controller to your current selection', 0),
('RELATIVE_CONSTRAINED', 'Relative Constrained','Select the Relative original constrained bone to your current selection', 1),
('CONTROLLERS', 'All Ctrls', 'Select all the controller bones or empties', 2), ('CONSTRAINED', 'All Constrained', 'Select all the original constrained bones', 3)])
#smartbake setting
linksettings: bpy.props.BoolProperty(name = "Link Settings", description = "Link Settings", default = False, override = {'LIBRARY_OVERRIDABLE'})
bakesettings: bpy.props.BoolProperty(name = "bake settings", description = "bake settings", default = False, override = {'LIBRARY_OVERRIDABLE'})
cleansettings: bpy.props.BoolProperty(name = "clean settings", description = "clean settings", default = False, override = {'LIBRARY_OVERRIDABLE'})
smartbake: bpy.props.BoolProperty(name = "Smart Bake", description = "Keep Original Frame count", default = True, override = {'LIBRARY_OVERRIDABLE'})
inbetween_keyframes: bpy.props.IntProperty(name = "Inbetween Keyframes", description = "Add inbetween keyframes", default = 0, override = {'LIBRARY_OVERRIDABLE'})
from_origin: bpy.props.BoolProperty(name = "From Origin", description = "Use Keyframes from Original Bone", default = True, override = {'LIBRARY_OVERRIDABLE'})
from_ctrl: bpy.props.BoolProperty(name = "From Controller", description = "Use Keyframes from Controller Bone", default = False, override = {'LIBRARY_OVERRIDABLE'})
clean_ctrls: bpy.props.BoolProperty(name = "Remove Ctrls", description = "Remove Controls", default = True, override = {'LIBRARY_OVERRIDABLE'})
clean_constraints: bpy.props.BoolProperty(name = "Remove Constraints", description = "Remove Constraints", default = True, override = {'LIBRARY_OVERRIDABLE'})
rebake_to_org: bpy.props.BoolProperty(name = "ReBake connections to original bones", description = "ReBake ctrls from connected chains current anim to the original bones", default = False, override = {'LIBRARY_OVERRIDABLE'})
link_to: bpy.props.EnumProperty(name = 'Link to Chain', description="Link to begining of an active chain or the tip of the chain", default = 1,
items = [('BASE', 'Base','Link to the base of the active chain', 0), ('TIP', 'Tip', 'Link to the tip of the chain', 1)])
# link_from: bpy.props.EnumProperty(name = 'Link to Chain', description="Link to begining of an active chain or the tip of the chain", default = 0,
# items = [('BASE', 'Base','Link to the base of the active chain', 0), ('TIP', 'Tip', 'Link to the tip of the chain', 1)])
shape_size: bpy.props.FloatProperty(name='Size', description="Multiple factor for the shape size of the temp controls", update = TempCtrls.tempctrl_shapesize, min = 0.001, default = 1.5, override = {'LIBRARY_OVERRIDABLE'})#
shape_type: bpy.props.EnumProperty(name = 'Shape Type', description="Display type for the controls", items = TempCtrls.ctrl_shape_items, update = TempCtrls.tempctrl_shape_type)
color_set: bpy.props.EnumProperty(name="Bone Color Set", description="Choose a bone color set", items = TempCtrls.get_bone_color_sets, update = TempCtrls.update_bone_color, default = 9)
add_ik_ctrl: bpy.props.BoolProperty(name = 'Add an Extra IK Ctrl Bone', description = "Adds an extra bone ctrl as the ik ctrl", default = False, update = TempCtrls.add_ik_prop)
pole_target: bpy.props.BoolProperty(name = 'Add Pole Target', description = "Adding Pole Target to the IK Chain", default = True, update = TempCtrls.pole_prop)
pole_offset: bpy.props.FloatProperty(name="Offset", description="Offset the bone in the axis direction", default=1.0, update = TempCtrls.pole_offset)
pole_axis: bpy.props.EnumProperty(name = 'Axis Direction', description="Which direction should the pole bone point", default = 4, update = TempCtrls.update_axis_prop,
items = [('+X', '+X','Target Bone the +X Axis', 0), ('+Y', '+Y', 'Target Bone the +Y Axis', 1),
('-X', '-X','Target Bone the -X Axis', 2), ('-Y', '-Y', 'Target Bone the -Y Axis', 3),
('AUTO', 'Auto','Select pole bone direction based on the current pose', 4)])
child: bpy.props.BoolProperty(name = 'Add extra child Ctrls', description = "Add an child control for an overlay control", default = False, update = TempCtrls.child_prop)
orientation: bpy.props.BoolProperty(name = 'Use World Space Orientation', description = "Orient the bones to world space instead of to the original bones", default = True)
# enabled: bpy.props.BoolProperty(name = 'Switch On / Off', description = "Enabling and Disabling Temp Ctrls influence", default = True)
class TempCtrlsBoneSettings(bpy.types.PropertyGroup):
#located at obj.pose.bones[##].btc
root: bpy.props.BoolProperty(name = "Root Bone", description = "Bone is marked as the root bone", default = False, override = {'LIBRARY_OVERRIDABLE'})
child: bpy.props.BoolProperty(name = "Child Bone", description = "Bone is marked as a child bone inside the setup", default = False, override = {'LIBRARY_OVERRIDABLE'})
org_id: bpy.props.IntProperty(name = "Originate ID", description = "ID number of the bone the ctrl originates from", override = {'LIBRARY_OVERRIDABLE'})
setup_id: bpy.props.IntProperty(name = "Setup ID", description = "ID number of the current chain setup", override = {'LIBRARY_OVERRIDABLE'})
setup: bpy.props.EnumProperty(name = 'Setup Type', description="Describes what kind of setup the bone is part of", override = {'LIBRARY_OVERRIDABLE'},
items = [('NONE', 'No Setup','No Setup Applied', 0),
('WORLDSPACE', 'World Space Ctrl','World Space Ctrl setup', 1),
('WORLDSPACE_CURSOR', 'World Space Cursor Ctrl','World Space Cursor pivot', 2),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 3),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 4),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 5),
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 6),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 7),
('ROOT', 'Root', 'Root Ctrl for all the setups', 8),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 9),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 10),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 11)])
#using org mostly to decide if it needs a custom shape
org: bpy.props.EnumProperty(name = 'Org Type', description="Describes what if the function of the bone", override = {'LIBRARY_OVERRIDABLE'},
items = [('CTRL', 'Controller Bone','Controller Bone', 0),
('ORG', 'Original Bone','Original Bone', 1),
('MCH', 'Mechanical Bone','Mechanical Bone', 2),
('NONE', 'Nothing applied','Nothing applied', 3)])
shape: bpy.props.BoolProperty(name = "Apply shape", description = "Mark if the bone needs a shape", default = False, override = {'LIBRARY_OVERRIDABLE'})
class TempCtrlsOrgIds(bpy.types.PropertyGroup):
# A collection of the org ids used in each setup. Org ID is the direct connection
# between original bones and ctrls
pass
class TempCtrlsObjectSetups(bpy.types.PropertyGroup):
#located at obj.animtoolbox.ctrl_setups
#name: using string of the id
setup: bpy.props.EnumProperty(name = 'Setup Type', description="Describes what kind of setup the bone is part of",
items = [('NONE', 'No Setup','No Setup Applied', 0),
('WORLDSPACE', 'World Space Ctrl','World Space Ctrl setup', 1),
('WORLDSPACE_CURSOR', 'World Space Cursor Ctrl','World Space Cursor pivot', 2),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 3),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 4),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 5),
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 6),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 7),
('ROOT', 'Root', 'Root Ctrl for all the setups', 8),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 9),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 10),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 11)])
org_ids: bpy.props.CollectionProperty(type = TempCtrlsOrgIds)
class MultikeyProperties(bpy.types.PropertyGroup):
selectedbones: bpy.props.BoolProperty(name="Selected Bones", description="Affect only selected bones", default=True, options={'HIDDEN'})
handletype: bpy.props.BoolProperty(name="Keep handle types", description="Keep handle types", default=False, options={'HIDDEN'})
scale: bpy.props.FloatProperty(name="Scale Factor", description="Scale percentage of the average value", default=1.0, update = multikey.scale_value)
randomness: bpy.props.FloatProperty(name="Randomness", description="Random Threshold of keyframes", default=0.1, min=0.0, max = 1.0, update = multikey.random_value)
class AnimToolBoxObjectSettings(bpy.types.PropertyGroup):
controlled: bpy.props.PointerProperty(name = 'Controlled Rig', description="Adding the rig object that is being controlled by the current object", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
controller: bpy.props.PointerProperty(name = 'Controller Rig', description="Adding the rig object that is used as the temp control object", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
ctrl_setups: bpy.props.CollectionProperty(type = TempCtrlsObjectSetups, override = {'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})
ctrls_enabled: bpy.props.BoolProperty(name = 'Temp Ctrls Switch', description = "Enabling and Disabling Temp Ctrls influence", default = True)
# influence: bpy.props.FloatProperty(name = "Influence Slider for the Temp Ctrls", description = "Switching the influence slider for the temp ctrls", default = 1, min = 0.0, max = 1.0)
#Used for Bake to Empties
# org_id: bpy.props.IntProperty(name = "Originate ID", description = "ID number of the bone the ctrl originates from", override = {'LIBRARY_OVERRIDABLE'})
setup_id: bpy.props.IntProperty(name = "Setup ID", description = "ID number of the current chain setup", override = {'LIBRARY_OVERRIDABLE'})
root: bpy.props.BoolProperty(name = "Root Empty", description = "Empty is marked as the root ctrl", default = False, override = {'LIBRARY_OVERRIDABLE'})
child: bpy.props.BoolProperty(name = "Child Empty", description = "Empty is marked as a child bone inside the setup", default = False, override = {'LIBRARY_OVERRIDABLE'})
keyframes_offset: bpy.props.FloatProperty(name = "Keyframes Offset", description = "Interactive slider to offset keyframes back and forth ", default = 0)
class IsolatedRigs(bpy.types.PropertyGroup):
hidden: bpy.props.PointerProperty(name = "Hidden Rigs", description = "List of Rigs that are hidden during pose mode isolation", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
selected: bpy.props.PointerProperty(name = "Selected Rigs", description = "List of Rigs that are hidden during pose mode isolation", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
class AnimToolBoxUILayout(bpy.types.PropertyGroup):
'''Layout properties for the UI'''
quick_menu: bpy.props.BoolProperty(name = "Use Quick Menu", description = "Opens header menu with only icon", default = False)
copy_paste_matrix: bpy.props.BoolProperty(name = "Copy Matrix Menu", description = "Opens the menu for copy paste matrices", default = True)
copy_paste_world: bpy.props.BoolProperty(name = "Copy Paste World Matrix", description = "Copy and Paste the World Matrix", default = False, update = Tools.copy_paste_world_update)
copy_paste_relative: bpy.props.BoolProperty(name = "Copy Paste Relative Matrix", description = "Copy and Paste the Matrix relative to the active bone", default = False, update = Tools.copy_paste_relative_update)
Inbetweens: bpy.props.BoolProperty(name = "Blendings/Inbetweens", description = "Opens the menu for Inbetweens", default = True)
gizmo_size: bpy.props.BoolProperty(name = "Gizmo size", description = "Change the Gizmo size using alt +/- hotkeys", default = False)
# temp_ctrls: bpy.props.BoolProperty(name = "Temp Ctrls", description = "Open Temp Ctrls", default = False)
temp_ctrls_switch: bpy.props.BoolProperty(name = "Temp Ctrls Switch", description = "Temp Ctrls Switch", default = True)
temp_ctrls_shapes: bpy.props.BoolProperty(name = "Temp Ctrls Shapes", description = "Temp Ctrls Shapes", default = True)
markers_retimer: bpy.props.BoolProperty(name = "Marker Retimer", description = "Flag when marker retimer turned on", default = False, override = {'LIBRARY_OVERRIDABLE'})
relative_cursor: bpy.props.BoolProperty(name = "Relative Cursor Mode", description = "Cursor moves relative to the selection", default = False)
is_dragging: bpy.props.BoolProperty(default = False)
#using Blending sliders in the window manager to avoid undo issues with modal operators
inbetween_worldmatrix: bpy.props.FloatProperty(name='Inbetween World Matrix', description="Adds an inbetween of the World Matrix to the Layer's neighbor keyframes", soft_min = -1, soft_max = 1, default=0.0, update = Tools.add_inbetween_worldmatrix, override = {'LIBRARY_OVERRIDABLE'})
blend_mirror: bpy.props.FloatProperty(name='Blend Mirror', description="Blend into the mirrored pose", soft_min = 0, soft_max = 1, default=0, step = 1, update = Tools.blend_to_mirror, override = {'LIBRARY_OVERRIDABLE'})
multikey: bpy.props.PointerProperty(type = MultikeyProperties, options={'LIBRARY_EDITABLE'}, override = {'LIBRARY_OVERRIDABLE'})
class AnimToolBoxGlobalSettings(bpy.types.PropertyGroup):
#context.scene.animtoolbox
marker_frame_range: bpy.props.BoolProperty(name = "Marker Frame Range", description = "Flag when marker frame range turned on", default = False, override = {'LIBRARY_OVERRIDABLE'})
bake_frame_range: bpy.props.BoolProperty(name = "Bake Frame Range", description = "Flag when marker bake range turned on", default = False, override = {'LIBRARY_OVERRIDABLE'})
# markers_retimer: bpy.props.BoolProperty(name = "Marker Retimer", description = "Flag when marker retimer turned on", default = False, override = {'LIBRARY_OVERRIDABLE'})
keyframes_offset: bpy.props.FloatProperty(name = "Keyframes Offset", description = "Interactive slider to offset keyframes back and forth ", soft_max = 2, soft_min = -2, default = 0, update = Tools.keyframes_offset_slider)
rotation_mode: bpy.props.EnumProperty(name = 'Rotation Mode', description="Describes what kind of setup the bone is part of", override = {'LIBRARY_OVERRIDABLE'},
items = [('QUATERNION', 'Quaternion','Quaternion Rotation Order - No Gimbal Lock', 0),
('XYZ', 'XYZ', 'XYZ Rotation Order', 1), ('XZY', 'XZY','XZY Rotation Order', 2),
('YXZ', 'YXZ','YXZ Rotation Order', 3), ('YZX', 'YZX', 'YZX Rotation Order', 4),
('ZXY', 'ZXY', 'ZXY Rotation Order', 5), ('ZYX', 'ZYX', 'ZYX Rotation Order', 6),
('AXIS_ANGLE', 'AXIS_ANGLE', 'Axis Angle Rotation Order', 7)])
isolate_pose_mode: bpy.props.BoolProperty(name = "Isolate rig in pose mode", description = "Isolates the rig during pose mode, rigs in object mode are hidden", default = False)
active_obj: bpy.props.PointerProperty(name = "Active Object", description = "Current Active Object", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
isolated: bpy.props.CollectionProperty(type = IsolatedRigs, override = {'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})
#Blendings
inbetweener : bpy.props.FloatProperty(name='Inbetween Keyframe', description="Adds an inbetween Keyframe between the Layer's neighbor keyframes", soft_min = -1, soft_max = 1, default=0.0, options = set(), update = Tools.add_inbetween_key, override = {'LIBRARY_OVERRIDABLE'})
gizmo_size: bpy.props.IntProperty(name = "Add to Gizmo Size", description = "Addition to Gizmo Size", max = 100, min = -100, default = 10)
#Copy/Pase Matrix
range_type: bpy.props.EnumProperty(name = 'Paste to Frames', description="Paste to current frame or a range of frames.", update = Tools.bake_range_type,
items = [('CURRENT', 'Current Frame','Paste Matrix to only current frame', 0),
('SELECTED', 'Selected Keyframes','Paste Matrix only to the selected keyframes', 1),
('RANGE', 'Frame Range','Paste Matrix to a Custom Frame Range', 2),
('SELECTED_RANGE', 'Selected Keyframes Range','Paste Matrix to every frame within a selected keyframe range', 3)])
bake_frame_start: bpy.props.IntProperty(name = "Bake Frame Start", description = "Define the start frame to paste the matrix", min = 0, update = Tools.bake_frame_start_limit)
bake_frame_end: bpy.props.IntProperty(name = "Bake Frame End", description = "Define the end frame to paste the matrix", min = 0, update = Tools.bake_frame_end_limit)
filter_location: bpy.props.BoolVectorProperty(name="Location", description="Filter Location properties", default=(False, False, False), size = 3, options={'HIDDEN'}, update = Tools.filter_name_update)
filter_rotation: bpy.props.BoolVectorProperty(name="Rotation", description="Filter Rotation properties", default=(False, False, False, False), size = 4, options={'HIDDEN'}, update = Tools.filter_name_update)
filter_scale: bpy.props.BoolVectorProperty(name="Scale", description="Filter Scale properties", default=(False, False, False), size = 3, options={'HIDDEN'}, update = Tools.filter_name_update)
#The name displayed on the filter button
filter_name: bpy.props.StringProperty(name="Filter Name", description="Change the name of the button while chaging the filter options", default= "", options={'HIDDEN'})
filter_custom_props: bpy.props.BoolProperty(name = "Filter Custom Properties", description = "Filter custom properties", default = False)
filter_keyframes: bpy.props.BoolProperty(name = "Filter Aelected Keyframes", description = "Filter selected keyframes for specific tools", default = False)
col_vis: bpy.props.BoolProperty(name = "Animated collections visibility", description = "Display if animated collections are turned on or off", default = False)
@addon_updater_ops.make_annotations
class AnimToolBoxPreferences(bpy.types.AddonPreferences):
# this must match the addon name, use '__package__'
# when defining this in a submodule of a python package.
bl_idname = __package__
category: bpy.props.StringProperty(
name="Tab Category",
description="Choose a name for the category of the panel",
default="Animation",
update=ui.update_panel
)
quick_menu: bpy.props.BoolProperty(name = "Use Quick Menu", description = "Opens header menu with only icon", default = False)
riggertoolbox: bpy.props.BoolProperty(name = "RiggerToolBox", description = "Include RiggerToolbox (experimental)", default = False, update = ui.add_riggertoolbox)
multikey: bpy.props.BoolProperty(name = "Multikey", description = "Include Multikey for adju\sting multiply keyframes", default = False, update = ui.add_multikey)
#Temp Ctrls properties
in_front: bpy.props.BoolProperty(name = "Always In Front", description = "Set Temp Ctrls to be always in front", default = True)
clear_setup : bpy.props.BoolProperty(name = "Clear Selection Before Creating New Temp Ctrls", description = "Clear old setup when adding Temp ctrls to an existing chain", default = False)
#Editable motion path
keyframes_range: bpy.props.IntProperty(name = "Keyframe Range", description = "The range of distance from the keyframes while hovering over them", min = 5, max = 100, default = 15)
mp_pref: bpy.props.BoolProperty(name = "Editable Motion Path Colors Theme", description = "Set the Color them of editable motion path visualization", default = False)
mp_keyframe_color: bpy.props.FloatVectorProperty(name="Keyframes", subtype='COLOR', default=(1.0, 1.0, 0.0, 1.0), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_handle_color: bpy.props.FloatVectorProperty(name="Handles", subtype='COLOR', default=(1.0, 0.8, 0.2, 1.0), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_remove_color: bpy.props.FloatVectorProperty(name="Remove Keyframes", subtype='COLOR', default=(0.0, 0.5, 1.0, 1.0), size=4, min=0.0, max=1.0, description="Keyframe color displayed before removing")
mp_hover_color: bpy.props.FloatVectorProperty(name="Hover", subtype='COLOR', default=(1.0, 0.4, 0.2, 1.0), size=4, min=0.0, max=1.0, description="Color during Hovering")
mp_handle_selection_color: bpy.props.FloatVectorProperty(name="Handles Selection", subtype='COLOR', default=(0.8, 0.65, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_key_selection_color: bpy.props.FloatVectorProperty(name="Keyframe Selection", subtype='COLOR', default=(0.8, 0.8, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Keyframe selection color")
# Motion Path brush colors
mp_brush_disabled: bpy.props.FloatVectorProperty(name="Disabled", subtype='COLOR', default=(0.7, 0.7, 0.9, 0.75), size=4, min=0.0, max=1.0, description="Brush is disabled and have no influence")
mp_brush_hover: bpy.props.FloatVectorProperty(name="Hover", subtype='COLOR', default=(0.8, 0.7, 0.2, 0.8), size=4, min=0.0, max=1.0, description="Brush is hovering and can be activated")
mp_brush_active: bpy.props.FloatVectorProperty(name="Active", subtype='COLOR', default=(1.0, 1.0, 1.0, 0.9), size=4, min=0.0, max=1.0, description="Brush is activated")
# addon updater preferences from `__init__`, be sure to copy all of them
auto_check_update: bpy.props.BoolProperty(
name = "Auto-check for Update",
description = "If enabled, auto-check for updates using an interval",
default = True,
)
updater_interval_months: bpy.props.IntProperty(
name='Months',
description = "Number of months between checking for updates",
default=0,
min=0
)
updater_interval_days: bpy.props.IntProperty(
name='Days',
description = "Number of days between checking for updates",
default=7,
min=0,
)
updater_interval_hours: bpy.props.IntProperty(
name='Hours',
description = "Number of hours between checking for updates",
default=0,
min=0,
max=23
)
updater_interval_minutes: bpy.props.IntProperty(
name='Minutes',
description = "Number of minutes between checking for updates",
default=0,
min=0,
max=59
)
#Draw the UI in the preferences
def draw(self, context):
layout = self.layout
addon_updater_ops.update_settings_ui(self, context)
row = layout.row()
col = row.column()
col.label(text="Tab Category:")
col.prop(self, "category", text="")
layout.separator()
col = layout.column()
col.prop(self, "quick_menu", text="Use Quick Icons Menu")
layout.separator()
box = layout.box()
row = box.row()
row.label(text = 'Temp Ctrls: ')
row = box.row()
row.prop(self, 'clear_setup')
row.prop(self, 'in_front')
layout.separator()
box = layout.box()
col = box.column()
col.prop(self, 'mp_pref', icon = 'DOWNARROW_HLT', text = 'Editable Motion Path Preferences')
if self.mp_pref:
col.prop(self, 'keyframes_range', text = 'Keyframe Distance Range')
col.label(text = 'Colors Theme')
row = box.row()
row.prop(self, 'mp_keyframe_color')
row.prop(self, 'mp_handle_color')
row.prop(self, 'mp_remove_color')
row = box.row()
row.prop(self, 'mp_hover_color')
row.prop(self, 'mp_key_selection_color')
row.prop(self, 'mp_handle_selection_color')
col = box.row()
col.label(text = 'Brush Colors')
row = box.row()
row.prop(self, 'mp_brush_disabled')
row.prop(self, 'mp_brush_hover')
row.prop(self, 'mp_brush_active')
layout.separator()
col = layout.column()
col.label(text = 'Include Extras: ')
row = layout.row()
row.prop(self, "multikey", text="Multikey - Edit Multiply keyframes")
row.prop(self, "riggertoolbox", text="RiggerToolBox (Experimental)")
@persistent
def loadanimtoolbox_pre(self, context):
scene = bpy.context.scene
dns = bpy.app.driver_namespace
if scene.animtoolbox.bake_frame_range:
scene.animtoolbox.bake_frame_range = False
if scene.emp.motion_path:
scene.emp.motion_path = False
bpy.context.workspace.status_text_set(None)
if 'mp_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('mp_dh')
emp.remove_draw_circle()
bpy.context.scene.emp.selected_keyframes = '{}'
if 'markers_retimer_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['markers_retimer_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('markers_retimer_dh')
#remove the motion path app handler if it's still inside
if emp.mp_value_update in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.remove(emp.mp_value_update)
if emp.mp_undo_update in bpy.app.handlers.undo_pre:
bpy.app.handlers.undo_pre.remove(emp.mp_undo_update)
@persistent
def loadanimtoolbox_post(self, context):
scene = bpy.context.scene
def remove_markers(prop, marker_type, names):
'''Remove Frame Range or Bake Range Markers if they were turned on'''
if not getattr(prop, marker_type):
return
setattr(prop, marker_type, False)
for marker_name in names:
if marker_name in scene.timeline_markers:
scene.timeline_markers.remove(scene.timeline_markers[marker_name])
dns = bpy.app.driver_namespace
if scene.animtoolbox.isolate_pose_mode:
if Display.isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
bpy.app.handlers.depsgraph_update_pre.append(Display.isolate_pose_mode)
if scene.emp.motion_path:
# In case motion path was active in the last save, make sure atb mp name property
# Is not included inside the bones, for a proper cleanup
for obj in bpy.context.view_layer.objects:
if "atb_mp_name" in obj:
del obj["atb_mp_name"]
if obj.type == 'ARMATURE':
for pbone in obj.pose.bones:
if "atb_mp_name" in pbone:
del pbone["atb_mp_name"]
scene.emp.motion_path = False
bpy.context.workspace.status_text_set(None)
if 'mp_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('mp_dh')
# Reset keyframe selection for motion paths it is not used in window manager
# Because it is used for undo
bpy.context.scene.emp.selected_keyframes = '{}'
remove_markers(scene.animtoolbox, 'marker_frame_range', {'Frame Start', 'Frame End'})
remove_markers(scene.animtoolbox, 'bake_frame_range', {'Bake Start', 'Bake End'})
remove_markers(scene.emp, 'marker_frame_range', {'MotionPath Start', 'MotionPath End'})
Tools.selection_order(self, context)
classes = (TempCtrlsItems, TempCtrlsOrgIds, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties,
IsolatedRigs, AnimToolBoxObjectSettings, AnimToolBoxUILayout, AnimToolBoxGlobalSettings) + ui.classes
addon_keymaps = []
def register():
# Note that preview collections returned by bpy.utils.previews
# are regular py objects - you can use them to store custom data.
addon_updater_ops.register(bl_info)
register_class(AnimToolBoxPreferences)
addon_updater_ops.make_annotations(AnimToolBoxPreferences) # to avoid blender 2.8 warnings
TempCtrls.register()
Tools.register()
Display.register()
emp.register()
ui.register_custom_icon()
for cls in classes:
# print(cls)
register_class(cls)
bpy.types.Scene.btc = bpy.props.PointerProperty(type = TempCtrlsSceneSettings, override = {'LIBRARY_OVERRIDABLE'})
bpy.types.PoseBone.btc = bpy.props.PointerProperty(type = TempCtrlsBoneSettings, override = {'LIBRARY_OVERRIDABLE'})
bpy.types.Object.animtoolbox = bpy.props.PointerProperty(type = AnimToolBoxObjectSettings, override = {'LIBRARY_OVERRIDABLE'})
bpy.types.Scene.animtoolbox = bpy.props.PointerProperty(type = AnimToolBoxGlobalSettings, override = {'LIBRARY_OVERRIDABLE'})
bpy.types.WindowManager.atb_ui = bpy.props.PointerProperty(type = AnimToolBoxUILayout, override = {'LIBRARY_OVERRIDABLE'})
ui.update_panel(None, bpy.context)
ui.add_multikey(None, bpy.context)
ui.add_riggertoolbox(None, bpy.context)
if loadanimtoolbox_pre not in bpy.app.handlers.load_pre:
bpy.app.handlers.load_pre.append(loadanimtoolbox_pre)
if loadanimtoolbox_post not in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.append(loadanimtoolbox_post)
if Tools.selection_order not in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.append(Tools.selection_order)
#Make sure TAB hotkey in the NLA goes into full stack mode
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
km = kc.keymaps.new(name= '3D View', space_type= 'VIEW_3D')
if 'view3d.gizmo_size_up' not in km.keymap_items:
kmi = km.keymap_items.new('view3d.gizmo_size_up', type= 'NUMPAD_PLUS', value= 'PRESS', alt = True, repeat = True)
addon_keymaps.append((km, kmi))
if 'view3d.gizmo_size_down' not in km.keymap_items:
kmi = km.keymap_items.new('view3d.gizmo_size_down', type= 'NUMPAD_MINUS', value= 'PRESS', alt = True, repeat = True)
addon_keymaps.append((km, kmi))
#Add Tools to the Toolbar
bpy.utils.register_tool(ui.KeyframeOffsetTool, separator=True)
#Add tools to the menu
bpy.types.VIEW3D_MT_editor_menus.append(ui.draw_menu)
def unregister():
for pcoll in ui.preview_collections.values():
bpy.utils.previews.remove(pcoll)
ui.preview_collections.clear()
#addon_updater_ops.unregister()
addon_updater_ops.unregister()
unregister_class(AnimToolBoxPreferences)
TempCtrls.unregister()
# Rigger_Toolbox.unregister()
Tools.unregister()
Display.unregister()
emp.unregister()
ui.add_multikey(None, bpy.context)
ui.add_riggertoolbox(None, bpy.context)
for cls in classes:
unregister_class(cls)
bpy.utils.unregister_tool(ui.KeyframeOffsetTool)
#Remove the header menu ui
bpy.types.VIEW3D_MT_editor_menus.remove(ui.draw_menu)
del bpy.types.Scene.btc
# del bpy.types.Bone.btc
del bpy.types.Object.animtoolbox
del bpy.types.Scene.animtoolbox
if hasattr(bpy.types.Object, 'keyframes_offset'):
del bpy.types.Object.keyframes_offset
if hasattr(bpy.types.PoseBone, 'keyframes_offset'):
del bpy.types.PoseBone.keyframes_offset
if loadanimtoolbox_pre in bpy.app.handlers.load_pre:
bpy.app.handlers.load_pre.remove(loadanimtoolbox_pre)
if loadanimtoolbox_post in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(loadanimtoolbox_post)
if Tools.selection_order in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.remove(Tools.selection_order)
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
if __name__ == "__main__":
register()