2025-07-01
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
# ***** 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 *****
|
||||
|
||||
import bpy
|
||||
|
||||
class GizmoSizeUp(bpy.types.Operator):
|
||||
"""Share keyframes between all the selected objects and bones"""
|
||||
bl_idname = "view3d.gizmo_size_up"
|
||||
bl_label = "Gizmo_Size_Up"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
context.preferences.view.gizmo_size += context.scene.animtoolbox.gizmo_size
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
class GizmoSizeDown(bpy.types.Operator):
|
||||
"""Share keyframes between all the selected objects and bones"""
|
||||
bl_idname = "view3d.gizmo_size_down"
|
||||
bl_label = "Gizmo_Size_Down"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
context.preferences.view.gizmo_size -= context.scene.animtoolbox.gizmo_size
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
def clear_isolate_pose_mode(scene):
|
||||
if not len(scene.animtoolbox.isolated):
|
||||
return
|
||||
for obj in scene.animtoolbox.isolated:
|
||||
if not obj.hidden:
|
||||
continue
|
||||
obj.hidden.hide_set(False)
|
||||
scene.animtoolbox.isolated.clear()
|
||||
scene.animtoolbox.active_obj = None
|
||||
|
||||
def isolate_pose_mode(scene):
|
||||
context = bpy.context
|
||||
#return when going out of isolate pose or when active object is not in pose mode
|
||||
if not scene.animtoolbox.isolate_pose_mode or context.active_object.mode != 'POSE':
|
||||
clear_isolate_pose_mode(scene)
|
||||
return
|
||||
|
||||
#handler continue only if the active object is None otherwise it collects all armature objects
|
||||
if scene.animtoolbox.active_obj is None:
|
||||
scene.animtoolbox.active_obj = context.active_object
|
||||
else:
|
||||
return
|
||||
|
||||
isolated = scene.animtoolbox.isolated
|
||||
for obj in context.view_layer.objects:
|
||||
if obj.type != 'ARMATURE':
|
||||
continue
|
||||
if obj.hide_get():
|
||||
continue
|
||||
rig = isolated.add()
|
||||
if obj.mode == 'POSE':
|
||||
rig.selected = obj
|
||||
else:
|
||||
rig.hidden = obj
|
||||
obj.hide_set(True)
|
||||
|
||||
class IsolatePoseMode(bpy.types.Operator):
|
||||
"""Isolates armatures during pose mode"""
|
||||
bl_idname = "anim.isolate_pose_mode"
|
||||
bl_label = "Isolate Pose Mode"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
# If the modal is already running, then don't run it the second time
|
||||
scene = context.scene
|
||||
if scene.animtoolbox.isolate_pose_mode:
|
||||
if isolate_pose_mode in bpy.app.handlers.depsgraph_update_pre:
|
||||
clear_isolate_pose_mode(scene)
|
||||
bpy.app.handlers.depsgraph_update_pre.remove(isolate_pose_mode)
|
||||
scene.animtoolbox.isolate_pose_mode = False
|
||||
return {'FINISHED'}
|
||||
|
||||
scene.animtoolbox.isolate_pose_mode = True
|
||||
isolate_pose_mode(scene)
|
||||
if isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
|
||||
bpy.app.handlers.depsgraph_update_pre.append(isolate_pose_mode)
|
||||
return {'FINISHED'}
|
||||
|
||||
class SwitchBoneCollectionsVisibility(bpy.types.Operator):
|
||||
"""Turn all bone collections visible and then press again to switch back"""
|
||||
bl_idname = "anim.switch_collections_visibility"
|
||||
bl_label = "Bone Collections Visibility"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bpy.app.version >= (4, 0, 0)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.object
|
||||
if obj.type != 'ARMATURE':
|
||||
return {'CANCELLED'}
|
||||
if not obj.animation_data:
|
||||
self.report({'INFO'}, 'No animation is available')
|
||||
return {'CANCELLED'}
|
||||
|
||||
if not obj.animation_data.action:
|
||||
self.report({'INFO'}, 'No animation is available')
|
||||
return {'CANCELLED'}
|
||||
|
||||
collections = obj.data.collections
|
||||
|
||||
if not len(collections):
|
||||
self.report({'INFO'}, 'No collections are available')
|
||||
return {'CANCELLED'}
|
||||
#check if there are collections that are marked with
|
||||
tagged_col = ['atb' in col.keys() for col in collections]
|
||||
atb_ui = context.scene.animtoolbox
|
||||
|
||||
if any(tagged_col) and atb_ui.col_vis:
|
||||
#collections are already marked so return to previous collection visibilty
|
||||
for col in collections:
|
||||
if 'atb' in col.keys():
|
||||
col.is_visible = col['atb']
|
||||
del col['atb']
|
||||
atb_ui.col_vis = False
|
||||
else:
|
||||
#Mark visible collections and turn collections with animated bones on
|
||||
animated_bones = set()
|
||||
start = 'pose.bones["'
|
||||
end = '"]'
|
||||
#get all the animated bones from the fcurves
|
||||
for fcu in obj.animation_data.action.fcurves:
|
||||
start_index = fcu.data_path.find(start)
|
||||
end_index = fcu.data_path.find(end)
|
||||
#if it's not a posebone fcurve then skip
|
||||
if start_index == -1 or end_index == -1:
|
||||
continue
|
||||
animated_bones.add(fcu.data_path[start_index + len(start):end_index])
|
||||
|
||||
#check if the collecetion that is turned off has animated bones
|
||||
find_anim = []
|
||||
for col in collections:
|
||||
for bone in col.bones:
|
||||
if bone.name in animated_bones and not col.is_visible:
|
||||
# print(bone.name, 'in ', col.name)
|
||||
find_anim.append(col)
|
||||
break
|
||||
|
||||
if not find_anim:
|
||||
self.report({'INFO'}, 'No collections with animated bones and no visibility are found')
|
||||
return {'CANCELLED'}
|
||||
|
||||
#Turn on collections without visiblity
|
||||
for col in collections:
|
||||
if col in find_anim:
|
||||
#tag visibility
|
||||
col['atb'] = col.is_visible
|
||||
col.is_visible = True
|
||||
|
||||
atb_ui.col_vis = True
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
classes = (GizmoSizeUp, GizmoSizeDown, IsolatePoseMode, SwitchBoneCollectionsVisibility)
|
||||
|
||||
def register():
|
||||
from bpy.utils import register_class
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
||||
@@ -0,0 +1,549 @@
|
||||
# ***** 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 *****
|
||||
|
||||
import bpy
|
||||
from mathutils import Matrix, Vector
|
||||
from math import radians
|
||||
import numpy
|
||||
|
||||
def draw_wgt(boneLength, bone):
|
||||
suffix = bone.id_data.name + '_' + bone.name
|
||||
if 'WGTB_object' + suffix in bpy.data.objects:
|
||||
obj = bpy.data.objects['WGTB_object'] + suffix
|
||||
if 'WGTB_shape' + suffix in obj.data.name:
|
||||
return obj
|
||||
mesh = bpy.data.meshes.new('WGTB_shape_' + suffix)
|
||||
obj = bpy.data.objects.new('WGTB_object_' + suffix, mesh)
|
||||
#coordinates of the sphere widget shape
|
||||
sphere = {"edges": [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10], [10, 11], [11, 12], [12, 13], [13, 14], [14, 15], [15, 16], [16, 17], [17, 18], [18, 19], [19, 20], [20, 21], [21, 22], [22, 23], [0, 23], [24, 25], [25, 26], [26, 27], [27, 28], [28, 29], [29, 30], [30, 31], [31, 32], [32, 33], [33, 34], [34, 35], [35, 36], [36, 37], [37, 38], [38, 39], [39, 40], [40, 41], [41, 42], [42, 43], [43, 44], [44, 45], [45, 46], [46, 47], [24, 47], [48, 49], [49, 50], [50, 51], [51, 52], [52, 53], [53, 54], [54, 55], [55, 56], [56, 57], [57, 58], [58, 59], [59, 60], [60, 61], [61, 62], [62, 63], [63, 64], [64, 65], [65, 66], [66, 67], [67, 68], [68, 69], [69, 70], [70, 71], [48, 71]],
|
||||
"vertices": [[0.0, 0.10000002384185791, 0.0], [-0.025881901383399963, 0.09659260511398315, 0.0], [-0.050000011920928955, 0.08660250902175903, 0.0], [-0.07071065902709961, 0.07071065902709961, 0.0], [-0.08660256862640381, 0.04999998211860657, 0.0], [-0.09659260511398315, 0.025881901383399963, 0.0], [-0.10000002384185791, 7.549793679118011e-09, 0.0], [-0.09659260511398315, -0.02588188648223877, 0.0], [-0.08660256862640381, -0.04999998211860657, 0.0], [-0.07071071863174438, -0.07071065902709961, 0.0], [-0.050000011920928955, -0.08660250902175903, 0.0], [-0.02588193118572235, -0.09659260511398315, 0.0], [-3.894143674187944e-08, -0.10000002384185791, 0.0], [0.025881856679916382, -0.09659260511398315, 0.0], [0.04999995231628418, -0.08660256862640381, 0.0], [0.07071065902709961, -0.07071071863174438, 0.0], [0.08660250902175903, -0.05000004172325134, 0.0], [0.09659254550933838, -0.025881946086883545, 0.0], [0.10000002384185791, -4.649123752642481e-08, 0.0], [0.09659260511398315, 0.025881856679916382, 0.0], [0.08660256862640381, 0.04999995231628418, 0.0], [0.07071071863174438, 0.07071065902709961, 0.0], [0.05000007152557373, 0.08660250902175903, 0.0], [0.025881975889205933, 0.09659254550933838, 0.0], [0.0, 7.450580596923828e-09, 0.10000002384185791], [-0.025881901383399963, 7.450580596923828e-09, 0.09659260511398315], [-0.050000011920928955, 7.450580596923828e-09, 0.08660250902175903], [-0.07071065902709961, 7.450580596923828e-09, 0.07071065902709961], [-0.08660256862640381, 3.725290298461914e-09, 0.04999998211860657], [-0.09659260511398315, 1.862645149230957e-09, 0.025881901383399963], [-0.10000002384185791, 8.881784197001252e-16, 7.549793679118011e-09], [-0.09659260511398315, -1.862645149230957e-09, -0.02588188648223877], [-0.08660256862640381, -3.725290298461914e-09, -0.04999998211860657], [-0.07071071863174438, -7.450580596923828e-09, -0.07071065902709961], [-0.050000011920928955, -7.450580596923828e-09, -0.08660250902175903], [-0.02588193118572235, -7.450580596923828e-09, -0.09659260511398315], [-3.894143674187944e-08, -7.450580596923828e-09, -0.10000002384185791], [0.025881856679916382, -7.450580596923828e-09, -0.09659260511398315], [0.04999995231628418, -7.450580596923828e-09, -0.08660256862640381], [0.07071065902709961, -7.450580596923828e-09, -0.07071071863174438], [0.08660250902175903, -3.725290298461914e-09, -0.05000004172325134], [0.09659254550933838, -1.862645149230957e-09, -0.025881946086883545], [0.10000002384185791, -3.552713678800501e-15, -4.649123752642481e-08], [0.09659260511398315, 1.862645149230957e-09, 0.025881856679916382], [0.08660256862640381, 3.725290298461914e-09, 0.04999995231628418], [0.07071071863174438, 7.450580596923828e-09, 0.07071065902709961], [0.05000007152557373, 7.450580596923828e-09, 0.08660250902175903], [0.025881975889205933, 7.450580596923828e-09, 0.09659254550933838], [-7.450580596923828e-09, 4.440892098500626e-16, 0.10000002384185791], [-9.313225746154785e-09, -0.025881901383399963, 0.09659260511398315], [-1.1175870895385742e-08, -0.050000011920928955, 0.08660250902175903], [-1.4901161193847656e-08, -0.07071065902709961, 0.07071065902709961], [-7.450580596923828e-09, -0.08660256862640381, 0.04999998211860657], [-7.450580596923828e-09, -0.09659260511398315, 0.025881901383399963], [-7.450580596923828e-09, -0.10000002384185791, 7.549793679118011e-09], [-7.450580596923828e-09, -0.09659260511398315, -0.02588188648223877], [0.0, -0.08660256862640381, -0.04999998211860657], [0.0, -0.07071071863174438, -0.07071065902709961], [3.725290298461914e-09, -0.050000011920928955, -0.08660250902175903], [5.587935447692871e-09, -0.02588193118572235, -0.09659260511398315], [7.450577044210149e-09, -3.894143674187944e-08, -0.10000002384185791], [9.313225746154785e-09, 0.025881856679916382, -0.09659260511398315], [1.1175870895385742e-08, 0.04999995231628418, -0.08660256862640381], [1.4901161193847656e-08, 0.07071065902709961, -0.07071071863174438], [7.450580596923828e-09, 0.08660250902175903, -0.05000004172325134], [7.450580596923828e-09, 0.09659254550933838, -0.025881946086883545], [7.450580596923828e-09, 0.10000002384185791, -4.649123752642481e-08], [7.450580596923828e-09, 0.09659260511398315, 0.025881856679916382], [0.0, 0.08660256862640381, 0.04999995231628418], [0.0, 0.07071071863174438, 0.07071065902709961], [-3.725290298461914e-09, 0.05000007152557373, 0.08660250902175903], [-5.587935447692871e-09, 0.025881975889205933, 0.09659254550933838]], "faces": []}
|
||||
mesh.from_pydata(numpy.array(sphere['vertices'])*[boneLength, boneLength, boneLength] , sphere['edges'], sphere['faces'])
|
||||
|
||||
return obj
|
||||
|
||||
def add_driver(obj, posebone, control, target, path, multiply = ''):
|
||||
|
||||
if isinstance(target, tuple):
|
||||
attr = posebone.driver_add(target[0], target[1])
|
||||
else:
|
||||
attr = posebone.driver_add(target)
|
||||
|
||||
var = attr.driver.variables.new()
|
||||
|
||||
var.targets[0].id = obj
|
||||
var.targets[0].data_path = 'pose.bones["' + control +'"].'+ path
|
||||
attr.driver.expression = var.name + multiply
|
||||
|
||||
def dup_values(source, target):
|
||||
if hasattr(source, 'parent'):
|
||||
target.parent = source.parent
|
||||
for prop in dir(source):
|
||||
if not hasattr(target, prop):
|
||||
continue
|
||||
value = getattr(source, prop)
|
||||
if type(value) not in {int, float, bool, str, Vector, Matrix, bpy.types.Object}:
|
||||
continue
|
||||
if '__' in prop[:2] and '__' in prop[-2:]:
|
||||
continue
|
||||
if target.is_property_readonly(prop):
|
||||
continue
|
||||
setattr(target, prop, value)
|
||||
|
||||
return target
|
||||
|
||||
def dup_constraints(source, target):
|
||||
if not source.constraints.items():
|
||||
return
|
||||
for source_con in source.constraints:
|
||||
target_con = target.constraints.new(source_con.type)
|
||||
dup_values(source_con, target_con)
|
||||
|
||||
def add_vis_bone_con(obj, bone_vis_name, bone_wgt_name):
|
||||
bone_vis = obj.pose.bones[bone_vis_name]
|
||||
con = bone_vis.constraints.new('STRETCH_TO')
|
||||
con.target = obj
|
||||
con.subtarget = bone_wgt_name
|
||||
|
||||
return bone_vis
|
||||
|
||||
class target:
|
||||
def __init__(self, bone):
|
||||
self.name = bone.name
|
||||
self.point = tuple(bone.tail)
|
||||
self.ctrl = 'TRGT_' + bone.name
|
||||
if bone.parent:
|
||||
self.parent = bone.parent.name
|
||||
#print('assign parent to target ', self.name, self.ctrl, self.parent)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.point < other.point
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.point)
|
||||
|
||||
def __eq__(self, other):
|
||||
#if not isinstance(other, type(self)):
|
||||
# return NotImplemented
|
||||
return self.point == other.point
|
||||
|
||||
class parent:
|
||||
def __init__(self, bone):
|
||||
self.name = bone.name
|
||||
self.point = tuple(bone.head)
|
||||
self.ctrl = 'CTRL_' + bone.name
|
||||
if bone.parent:
|
||||
self.parent = bone.parent.name
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.point < other.point
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.point)
|
||||
|
||||
def __eq__(self, other):
|
||||
#if not isinstance(other, type(self)):
|
||||
# return NotImplemented
|
||||
return self.point == other.point
|
||||
|
||||
class constraint_dup:
|
||||
def __init__(self, bone, con):
|
||||
self.name = con.name
|
||||
self.target = con.target
|
||||
self.subtarget = con.subtarget
|
||||
self.bone = bone.name
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.bone)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.bone == other.bone
|
||||
|
||||
def bone_orientation(source, target, value):
|
||||
source.align_orientation(target)
|
||||
x, y, z = source.matrix.to_3x3().col
|
||||
R = (Matrix.Translation(source.head) @ Matrix.Rotation(radians(value), 4, x) @ Matrix.Translation(-source.head))
|
||||
source.transform(R, roll = False)
|
||||
source.align_roll(target.vector)
|
||||
|
||||
def find_ctrl(bone, controls):
|
||||
i = list(controls).index(bone)
|
||||
bone.ctrl = list(controls)[i].ctrl
|
||||
|
||||
return bone.ctrl
|
||||
|
||||
def add_controlers(self, obj, parents, targets):
|
||||
|
||||
#controls = set(parents).union(targets)
|
||||
controls = set(parents + targets)
|
||||
|
||||
#create hierarchy
|
||||
for bone in controls:
|
||||
editbone = obj.data.edit_bones[bone.name]
|
||||
if editbone.parent is None:
|
||||
continue
|
||||
parentnames = [bone.name for bone in parents]
|
||||
#if a target and its parent are part of the hierarchy then linked to its own bone parent
|
||||
if bone in targets and bone not in parents and editbone.parent.name in parentnames:
|
||||
parentbone = parent(editbone)
|
||||
else:
|
||||
parentbone = parent(editbone.parent)
|
||||
|
||||
if parentbone in controls and parentbone != bone:
|
||||
bone.parent = find_ctrl(parentbone, controls)
|
||||
else:
|
||||
bone.parent = editbone.parent.name
|
||||
|
||||
|
||||
edit_bones = obj.data.edit_bones
|
||||
for bone in controls:
|
||||
editbone = edit_bones[bone.name]
|
||||
ctrl = obj.data.edit_bones.new(bone.ctrl)
|
||||
ctrl.head = bone.point
|
||||
ctrl.tail = bone.point
|
||||
ctrl.tail[2] = bone.point[2] + (editbone.length / 3)
|
||||
ctrl.bbone_x = editbone.bbone_x
|
||||
ctrl.bbone_z = editbone.bbone_z
|
||||
ctrl.use_deform = False
|
||||
if self.bone_align:
|
||||
angle = 90 if self.align_90 else 0
|
||||
bone_orientation(ctrl, editbone, angle)
|
||||
if angle == 90:
|
||||
ctrl.align_roll(editbone.vector)
|
||||
else:
|
||||
ctrl.roll = editbone.roll
|
||||
|
||||
#apply hierarchy
|
||||
for bone in parents:
|
||||
editbone = edit_bones[bone.name]
|
||||
ctrl_name = find_ctrl(bone, controls)
|
||||
ctrl = edit_bones[ctrl_name]
|
||||
editbone.parent = ctrl
|
||||
|
||||
for bone in controls:
|
||||
ctrl = edit_bones[bone.ctrl]
|
||||
if hasattr(bone, 'parent'):
|
||||
ctrl.parent = edit_bones[bone.parent]
|
||||
|
||||
return controls
|
||||
|
||||
def pose_bbone_setup(bone, posebone, bbone_group = None):
|
||||
#add the custom shape to the widget bones
|
||||
custom_shape = draw_wgt(bone['length'], posebone)
|
||||
posebone.custom_shape = custom_shape
|
||||
posebone.use_custom_shape_bone_size = False
|
||||
if bbone_group:
|
||||
posebone.bone_group = bbone_group
|
||||
posebone.rotation_mode = 'XZY'
|
||||
posebone.lock_rotation[0] = True
|
||||
posebone.lock_rotation[2] = True
|
||||
|
||||
#####MAIN####
|
||||
class BboneWidgets(bpy.types.Operator):
|
||||
"""Add Bbone widget controls to the selected bones"""
|
||||
bl_idname = "armature.add_bbone_widgets"
|
||||
bl_label = "Add_Bbone_widgets"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bpy.context.object.type == 'ARMATURE'
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.object
|
||||
|
||||
obj.data.display_type = 'BBONE'
|
||||
|
||||
bones = []
|
||||
parentlayers = [False if i != 24 else True for i in range(32)]
|
||||
wgtlayers = [True if i == 0 else False for i in range(32)]
|
||||
|
||||
bpy.ops.object.mode_set(mode = 'EDIT')
|
||||
obj.data.use_mirror_x = False
|
||||
for bone in obj.data.edit_bones:
|
||||
if not bone.select:
|
||||
continue
|
||||
if bone.bbone_segments == 1:
|
||||
bone.bbone_segments = 10
|
||||
bone.bbone_handle_type_start = 'TANGENT'
|
||||
bone.bbone_handle_type_end = 'TANGENT'
|
||||
bone_name = bone.name
|
||||
#add parent bone to the Bbone widgets
|
||||
parent = obj.data.edit_bones.new('WGTB_parent_'+ bone_name)
|
||||
parent_name = parent.name
|
||||
dup_values(bone, parent)
|
||||
parent.name = parent_name
|
||||
parent.select = False
|
||||
parent.select_head = False
|
||||
parent.select_tail = False
|
||||
|
||||
#change layer of the parent bone
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
parent.layers = parentlayers
|
||||
|
||||
#add bbone widget bones
|
||||
head_widget = obj.data.edit_bones.new('Bhead_'+ bone.name)
|
||||
head_widget.parent = parent
|
||||
#head_widget.head = bone.head
|
||||
head_widget.head = bone.head + (bone.tail - bone.head) * 0.25
|
||||
#head_widget.tail = (bone.tail - bone.head)/10
|
||||
head_widget.length = bone.length * 0.1
|
||||
head_widget.bbone_x = bone.bbone_x
|
||||
head_widget.bbone_z = bone.bbone_z
|
||||
head_widget.align_orientation(bone)
|
||||
head_widget.inherit_scale = 'NONE'
|
||||
head_name = head_widget.name
|
||||
|
||||
tail_widget = obj.data.edit_bones.new('Btail_'+ bone.name)
|
||||
tail_widget.parent = parent
|
||||
#tail_widget.head = bone.tail
|
||||
tail_widget.head = bone.head + (bone.tail - bone.head) * 0.75
|
||||
#tail_widget.tail = bone.tail - (bone.tail - bone.head)/10
|
||||
tail_widget.length = bone.length * 0.1
|
||||
tail_widget.bbone_x = bone.bbone_x
|
||||
tail_widget.bbone_z = bone.bbone_z
|
||||
tail_widget.align_orientation(bone)
|
||||
tail_widget.inherit_scale = 'NONE'
|
||||
tail_name = tail_widget.name
|
||||
|
||||
#add vis bones
|
||||
head_vis = obj.data.edit_bones.new('Bhead_vis_'+ bone.name)
|
||||
head_vis.parent = parent
|
||||
head_vis.head = bone.head
|
||||
head_vis.tail = head_widget.head
|
||||
head_vis.bbone_x = bone.bbone_x*0.1
|
||||
head_vis.bbone_z = bone.bbone_z*0.1
|
||||
|
||||
#head_vis_name = head_vis.name
|
||||
head_vis.hide_select = True
|
||||
head_vis.use_deform = False
|
||||
tail_vis = obj.data.edit_bones.new('Btail_vis_'+ bone.name)
|
||||
tail_vis.parent = parent
|
||||
tail_vis.head = bone.tail
|
||||
tail_vis.tail = tail_widget.head
|
||||
tail_vis.bbone_x = bone.bbone_x*0.1
|
||||
tail_vis.bbone_z = bone.bbone_z*0.1
|
||||
|
||||
#tail_vis_name = tail_vis.name
|
||||
tail_vis.hide_select = True
|
||||
|
||||
tail_vis.use_deform = False
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
tail_widget.layers = wgtlayers
|
||||
head_widget.layers = wgtlayers
|
||||
head_vis.layers = wgtlayers
|
||||
tail_vis.layers = wgtlayers
|
||||
|
||||
bones.append({'name': bone_name, 'parent': parent_name, 'head': head_name, 'tail': tail_name, 'head_vis': head_vis.name, 'tail_vis': tail_vis.name, 'length': bone.length})
|
||||
|
||||
#####POSE MODE#######
|
||||
bpy.ops.object.mode_set(mode = 'POSE')
|
||||
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
bone_groups = obj.pose.bone_groups
|
||||
if 'BBone Widgets' not in bone_groups:
|
||||
bbone_group = bone_groups.new(name = 'BBone Widgets')
|
||||
bbone_group.color_set = 'THEME09'
|
||||
else:
|
||||
bbone_group = bone_groups['BBone Widgets']
|
||||
else:
|
||||
bbone_group = None
|
||||
|
||||
for bone in bones:
|
||||
posebone = obj.pose.bones[bone['name']]
|
||||
# Prepare parent bone in pose mode
|
||||
poseparent = obj.pose.bones[bone['parent']]
|
||||
#disable use deform
|
||||
obj.data.bones[bone['parent']].use_deform = False
|
||||
obj.data.bones[bone['head']].use_deform = False
|
||||
obj.data.bones[bone['tail']].use_deform = False
|
||||
|
||||
pose_bbone_setup(bone, obj.pose.bones[bone['head']], bbone_group)
|
||||
pose_bbone_setup(bone, obj.pose.bones[bone['tail']], bbone_group)
|
||||
|
||||
dup_constraints(posebone, poseparent)
|
||||
|
||||
#add all the drivers
|
||||
add_driver(obj, posebone, bone['head'], 'bbone_curveinx', 'location.x')
|
||||
add_driver(obj, posebone, bone['head'], 'bbone_curveinz', 'location.z')
|
||||
add_driver(obj, posebone, bone['head'], 'bbone_easein', 'location.y', '*5/'+ str(bone['length']))
|
||||
add_driver(obj, posebone, bone['head'], 'bbone_rollin', 'rotation_euler.y')
|
||||
add_driver(obj, posebone, bone['head'], ('bbone_scalein', 0), 'scale.x')
|
||||
add_driver(obj, posebone, bone['head'], ('bbone_scalein', 1), 'scale.y')
|
||||
add_driver(obj, posebone, bone['head'], ('bbone_scalein', 2), 'scale.z')
|
||||
|
||||
add_driver(obj, posebone, bone['tail'], 'bbone_curveoutx', 'location.x')
|
||||
add_driver(obj, posebone, bone['tail'], 'bbone_curveoutz', 'location.z')
|
||||
add_driver(obj, posebone, bone['tail'], 'bbone_easeout', 'location.y', '*-5/'+ str(bone['length']))
|
||||
add_driver(obj, posebone, bone['tail'], 'bbone_rollout', 'rotation_euler.y')
|
||||
add_driver(obj, posebone, bone['tail'], ('bbone_scaleout', 0), 'scale.x')
|
||||
add_driver(obj, posebone, bone['tail'], ('bbone_scaleout', 1), 'scale.y')
|
||||
add_driver(obj, posebone, bone['tail'], ('bbone_scaleout', 2), 'scale.z')
|
||||
|
||||
#add constraints to visual bones
|
||||
head_vis = add_vis_bone_con(obj, bone['head_vis'], bone['head'])
|
||||
tail_vis = add_vis_bone_con(obj, bone['tail_vis'], bone['tail'])
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
head_vis.bone_group = bbone_group
|
||||
tail_vis.bone_group = bbone_group
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
class ChainControls(bpy.types.Operator):
|
||||
"""Add parent and target controls to the selected bones to create a chain control"""
|
||||
bl_idname = "armature.add_chain_ctrls"
|
||||
bl_label = "Add_Chain_Controls"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
parents: bpy.props.BoolProperty(name = 'Add Parents', description = "Align the controls 90 degrees to the original bones", default = True)
|
||||
targets: bpy.props.BoolProperty(name = 'Add Targets', description = "Align the controls 90 degrees to the original bones", default = True)
|
||||
keep_hierarchy: bpy.props.BoolProperty(name = 'Keep Hierarchy', description = "Keep the controls in the hierarchy of the original bones", default = True)
|
||||
bone_align: bpy.props.BoolProperty(name = 'Align to Bones', description = "Align the controls to the original bones", default = True)
|
||||
align_90: bpy.props.BoolProperty(name = '+90°', description = "Align the controls 90 degrees to the original bones", default = True)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bpy.context.object.type == 'ARMATURE'
|
||||
def invoke(self, context, event):
|
||||
#obj = context.object
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self, width = 200)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.label(text = 'Add Control Bones')
|
||||
row = layout.row()
|
||||
row.prop(self, 'parents') #text = 'Size'
|
||||
row.prop(self, 'targets')
|
||||
|
||||
layout.separator()
|
||||
col = layout.column()
|
||||
col.prop(self, 'keep_hierarchy')
|
||||
row = layout.row()
|
||||
row.prop(self, 'bone_align')
|
||||
if self.bone_align:
|
||||
row.prop(self, 'align_90', toggle=True)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.object
|
||||
targets = []
|
||||
parents = []
|
||||
bpy.ops.object.mode_set(mode = 'EDIT')
|
||||
edit_bones = bpy.context.selected_editable_bones
|
||||
#create list of parent and target objects
|
||||
for bone in edit_bones:
|
||||
bone.use_connect = False
|
||||
if self.targets:
|
||||
targets.append(target(bone))
|
||||
if self.parents:
|
||||
parents.append(parent(bone))
|
||||
|
||||
controls = add_controlers(self, obj, parents, targets)
|
||||
|
||||
bpy.ops.object.mode_set(mode = 'POSE')
|
||||
|
||||
#Add the bone group for the ctrls if doesn't exist
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
bone_groups = obj.pose.bone_groups
|
||||
if 'Ctrl Bones' not in bone_groups:
|
||||
ctrl_group = bone_groups.new(name = 'Ctrl Bones')
|
||||
ctrl_group.color_set = 'THEME01'
|
||||
else:
|
||||
ctrl_group = bone_groups['Ctrl Bones']
|
||||
|
||||
for bone in controls:
|
||||
posebone = obj.pose.bones[bone.ctrl]
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
posebone.bone_group = ctrl_group
|
||||
else:
|
||||
posebone.color.palette = 'THEME01'
|
||||
|
||||
if self.targets:
|
||||
for bone in targets:
|
||||
#update from the controls set
|
||||
ctrl = find_ctrl(bone, controls)
|
||||
posebone = obj.pose.bones[bone.name]
|
||||
|
||||
con = posebone.constraints.new('STRETCH_TO')
|
||||
con.target = obj
|
||||
|
||||
con.subtarget = ctrl
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
class MergeRigs(bpy.types.Operator):
|
||||
"""Merge selected rigs to active and keep hierarchy and constraints for shared bones"""
|
||||
bl_idname = "armature.merge"
|
||||
bl_label = "Merge_Rigs"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bpy.context.object.type == 'ARMATURE'
|
||||
|
||||
def execute(self, context):
|
||||
target_obj = context.object
|
||||
|
||||
if target_obj.type != 'ARMATURE':
|
||||
return {"CANCELLED"}
|
||||
|
||||
target_bones = set([bone.name for bone in target_obj.data.bones])
|
||||
constraints = []
|
||||
childrens = {}
|
||||
|
||||
#Store children and constraints
|
||||
bpy.ops.object.mode_set(mode = 'POSE')
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj.type != 'ARMATURE':
|
||||
continue
|
||||
if obj == target_obj:
|
||||
continue
|
||||
#create a set of all the similiar bones in all the rigs
|
||||
obj_bones = set([bone.name for bone in obj.data.bones])
|
||||
shared_bones = target_bones.intersection(obj_bones)
|
||||
|
||||
#find all the constraints and children
|
||||
for bone in obj.pose.bones:
|
||||
#store all the constraints
|
||||
for con in bone.constraints:
|
||||
if not hasattr(con, 'subtarget'):
|
||||
continue
|
||||
if con.target == obj and con.subtarget in shared_bones:
|
||||
constraints.append(constraint_dup(bone, con))
|
||||
if bone.name in shared_bones:
|
||||
for child in bone.children:
|
||||
if child.name in childrens:
|
||||
continue
|
||||
childrens.update({child.name : bone.name})
|
||||
|
||||
#remove shared bones
|
||||
bpy.ops.object.mode_set(mode = 'EDIT')
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj.type != 'ARMATURE':
|
||||
continue
|
||||
if obj == target_obj:
|
||||
continue
|
||||
for bone in shared_bones:
|
||||
if bone not in obj.data.edit_bones:
|
||||
continue
|
||||
obj.data.edit_bones.remove(obj.data.edit_bones[bone])
|
||||
|
||||
bpy.ops.object.mode_set(mode = 'POSE')
|
||||
bpy.ops.object.join()
|
||||
|
||||
#restore constraints
|
||||
for con_dup in constraints:
|
||||
if con_dup.bone in target_bones:
|
||||
continue
|
||||
if con_dup.bone not in target_obj.pose.bones:
|
||||
continue
|
||||
#print('constraint on ',con_dup.bone, con_dup.name)
|
||||
posebone = target_obj.pose.bones[con_dup.bone]
|
||||
if con_dup.name not in posebone.constraints:
|
||||
continue
|
||||
con = posebone.constraints[con_dup.name]
|
||||
con.target = target_obj
|
||||
con.subtarget = con_dup.subtarget
|
||||
|
||||
#reparent all child bones
|
||||
bpy.ops.object.mode_set(mode = 'EDIT')
|
||||
for child, parent in childrens.items():
|
||||
target_obj.data.edit_bones[child].parent = target_obj.data.edit_bones[parent]
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
classes = (MergeRigs,BboneWidgets, ChainControls)
|
||||
def register():
|
||||
from bpy.utils import register_class
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
# bpy.utils.register_class(BboneWidgets)
|
||||
# bpy.utils.register_class(ChainControls)
|
||||
# bpy.utils.register_class(RiggerToolBox_PT_Panel)
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
||||
# bpy.utils.unregister_class(BboneWidgets)
|
||||
# bpy.utils.unregister_class(ChainControls)
|
||||
# bpy.utils.unregister_class(RiggerToolBox_PT_Panel)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,527 @@
|
||||
# ***** 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, 0, 7, 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)
|
||||
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),
|
||||
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 2),
|
||||
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 3),
|
||||
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 4),
|
||||
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 5),
|
||||
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 6),
|
||||
('ROOT', 'Root', 'Root Ctrl for all the setups', 7),
|
||||
('EMPTY', 'Root', 'Root Ctrl for all the setups', 8),
|
||||
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 9),
|
||||
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 10)])
|
||||
|
||||
#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 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),
|
||||
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 2),
|
||||
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 3),
|
||||
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 4),
|
||||
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 5),
|
||||
('ROOT', 'Root', 'Root Ctrl for all the setups', 6),
|
||||
('EMPTY', 'Root', 'Root Ctrl for all the setups', 8),
|
||||
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 9),
|
||||
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 10),
|
||||
])
|
||||
|
||||
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'})
|
||||
|
||||
motion_path: bpy.props.BoolProperty(name = "Motion Path", description = "Flag when Motion Path is on", default = False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_settings: bpy.props.BoolProperty(name = "Motion Path Settings", description = "Open the settings Menu", default = False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_keyframe_scale: bpy.props.FloatProperty(name = "Scale Selecgted Keyframes Bounding Box", description = "Change the scale of the bounding box around the selected keyframes ", default = 0.1, step = 0.1, precision = 3)
|
||||
mp_color_before: bpy.props.FloatVectorProperty(name="Motion Path Before Color", subtype='COLOR', default=(1.0, 0.0, 0.0), min=0.0, max=1.0, description="Motion path color before the current frame")
|
||||
mp_color_after: bpy.props.FloatVectorProperty(name="Motion Path After Color", subtype='COLOR', default=(0.0, 1.0, 0.0), min=0.0, max=1.0, description="Motion path color before the current frame")
|
||||
mp_infront: bpy.props.BoolProperty(name = "Motion Path In Front", description = "Display motion path in front of all the objects", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_points: bpy.props.BoolProperty(name = "Motion Path Points", description = "Display motion path points", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_lines: bpy.props.BoolProperty(name = "Motion Path Lines", description = "Display motion path lines", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_handles: bpy.props.BoolProperty(name = "Motion Path Handles", description = "Display motion path handles on keyframe selection", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_display_frames: bpy.props.BoolProperty(name = "Frame Numbers", description = "Display frame numbers on all the keyframes", default = False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_handle_types: bpy.props.EnumProperty(name = 'Set Keyframe Handle Type', description="Set handle type for selected keyframes", default = 'AUTO', update = emp.update_handle_type_prop,
|
||||
items = [('FREE', 'Free', 'Free', 'HANDLE_FREE', 0),
|
||||
('ALIGNED','Aligned', 'Aligned', 'HANDLE_ALIGNED', 1),
|
||||
('VECTOR', 'Vector', 'Vector', 'HANDLE_VECTOR', 2),
|
||||
('AUTO','Automatic', 'Automatic', 'HANDLE_AUTO', 3),
|
||||
('AUTO_CLAMPED','Auto Clamped', 'Auto Clamped', 'HANDLE_AUTOCLAMPED', 4)])
|
||||
|
||||
mp_interpolation: bpy.props.EnumProperty(name = 'Set Interpolation', description="Set Keyframe Interpolation", default = 'BEZIER', update = emp.update_interpolation_prop,
|
||||
items = [('BEZIER', 'Bezier', 'Bezier', 'IPO_BEZIER', 0),
|
||||
('LINEAR','Linear', 'Linear', 'IPO_LINEAR', 1),
|
||||
('CONSTANT', 'Constant', 'Constant', 'IPO_CONSTANT', 2)])
|
||||
|
||||
mp_frame_range: bpy.props.EnumProperty(name = 'Frame Range', description="Type of Frame Range", default = 'SCENE', #update = emp.mp_frame_range_update,
|
||||
items = [('KEYS_ALL', 'All_Keys','Use the Scene Frame Length for the Range', 0),
|
||||
('SCENE', 'Scene','Use the Scene Frame Length for the Range', 1),
|
||||
('MANUAL', 'Manual','Custom Frame range using numerical input or the markers frame ranger', 2),
|
||||
('AROUND', 'Around Frames','Show only around the current frame', 3)])
|
||||
mp_before: bpy.props.IntProperty(name = "Before", description = "Show the frames Before the current frame", min = 0, default = 10)
|
||||
mp_after: bpy.props.IntProperty(name = "After", description = "Show the frames After the current frame", min = 0, default = 10)
|
||||
selected_keyframes: bpy.props.StringProperty(name="Selected Keyframes", description="Serialized representation of selected keyframes")
|
||||
|
||||
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 Keyframe','Paste Matrix to only selected keyframes', 1),
|
||||
('RANGE', 'Frame Range','Paste Matrix to a Frame Range', 2)])
|
||||
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)
|
||||
|
||||
# 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()
|
||||
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.animtoolbox.motion_path:
|
||||
scene.animtoolbox.motion_path = False
|
||||
if 'mp_dh' in dns:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
|
||||
bpy.app.driver_namespace.pop('mp_dh')
|
||||
|
||||
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_frame_change in bpy.app.handlers.frame_change_post:
|
||||
bpy.app.handlers.frame_change_post.remove(emp.mp_frame_change)
|
||||
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
|
||||
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.animtoolbox.motion_path:
|
||||
scene.animtoolbox.motion_path = False
|
||||
if 'mp_dh' in dns:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
|
||||
bpy.app.driver_namespace.pop('mp_dh')
|
||||
|
||||
Tools.selection_order(self, context)
|
||||
|
||||
classes = (TempCtrlsItems, 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()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"last_check": "2025-06-19 15:30:39.178174",
|
||||
"backup_date": "June-19-2025",
|
||||
"update_ready": false,
|
||||
"ignore": false,
|
||||
"just_restored": false,
|
||||
"just_updated": false,
|
||||
"version_text": {}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
# ***** 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 *****
|
||||
|
||||
import bpy
|
||||
|
||||
class GizmoSizeUp(bpy.types.Operator):
|
||||
"""Share keyframes between all the selected objects and bones"""
|
||||
bl_idname = "view3d.gizmo_size_up"
|
||||
bl_label = "Gizmo_Size_Up"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
context.preferences.view.gizmo_size += context.scene.animtoolbox.gizmo_size
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
class GizmoSizeDown(bpy.types.Operator):
|
||||
"""Share keyframes between all the selected objects and bones"""
|
||||
bl_idname = "view3d.gizmo_size_down"
|
||||
bl_label = "Gizmo_Size_Down"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
context.preferences.view.gizmo_size -= context.scene.animtoolbox.gizmo_size
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
def clear_isolate_pose_mode(scene):
|
||||
if not len(scene.animtoolbox.isolated):
|
||||
return
|
||||
for obj in scene.animtoolbox.isolated:
|
||||
if not obj.hidden:
|
||||
continue
|
||||
obj.hidden.hide_set(False)
|
||||
scene.animtoolbox.isolated.clear()
|
||||
scene.animtoolbox.active_obj = None
|
||||
|
||||
def isolate_pose_mode(scene):
|
||||
context = bpy.context
|
||||
#return when going out of isolate pose or when active object is not in pose mode
|
||||
if not scene.animtoolbox.isolate_pose_mode or context.active_object.mode != 'POSE':
|
||||
clear_isolate_pose_mode(scene)
|
||||
return
|
||||
|
||||
#handler continue only if the active object is None otherwise it collects all armature objects
|
||||
if scene.animtoolbox.active_obj is None:
|
||||
scene.animtoolbox.active_obj = context.active_object
|
||||
else:
|
||||
return
|
||||
|
||||
isolated = scene.animtoolbox.isolated
|
||||
for obj in context.view_layer.objects:
|
||||
if obj.type != 'ARMATURE':
|
||||
continue
|
||||
if obj.hide_get():
|
||||
continue
|
||||
rig = isolated.add()
|
||||
if obj.mode == 'POSE':
|
||||
rig.selected = obj
|
||||
else:
|
||||
rig.hidden = obj
|
||||
obj.hide_set(True)
|
||||
|
||||
class IsolatePoseMode(bpy.types.Operator):
|
||||
"""Isolates armatures during pose mode"""
|
||||
bl_idname = "anim.isolate_pose_mode"
|
||||
bl_label = "Isolate Pose Mode"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
# If the modal is already running, then don't run it the second time
|
||||
scene = context.scene
|
||||
if scene.animtoolbox.isolate_pose_mode:
|
||||
if isolate_pose_mode in bpy.app.handlers.depsgraph_update_pre:
|
||||
clear_isolate_pose_mode(scene)
|
||||
bpy.app.handlers.depsgraph_update_pre.remove(isolate_pose_mode)
|
||||
scene.animtoolbox.isolate_pose_mode = False
|
||||
return {'FINISHED'}
|
||||
|
||||
scene.animtoolbox.isolate_pose_mode = True
|
||||
isolate_pose_mode(scene)
|
||||
if isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
|
||||
bpy.app.handlers.depsgraph_update_pre.append(isolate_pose_mode)
|
||||
return {'FINISHED'}
|
||||
|
||||
class SwitchBoneCollectionsVisibility(bpy.types.Operator):
|
||||
"""Turn all bone collections visible and then press again to switch back"""
|
||||
bl_idname = "anim.switch_collections_visibility"
|
||||
bl_label = "Bone Collections Visibility"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bpy.app.version >= (4, 0, 0)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.object
|
||||
if obj.type != 'ARMATURE':
|
||||
return {'CANCELLED'}
|
||||
if not obj.animation_data:
|
||||
self.report({'INFO'}, 'No animation is available')
|
||||
return {'CANCELLED'}
|
||||
|
||||
if not obj.animation_data.action:
|
||||
self.report({'INFO'}, 'No animation is available')
|
||||
return {'CANCELLED'}
|
||||
|
||||
collections = obj.data.collections
|
||||
|
||||
if not len(collections):
|
||||
self.report({'INFO'}, 'No collections are available')
|
||||
return {'CANCELLED'}
|
||||
#check if there are collections that are marked with
|
||||
tagged_col = ['atb' in col.keys() for col in collections]
|
||||
atb_ui = context.scene.animtoolbox
|
||||
|
||||
if any(tagged_col) and atb_ui.col_vis:
|
||||
#collections are already marked so return to previous collection visibilty
|
||||
for col in collections:
|
||||
if 'atb' in col.keys():
|
||||
col.is_visible = col['atb']
|
||||
del col['atb']
|
||||
atb_ui.col_vis = False
|
||||
else:
|
||||
#Mark visible collections and turn collections with animated bones on
|
||||
animated_bones = set()
|
||||
start = 'pose.bones["'
|
||||
end = '"]'
|
||||
#get all the animated bones from the fcurves
|
||||
for fcu in obj.animation_data.action.fcurves:
|
||||
start_index = fcu.data_path.find(start)
|
||||
end_index = fcu.data_path.find(end)
|
||||
#if it's not a posebone fcurve then skip
|
||||
if start_index == -1 or end_index == -1:
|
||||
continue
|
||||
animated_bones.add(fcu.data_path[start_index + len(start):end_index])
|
||||
|
||||
#check if the collecetion that is turned off has animated bones
|
||||
find_anim = []
|
||||
for col in collections:
|
||||
for bone in col.bones:
|
||||
if bone.name in animated_bones and not col.is_visible:
|
||||
# print(bone.name, 'in ', col.name)
|
||||
find_anim.append(col)
|
||||
break
|
||||
|
||||
if not find_anim:
|
||||
self.report({'INFO'}, 'No collections with animated bones and no visibility are found')
|
||||
return {'CANCELLED'}
|
||||
|
||||
#Turn on collections without visiblity
|
||||
for col in collections:
|
||||
if col in find_anim:
|
||||
#tag visibility
|
||||
col['atb'] = col.is_visible
|
||||
col.is_visible = True
|
||||
|
||||
atb_ui.col_vis = True
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
classes = (GizmoSizeUp, GizmoSizeDown, IsolatePoseMode, SwitchBoneCollectionsVisibility)
|
||||
|
||||
def register():
|
||||
from bpy.utils import register_class
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
||||
@@ -0,0 +1,549 @@
|
||||
# ***** 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 *****
|
||||
|
||||
import bpy
|
||||
from mathutils import Matrix, Vector
|
||||
from math import radians
|
||||
import numpy
|
||||
|
||||
def draw_wgt(boneLength, bone):
|
||||
suffix = bone.id_data.name + '_' + bone.name
|
||||
if 'WGTB_object' + suffix in bpy.data.objects:
|
||||
obj = bpy.data.objects['WGTB_object'] + suffix
|
||||
if 'WGTB_shape' + suffix in obj.data.name:
|
||||
return obj
|
||||
mesh = bpy.data.meshes.new('WGTB_shape_' + suffix)
|
||||
obj = bpy.data.objects.new('WGTB_object_' + suffix, mesh)
|
||||
#coordinates of the sphere widget shape
|
||||
sphere = {"edges": [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10], [10, 11], [11, 12], [12, 13], [13, 14], [14, 15], [15, 16], [16, 17], [17, 18], [18, 19], [19, 20], [20, 21], [21, 22], [22, 23], [0, 23], [24, 25], [25, 26], [26, 27], [27, 28], [28, 29], [29, 30], [30, 31], [31, 32], [32, 33], [33, 34], [34, 35], [35, 36], [36, 37], [37, 38], [38, 39], [39, 40], [40, 41], [41, 42], [42, 43], [43, 44], [44, 45], [45, 46], [46, 47], [24, 47], [48, 49], [49, 50], [50, 51], [51, 52], [52, 53], [53, 54], [54, 55], [55, 56], [56, 57], [57, 58], [58, 59], [59, 60], [60, 61], [61, 62], [62, 63], [63, 64], [64, 65], [65, 66], [66, 67], [67, 68], [68, 69], [69, 70], [70, 71], [48, 71]],
|
||||
"vertices": [[0.0, 0.10000002384185791, 0.0], [-0.025881901383399963, 0.09659260511398315, 0.0], [-0.050000011920928955, 0.08660250902175903, 0.0], [-0.07071065902709961, 0.07071065902709961, 0.0], [-0.08660256862640381, 0.04999998211860657, 0.0], [-0.09659260511398315, 0.025881901383399963, 0.0], [-0.10000002384185791, 7.549793679118011e-09, 0.0], [-0.09659260511398315, -0.02588188648223877, 0.0], [-0.08660256862640381, -0.04999998211860657, 0.0], [-0.07071071863174438, -0.07071065902709961, 0.0], [-0.050000011920928955, -0.08660250902175903, 0.0], [-0.02588193118572235, -0.09659260511398315, 0.0], [-3.894143674187944e-08, -0.10000002384185791, 0.0], [0.025881856679916382, -0.09659260511398315, 0.0], [0.04999995231628418, -0.08660256862640381, 0.0], [0.07071065902709961, -0.07071071863174438, 0.0], [0.08660250902175903, -0.05000004172325134, 0.0], [0.09659254550933838, -0.025881946086883545, 0.0], [0.10000002384185791, -4.649123752642481e-08, 0.0], [0.09659260511398315, 0.025881856679916382, 0.0], [0.08660256862640381, 0.04999995231628418, 0.0], [0.07071071863174438, 0.07071065902709961, 0.0], [0.05000007152557373, 0.08660250902175903, 0.0], [0.025881975889205933, 0.09659254550933838, 0.0], [0.0, 7.450580596923828e-09, 0.10000002384185791], [-0.025881901383399963, 7.450580596923828e-09, 0.09659260511398315], [-0.050000011920928955, 7.450580596923828e-09, 0.08660250902175903], [-0.07071065902709961, 7.450580596923828e-09, 0.07071065902709961], [-0.08660256862640381, 3.725290298461914e-09, 0.04999998211860657], [-0.09659260511398315, 1.862645149230957e-09, 0.025881901383399963], [-0.10000002384185791, 8.881784197001252e-16, 7.549793679118011e-09], [-0.09659260511398315, -1.862645149230957e-09, -0.02588188648223877], [-0.08660256862640381, -3.725290298461914e-09, -0.04999998211860657], [-0.07071071863174438, -7.450580596923828e-09, -0.07071065902709961], [-0.050000011920928955, -7.450580596923828e-09, -0.08660250902175903], [-0.02588193118572235, -7.450580596923828e-09, -0.09659260511398315], [-3.894143674187944e-08, -7.450580596923828e-09, -0.10000002384185791], [0.025881856679916382, -7.450580596923828e-09, -0.09659260511398315], [0.04999995231628418, -7.450580596923828e-09, -0.08660256862640381], [0.07071065902709961, -7.450580596923828e-09, -0.07071071863174438], [0.08660250902175903, -3.725290298461914e-09, -0.05000004172325134], [0.09659254550933838, -1.862645149230957e-09, -0.025881946086883545], [0.10000002384185791, -3.552713678800501e-15, -4.649123752642481e-08], [0.09659260511398315, 1.862645149230957e-09, 0.025881856679916382], [0.08660256862640381, 3.725290298461914e-09, 0.04999995231628418], [0.07071071863174438, 7.450580596923828e-09, 0.07071065902709961], [0.05000007152557373, 7.450580596923828e-09, 0.08660250902175903], [0.025881975889205933, 7.450580596923828e-09, 0.09659254550933838], [-7.450580596923828e-09, 4.440892098500626e-16, 0.10000002384185791], [-9.313225746154785e-09, -0.025881901383399963, 0.09659260511398315], [-1.1175870895385742e-08, -0.050000011920928955, 0.08660250902175903], [-1.4901161193847656e-08, -0.07071065902709961, 0.07071065902709961], [-7.450580596923828e-09, -0.08660256862640381, 0.04999998211860657], [-7.450580596923828e-09, -0.09659260511398315, 0.025881901383399963], [-7.450580596923828e-09, -0.10000002384185791, 7.549793679118011e-09], [-7.450580596923828e-09, -0.09659260511398315, -0.02588188648223877], [0.0, -0.08660256862640381, -0.04999998211860657], [0.0, -0.07071071863174438, -0.07071065902709961], [3.725290298461914e-09, -0.050000011920928955, -0.08660250902175903], [5.587935447692871e-09, -0.02588193118572235, -0.09659260511398315], [7.450577044210149e-09, -3.894143674187944e-08, -0.10000002384185791], [9.313225746154785e-09, 0.025881856679916382, -0.09659260511398315], [1.1175870895385742e-08, 0.04999995231628418, -0.08660256862640381], [1.4901161193847656e-08, 0.07071065902709961, -0.07071071863174438], [7.450580596923828e-09, 0.08660250902175903, -0.05000004172325134], [7.450580596923828e-09, 0.09659254550933838, -0.025881946086883545], [7.450580596923828e-09, 0.10000002384185791, -4.649123752642481e-08], [7.450580596923828e-09, 0.09659260511398315, 0.025881856679916382], [0.0, 0.08660256862640381, 0.04999995231628418], [0.0, 0.07071071863174438, 0.07071065902709961], [-3.725290298461914e-09, 0.05000007152557373, 0.08660250902175903], [-5.587935447692871e-09, 0.025881975889205933, 0.09659254550933838]], "faces": []}
|
||||
mesh.from_pydata(numpy.array(sphere['vertices'])*[boneLength, boneLength, boneLength] , sphere['edges'], sphere['faces'])
|
||||
|
||||
return obj
|
||||
|
||||
def add_driver(obj, posebone, control, target, path, multiply = ''):
|
||||
|
||||
if isinstance(target, tuple):
|
||||
attr = posebone.driver_add(target[0], target[1])
|
||||
else:
|
||||
attr = posebone.driver_add(target)
|
||||
|
||||
var = attr.driver.variables.new()
|
||||
|
||||
var.targets[0].id = obj
|
||||
var.targets[0].data_path = 'pose.bones["' + control +'"].'+ path
|
||||
attr.driver.expression = var.name + multiply
|
||||
|
||||
def dup_values(source, target):
|
||||
if hasattr(source, 'parent'):
|
||||
target.parent = source.parent
|
||||
for prop in dir(source):
|
||||
if not hasattr(target, prop):
|
||||
continue
|
||||
value = getattr(source, prop)
|
||||
if type(value) not in {int, float, bool, str, Vector, Matrix, bpy.types.Object}:
|
||||
continue
|
||||
if '__' in prop[:2] and '__' in prop[-2:]:
|
||||
continue
|
||||
if target.is_property_readonly(prop):
|
||||
continue
|
||||
setattr(target, prop, value)
|
||||
|
||||
return target
|
||||
|
||||
def dup_constraints(source, target):
|
||||
if not source.constraints.items():
|
||||
return
|
||||
for source_con in source.constraints:
|
||||
target_con = target.constraints.new(source_con.type)
|
||||
dup_values(source_con, target_con)
|
||||
|
||||
def add_vis_bone_con(obj, bone_vis_name, bone_wgt_name):
|
||||
bone_vis = obj.pose.bones[bone_vis_name]
|
||||
con = bone_vis.constraints.new('STRETCH_TO')
|
||||
con.target = obj
|
||||
con.subtarget = bone_wgt_name
|
||||
|
||||
return bone_vis
|
||||
|
||||
class target:
|
||||
def __init__(self, bone):
|
||||
self.name = bone.name
|
||||
self.point = tuple(bone.tail)
|
||||
self.ctrl = 'TRGT_' + bone.name
|
||||
if bone.parent:
|
||||
self.parent = bone.parent.name
|
||||
#print('assign parent to target ', self.name, self.ctrl, self.parent)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.point < other.point
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.point)
|
||||
|
||||
def __eq__(self, other):
|
||||
#if not isinstance(other, type(self)):
|
||||
# return NotImplemented
|
||||
return self.point == other.point
|
||||
|
||||
class parent:
|
||||
def __init__(self, bone):
|
||||
self.name = bone.name
|
||||
self.point = tuple(bone.head)
|
||||
self.ctrl = 'CTRL_' + bone.name
|
||||
if bone.parent:
|
||||
self.parent = bone.parent.name
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.point < other.point
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.point)
|
||||
|
||||
def __eq__(self, other):
|
||||
#if not isinstance(other, type(self)):
|
||||
# return NotImplemented
|
||||
return self.point == other.point
|
||||
|
||||
class constraint_dup:
|
||||
def __init__(self, bone, con):
|
||||
self.name = con.name
|
||||
self.target = con.target
|
||||
self.subtarget = con.subtarget
|
||||
self.bone = bone.name
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.bone)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.bone == other.bone
|
||||
|
||||
def bone_orientation(source, target, value):
|
||||
source.align_orientation(target)
|
||||
x, y, z = source.matrix.to_3x3().col
|
||||
R = (Matrix.Translation(source.head) @ Matrix.Rotation(radians(value), 4, x) @ Matrix.Translation(-source.head))
|
||||
source.transform(R, roll = False)
|
||||
source.align_roll(target.vector)
|
||||
|
||||
def find_ctrl(bone, controls):
|
||||
i = list(controls).index(bone)
|
||||
bone.ctrl = list(controls)[i].ctrl
|
||||
|
||||
return bone.ctrl
|
||||
|
||||
def add_controlers(self, obj, parents, targets):
|
||||
|
||||
#controls = set(parents).union(targets)
|
||||
controls = set(parents + targets)
|
||||
|
||||
#create hierarchy
|
||||
for bone in controls:
|
||||
editbone = obj.data.edit_bones[bone.name]
|
||||
if editbone.parent is None:
|
||||
continue
|
||||
parentnames = [bone.name for bone in parents]
|
||||
#if a target and its parent are part of the hierarchy then linked to its own bone parent
|
||||
if bone in targets and bone not in parents and editbone.parent.name in parentnames:
|
||||
parentbone = parent(editbone)
|
||||
else:
|
||||
parentbone = parent(editbone.parent)
|
||||
|
||||
if parentbone in controls and parentbone != bone:
|
||||
bone.parent = find_ctrl(parentbone, controls)
|
||||
else:
|
||||
bone.parent = editbone.parent.name
|
||||
|
||||
|
||||
edit_bones = obj.data.edit_bones
|
||||
for bone in controls:
|
||||
editbone = edit_bones[bone.name]
|
||||
ctrl = obj.data.edit_bones.new(bone.ctrl)
|
||||
ctrl.head = bone.point
|
||||
ctrl.tail = bone.point
|
||||
ctrl.tail[2] = bone.point[2] + (editbone.length / 3)
|
||||
ctrl.bbone_x = editbone.bbone_x
|
||||
ctrl.bbone_z = editbone.bbone_z
|
||||
ctrl.use_deform = False
|
||||
if self.bone_align:
|
||||
angle = 90 if self.align_90 else 0
|
||||
bone_orientation(ctrl, editbone, angle)
|
||||
if angle == 90:
|
||||
ctrl.align_roll(editbone.vector)
|
||||
else:
|
||||
ctrl.roll = editbone.roll
|
||||
|
||||
#apply hierarchy
|
||||
for bone in parents:
|
||||
editbone = edit_bones[bone.name]
|
||||
ctrl_name = find_ctrl(bone, controls)
|
||||
ctrl = edit_bones[ctrl_name]
|
||||
editbone.parent = ctrl
|
||||
|
||||
for bone in controls:
|
||||
ctrl = edit_bones[bone.ctrl]
|
||||
if hasattr(bone, 'parent'):
|
||||
ctrl.parent = edit_bones[bone.parent]
|
||||
|
||||
return controls
|
||||
|
||||
def pose_bbone_setup(bone, posebone, bbone_group = None):
|
||||
#add the custom shape to the widget bones
|
||||
custom_shape = draw_wgt(bone['length'], posebone)
|
||||
posebone.custom_shape = custom_shape
|
||||
posebone.use_custom_shape_bone_size = False
|
||||
if bbone_group:
|
||||
posebone.bone_group = bbone_group
|
||||
posebone.rotation_mode = 'XZY'
|
||||
posebone.lock_rotation[0] = True
|
||||
posebone.lock_rotation[2] = True
|
||||
|
||||
#####MAIN####
|
||||
class BboneWidgets(bpy.types.Operator):
|
||||
"""Add Bbone widget controls to the selected bones"""
|
||||
bl_idname = "armature.add_bbone_widgets"
|
||||
bl_label = "Add_Bbone_widgets"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bpy.context.object.type == 'ARMATURE'
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.object
|
||||
|
||||
obj.data.display_type = 'BBONE'
|
||||
|
||||
bones = []
|
||||
parentlayers = [False if i != 24 else True for i in range(32)]
|
||||
wgtlayers = [True if i == 0 else False for i in range(32)]
|
||||
|
||||
bpy.ops.object.mode_set(mode = 'EDIT')
|
||||
obj.data.use_mirror_x = False
|
||||
for bone in obj.data.edit_bones:
|
||||
if not bone.select:
|
||||
continue
|
||||
if bone.bbone_segments == 1:
|
||||
bone.bbone_segments = 10
|
||||
bone.bbone_handle_type_start = 'TANGENT'
|
||||
bone.bbone_handle_type_end = 'TANGENT'
|
||||
bone_name = bone.name
|
||||
#add parent bone to the Bbone widgets
|
||||
parent = obj.data.edit_bones.new('WGTB_parent_'+ bone_name)
|
||||
parent_name = parent.name
|
||||
dup_values(bone, parent)
|
||||
parent.name = parent_name
|
||||
parent.select = False
|
||||
parent.select_head = False
|
||||
parent.select_tail = False
|
||||
|
||||
#change layer of the parent bone
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
parent.layers = parentlayers
|
||||
|
||||
#add bbone widget bones
|
||||
head_widget = obj.data.edit_bones.new('Bhead_'+ bone.name)
|
||||
head_widget.parent = parent
|
||||
#head_widget.head = bone.head
|
||||
head_widget.head = bone.head + (bone.tail - bone.head) * 0.25
|
||||
#head_widget.tail = (bone.tail - bone.head)/10
|
||||
head_widget.length = bone.length * 0.1
|
||||
head_widget.bbone_x = bone.bbone_x
|
||||
head_widget.bbone_z = bone.bbone_z
|
||||
head_widget.align_orientation(bone)
|
||||
head_widget.inherit_scale = 'NONE'
|
||||
head_name = head_widget.name
|
||||
|
||||
tail_widget = obj.data.edit_bones.new('Btail_'+ bone.name)
|
||||
tail_widget.parent = parent
|
||||
#tail_widget.head = bone.tail
|
||||
tail_widget.head = bone.head + (bone.tail - bone.head) * 0.75
|
||||
#tail_widget.tail = bone.tail - (bone.tail - bone.head)/10
|
||||
tail_widget.length = bone.length * 0.1
|
||||
tail_widget.bbone_x = bone.bbone_x
|
||||
tail_widget.bbone_z = bone.bbone_z
|
||||
tail_widget.align_orientation(bone)
|
||||
tail_widget.inherit_scale = 'NONE'
|
||||
tail_name = tail_widget.name
|
||||
|
||||
#add vis bones
|
||||
head_vis = obj.data.edit_bones.new('Bhead_vis_'+ bone.name)
|
||||
head_vis.parent = parent
|
||||
head_vis.head = bone.head
|
||||
head_vis.tail = head_widget.head
|
||||
head_vis.bbone_x = bone.bbone_x*0.1
|
||||
head_vis.bbone_z = bone.bbone_z*0.1
|
||||
|
||||
#head_vis_name = head_vis.name
|
||||
head_vis.hide_select = True
|
||||
head_vis.use_deform = False
|
||||
tail_vis = obj.data.edit_bones.new('Btail_vis_'+ bone.name)
|
||||
tail_vis.parent = parent
|
||||
tail_vis.head = bone.tail
|
||||
tail_vis.tail = tail_widget.head
|
||||
tail_vis.bbone_x = bone.bbone_x*0.1
|
||||
tail_vis.bbone_z = bone.bbone_z*0.1
|
||||
|
||||
#tail_vis_name = tail_vis.name
|
||||
tail_vis.hide_select = True
|
||||
|
||||
tail_vis.use_deform = False
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
tail_widget.layers = wgtlayers
|
||||
head_widget.layers = wgtlayers
|
||||
head_vis.layers = wgtlayers
|
||||
tail_vis.layers = wgtlayers
|
||||
|
||||
bones.append({'name': bone_name, 'parent': parent_name, 'head': head_name, 'tail': tail_name, 'head_vis': head_vis.name, 'tail_vis': tail_vis.name, 'length': bone.length})
|
||||
|
||||
#####POSE MODE#######
|
||||
bpy.ops.object.mode_set(mode = 'POSE')
|
||||
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
bone_groups = obj.pose.bone_groups
|
||||
if 'BBone Widgets' not in bone_groups:
|
||||
bbone_group = bone_groups.new(name = 'BBone Widgets')
|
||||
bbone_group.color_set = 'THEME09'
|
||||
else:
|
||||
bbone_group = bone_groups['BBone Widgets']
|
||||
else:
|
||||
bbone_group = None
|
||||
|
||||
for bone in bones:
|
||||
posebone = obj.pose.bones[bone['name']]
|
||||
# Prepare parent bone in pose mode
|
||||
poseparent = obj.pose.bones[bone['parent']]
|
||||
#disable use deform
|
||||
obj.data.bones[bone['parent']].use_deform = False
|
||||
obj.data.bones[bone['head']].use_deform = False
|
||||
obj.data.bones[bone['tail']].use_deform = False
|
||||
|
||||
pose_bbone_setup(bone, obj.pose.bones[bone['head']], bbone_group)
|
||||
pose_bbone_setup(bone, obj.pose.bones[bone['tail']], bbone_group)
|
||||
|
||||
dup_constraints(posebone, poseparent)
|
||||
|
||||
#add all the drivers
|
||||
add_driver(obj, posebone, bone['head'], 'bbone_curveinx', 'location.x')
|
||||
add_driver(obj, posebone, bone['head'], 'bbone_curveinz', 'location.z')
|
||||
add_driver(obj, posebone, bone['head'], 'bbone_easein', 'location.y', '*5/'+ str(bone['length']))
|
||||
add_driver(obj, posebone, bone['head'], 'bbone_rollin', 'rotation_euler.y')
|
||||
add_driver(obj, posebone, bone['head'], ('bbone_scalein', 0), 'scale.x')
|
||||
add_driver(obj, posebone, bone['head'], ('bbone_scalein', 1), 'scale.y')
|
||||
add_driver(obj, posebone, bone['head'], ('bbone_scalein', 2), 'scale.z')
|
||||
|
||||
add_driver(obj, posebone, bone['tail'], 'bbone_curveoutx', 'location.x')
|
||||
add_driver(obj, posebone, bone['tail'], 'bbone_curveoutz', 'location.z')
|
||||
add_driver(obj, posebone, bone['tail'], 'bbone_easeout', 'location.y', '*-5/'+ str(bone['length']))
|
||||
add_driver(obj, posebone, bone['tail'], 'bbone_rollout', 'rotation_euler.y')
|
||||
add_driver(obj, posebone, bone['tail'], ('bbone_scaleout', 0), 'scale.x')
|
||||
add_driver(obj, posebone, bone['tail'], ('bbone_scaleout', 1), 'scale.y')
|
||||
add_driver(obj, posebone, bone['tail'], ('bbone_scaleout', 2), 'scale.z')
|
||||
|
||||
#add constraints to visual bones
|
||||
head_vis = add_vis_bone_con(obj, bone['head_vis'], bone['head'])
|
||||
tail_vis = add_vis_bone_con(obj, bone['tail_vis'], bone['tail'])
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
head_vis.bone_group = bbone_group
|
||||
tail_vis.bone_group = bbone_group
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
class ChainControls(bpy.types.Operator):
|
||||
"""Add parent and target controls to the selected bones to create a chain control"""
|
||||
bl_idname = "armature.add_chain_ctrls"
|
||||
bl_label = "Add_Chain_Controls"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
parents: bpy.props.BoolProperty(name = 'Add Parents', description = "Align the controls 90 degrees to the original bones", default = True)
|
||||
targets: bpy.props.BoolProperty(name = 'Add Targets', description = "Align the controls 90 degrees to the original bones", default = True)
|
||||
keep_hierarchy: bpy.props.BoolProperty(name = 'Keep Hierarchy', description = "Keep the controls in the hierarchy of the original bones", default = True)
|
||||
bone_align: bpy.props.BoolProperty(name = 'Align to Bones', description = "Align the controls to the original bones", default = True)
|
||||
align_90: bpy.props.BoolProperty(name = '+90°', description = "Align the controls 90 degrees to the original bones", default = True)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bpy.context.object.type == 'ARMATURE'
|
||||
def invoke(self, context, event):
|
||||
#obj = context.object
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self, width = 200)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.label(text = 'Add Control Bones')
|
||||
row = layout.row()
|
||||
row.prop(self, 'parents') #text = 'Size'
|
||||
row.prop(self, 'targets')
|
||||
|
||||
layout.separator()
|
||||
col = layout.column()
|
||||
col.prop(self, 'keep_hierarchy')
|
||||
row = layout.row()
|
||||
row.prop(self, 'bone_align')
|
||||
if self.bone_align:
|
||||
row.prop(self, 'align_90', toggle=True)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.object
|
||||
targets = []
|
||||
parents = []
|
||||
bpy.ops.object.mode_set(mode = 'EDIT')
|
||||
edit_bones = bpy.context.selected_editable_bones
|
||||
#create list of parent and target objects
|
||||
for bone in edit_bones:
|
||||
bone.use_connect = False
|
||||
if self.targets:
|
||||
targets.append(target(bone))
|
||||
if self.parents:
|
||||
parents.append(parent(bone))
|
||||
|
||||
controls = add_controlers(self, obj, parents, targets)
|
||||
|
||||
bpy.ops.object.mode_set(mode = 'POSE')
|
||||
|
||||
#Add the bone group for the ctrls if doesn't exist
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
bone_groups = obj.pose.bone_groups
|
||||
if 'Ctrl Bones' not in bone_groups:
|
||||
ctrl_group = bone_groups.new(name = 'Ctrl Bones')
|
||||
ctrl_group.color_set = 'THEME01'
|
||||
else:
|
||||
ctrl_group = bone_groups['Ctrl Bones']
|
||||
|
||||
for bone in controls:
|
||||
posebone = obj.pose.bones[bone.ctrl]
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
posebone.bone_group = ctrl_group
|
||||
else:
|
||||
posebone.color.palette = 'THEME01'
|
||||
|
||||
if self.targets:
|
||||
for bone in targets:
|
||||
#update from the controls set
|
||||
ctrl = find_ctrl(bone, controls)
|
||||
posebone = obj.pose.bones[bone.name]
|
||||
|
||||
con = posebone.constraints.new('STRETCH_TO')
|
||||
con.target = obj
|
||||
|
||||
con.subtarget = ctrl
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
class MergeRigs(bpy.types.Operator):
|
||||
"""Merge selected rigs to active and keep hierarchy and constraints for shared bones"""
|
||||
bl_idname = "armature.merge"
|
||||
bl_label = "Merge_Rigs"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bpy.context.object.type == 'ARMATURE'
|
||||
|
||||
def execute(self, context):
|
||||
target_obj = context.object
|
||||
|
||||
if target_obj.type != 'ARMATURE':
|
||||
return {"CANCELLED"}
|
||||
|
||||
target_bones = set([bone.name for bone in target_obj.data.bones])
|
||||
constraints = []
|
||||
childrens = {}
|
||||
|
||||
#Store children and constraints
|
||||
bpy.ops.object.mode_set(mode = 'POSE')
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj.type != 'ARMATURE':
|
||||
continue
|
||||
if obj == target_obj:
|
||||
continue
|
||||
#create a set of all the similiar bones in all the rigs
|
||||
obj_bones = set([bone.name for bone in obj.data.bones])
|
||||
shared_bones = target_bones.intersection(obj_bones)
|
||||
|
||||
#find all the constraints and children
|
||||
for bone in obj.pose.bones:
|
||||
#store all the constraints
|
||||
for con in bone.constraints:
|
||||
if not hasattr(con, 'subtarget'):
|
||||
continue
|
||||
if con.target == obj and con.subtarget in shared_bones:
|
||||
constraints.append(constraint_dup(bone, con))
|
||||
if bone.name in shared_bones:
|
||||
for child in bone.children:
|
||||
if child.name in childrens:
|
||||
continue
|
||||
childrens.update({child.name : bone.name})
|
||||
|
||||
#remove shared bones
|
||||
bpy.ops.object.mode_set(mode = 'EDIT')
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj.type != 'ARMATURE':
|
||||
continue
|
||||
if obj == target_obj:
|
||||
continue
|
||||
for bone in shared_bones:
|
||||
if bone not in obj.data.edit_bones:
|
||||
continue
|
||||
obj.data.edit_bones.remove(obj.data.edit_bones[bone])
|
||||
|
||||
bpy.ops.object.mode_set(mode = 'POSE')
|
||||
bpy.ops.object.join()
|
||||
|
||||
#restore constraints
|
||||
for con_dup in constraints:
|
||||
if con_dup.bone in target_bones:
|
||||
continue
|
||||
if con_dup.bone not in target_obj.pose.bones:
|
||||
continue
|
||||
#print('constraint on ',con_dup.bone, con_dup.name)
|
||||
posebone = target_obj.pose.bones[con_dup.bone]
|
||||
if con_dup.name not in posebone.constraints:
|
||||
continue
|
||||
con = posebone.constraints[con_dup.name]
|
||||
con.target = target_obj
|
||||
con.subtarget = con_dup.subtarget
|
||||
|
||||
#reparent all child bones
|
||||
bpy.ops.object.mode_set(mode = 'EDIT')
|
||||
for child, parent in childrens.items():
|
||||
target_obj.data.edit_bones[child].parent = target_obj.data.edit_bones[parent]
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
classes = (MergeRigs,BboneWidgets, ChainControls)
|
||||
def register():
|
||||
from bpy.utils import register_class
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
# bpy.utils.register_class(BboneWidgets)
|
||||
# bpy.utils.register_class(ChainControls)
|
||||
# bpy.utils.register_class(RiggerToolBox_PT_Panel)
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
||||
# bpy.utils.unregister_class(BboneWidgets)
|
||||
# bpy.utils.unregister_class(ChainControls)
|
||||
# bpy.utils.unregister_class(RiggerToolBox_PT_Panel)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,527 @@
|
||||
# ***** 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, 0, 7, 1),
|
||||
"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)
|
||||
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),
|
||||
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 2),
|
||||
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 3),
|
||||
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 4),
|
||||
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 5),
|
||||
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 6),
|
||||
('ROOT', 'Root', 'Root Ctrl for all the setups', 7),
|
||||
('EMPTY', 'Root', 'Root Ctrl for all the setups', 8),
|
||||
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 9),
|
||||
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 10)])
|
||||
|
||||
#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 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),
|
||||
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 2),
|
||||
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 3),
|
||||
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 4),
|
||||
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 5),
|
||||
('ROOT', 'Root', 'Root Ctrl for all the setups', 6),
|
||||
('EMPTY', 'Root', 'Root Ctrl for all the setups', 8),
|
||||
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 9),
|
||||
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 10),
|
||||
])
|
||||
|
||||
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'})
|
||||
|
||||
motion_path: bpy.props.BoolProperty(name = "Motion Path", description = "Flag when Motion Path is on", default = False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_settings: bpy.props.BoolProperty(name = "Motion Path Settings", description = "Open the settings Menu", default = False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_keyframe_scale: bpy.props.FloatProperty(name = "Scale Selecgted Keyframes Bounding Box", description = "Change the scale of the bounding box around the selected keyframes ", default = 0.1, step = 0.1, precision = 3)
|
||||
mp_color_before: bpy.props.FloatVectorProperty(name="Motion Path Before Color", subtype='COLOR', default=(1.0, 0.0, 0.0), min=0.0, max=1.0, description="Motion path color before the current frame")
|
||||
mp_color_after: bpy.props.FloatVectorProperty(name="Motion Path After Color", subtype='COLOR', default=(0.0, 1.0, 0.0), min=0.0, max=1.0, description="Motion path color before the current frame")
|
||||
mp_infront: bpy.props.BoolProperty(name = "Motion Path In Front", description = "Display motion path in front of all the objects", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_points: bpy.props.BoolProperty(name = "Motion Path Points", description = "Display motion path points", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_lines: bpy.props.BoolProperty(name = "Motion Path Lines", description = "Display motion path lines", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_handles: bpy.props.BoolProperty(name = "Motion Path Handles", description = "Display motion path handles on keyframe selection", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_display_frames: bpy.props.BoolProperty(name = "Frame Numbers", description = "Display frame numbers on all the keyframes", default = False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_handle_types: bpy.props.EnumProperty(name = 'Set Keyframe Handle Type', description="Set handle type for selected keyframes", default = 'AUTO', update = emp.update_handle_type_prop,
|
||||
items = [('FREE', 'Free', 'Free', 'HANDLE_FREE', 0),
|
||||
('ALIGNED','Aligned', 'Aligned', 'HANDLE_ALIGNED', 1),
|
||||
('VECTOR', 'Vector', 'Vector', 'HANDLE_VECTOR', 2),
|
||||
('AUTO','Automatic', 'Automatic', 'HANDLE_AUTO', 3),
|
||||
('AUTO_CLAMPED','Auto Clamped', 'Auto Clamped', 'HANDLE_AUTOCLAMPED', 4)])
|
||||
|
||||
mp_interpolation: bpy.props.EnumProperty(name = 'Set Interpolation', description="Set Keyframe Interpolation", default = 'BEZIER', update = emp.update_interpolation_prop,
|
||||
items = [('BEZIER', 'Bezier', 'Bezier', 'IPO_BEZIER', 0),
|
||||
('LINEAR','Linear', 'Linear', 'IPO_LINEAR', 1),
|
||||
('CONSTANT', 'Constant', 'Constant', 'IPO_CONSTANT', 2)])
|
||||
|
||||
mp_frame_range: bpy.props.EnumProperty(name = 'Frame Range', description="Type of Frame Range", default = 'SCENE', #update = emp.mp_frame_range_update,
|
||||
items = [('KEYS_ALL', 'All_Keys','Use the Scene Frame Length for the Range', 0),
|
||||
('SCENE', 'Scene','Use the Scene Frame Length for the Range', 1),
|
||||
('MANUAL', 'Manual','Custom Frame range using numerical input or the markers frame ranger', 2),
|
||||
('AROUND', 'Around Frames','Show only around the current frame', 3)])
|
||||
mp_before: bpy.props.IntProperty(name = "Before", description = "Show the frames Before the current frame", min = 0, default = 10)
|
||||
mp_after: bpy.props.IntProperty(name = "After", description = "Show the frames After the current frame", min = 0, default = 10)
|
||||
selected_keyframes: bpy.props.StringProperty(name="Selected Keyframes", description="Serialized representation of selected keyframes")
|
||||
|
||||
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 Keyframe','Paste Matrix to only selected keyframes', 1),
|
||||
('RANGE', 'Frame Range','Paste Matrix to a Frame Range', 2)])
|
||||
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)
|
||||
|
||||
# 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()
|
||||
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.animtoolbox.motion_path:
|
||||
scene.animtoolbox.motion_path = False
|
||||
if 'mp_dh' in dns:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
|
||||
bpy.app.driver_namespace.pop('mp_dh')
|
||||
|
||||
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_frame_change in bpy.app.handlers.frame_change_post:
|
||||
bpy.app.handlers.frame_change_post.remove(emp.mp_frame_change)
|
||||
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
|
||||
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.animtoolbox.motion_path:
|
||||
scene.animtoolbox.motion_path = False
|
||||
if 'mp_dh' in dns:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
|
||||
bpy.app.driver_namespace.pop('mp_dh')
|
||||
|
||||
Tools.selection_order(self, context)
|
||||
|
||||
classes = (TempCtrlsItems, 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()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"last_check": "2025-06-19 15:30:39.178174",
|
||||
"backup_date": "",
|
||||
"update_ready": true,
|
||||
"ignore": false,
|
||||
"just_restored": false,
|
||||
"just_updated": false,
|
||||
"version_text": {
|
||||
"link": "https://gitlab.com/api/v4/projects/45739913/repository/archive.zip?sha=198933935077e89aa87550163d3747388f2552e8",
|
||||
"version": [
|
||||
0,
|
||||
0,
|
||||
7,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,527 @@
|
||||
# ***** 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 *****
|
||||
|
||||
import bpy
|
||||
import random
|
||||
import numpy as np
|
||||
from mathutils import Quaternion
|
||||
from . import Tools
|
||||
|
||||
def attr_default(obj, fcu_key):
|
||||
#check if the fcurve source belongs to a bone or obj
|
||||
if fcu_key[0][:10] == 'pose.bones':
|
||||
transform = fcu_key[0].split('.')[-1]
|
||||
attr = fcu_key[0].split('"')[-2]
|
||||
bone = fcu_key[0].split('"')[1]
|
||||
|
||||
if bone in obj.pose.bones:
|
||||
source = obj.pose.bones[bone]
|
||||
#if the bone not found still calculate the default based on the path
|
||||
elif '.rotation_quaternion' in fcu_key[0]:
|
||||
return [1.0, 0.0, 0.0, 0.0]
|
||||
elif '.scale' in fcu_key[0]:
|
||||
return [1.0, 1.0, 1.0]
|
||||
else:
|
||||
return [0]
|
||||
|
||||
#in case of shapekey animation
|
||||
elif fcu_key[0][:10] == 'key_blocks':
|
||||
attr = fcu_key[0].split('"')[1]
|
||||
if attr not in obj.data.shape_keys.key_blocks:
|
||||
return [0]
|
||||
shapekey = obj.data.shape_keys.key_blocks[attr]
|
||||
return 0 if shapekey.slider_min <= 0 else shapekey.slider_min
|
||||
#in case of transforms in object mode
|
||||
else:# fcu_key[0] in transform_types:
|
||||
source = obj
|
||||
transform = fcu_key[0]
|
||||
|
||||
#check when it's transform property of Blender
|
||||
if transform in source.bl_rna.properties.keys():
|
||||
if hasattr(source.bl_rna.properties[transform], 'default_array'):
|
||||
if len(source.bl_rna.properties[transform].default_array) > fcu_key[1]:
|
||||
attrvalue = source.bl_rna.properties[transform].default_array
|
||||
return attrvalue
|
||||
|
||||
#in case of property on object
|
||||
elif len(fcu_key[0].split('"')) > 1:
|
||||
if fcu_key[0].split('"')[1] in obj.keys():
|
||||
attr = fcu_key[0].split('"')[1]
|
||||
|
||||
if 'attr' not in locals():
|
||||
return [0]
|
||||
|
||||
#since blender 3 access to custom property settings changed
|
||||
if attr in source:
|
||||
if not isinstance(source[attr], float) and not isinstance(source[attr], int):
|
||||
return [0]
|
||||
id_attr = source.id_properties_ui(attr).as_dict()
|
||||
attrvalue = id_attr['default']
|
||||
return [attrvalue]
|
||||
|
||||
return [0]
|
||||
|
||||
def store_handles(key):
|
||||
#storing the distance between the handles bezier to the key value
|
||||
handle_r = key.handle_right[1] - key.co[1]
|
||||
handle_l = key.handle_left[1] - key.co[1]
|
||||
|
||||
return handle_r, handle_l
|
||||
|
||||
def apply_handles(key, handle_r, handle_l):
|
||||
key.handle_right[1] = key.co[1] + handle_r
|
||||
key.handle_left[1] = key.co[1] + handle_l
|
||||
|
||||
def selected_bones_filter(obj, fcu_data_path):
|
||||
if not bpy.context.window_manager.atb_ui.multikey.selectedbones:
|
||||
#if not obj.als.onlyselected:
|
||||
return False
|
||||
if obj.mode != 'POSE':
|
||||
return True
|
||||
transform_types = ['location', 'rotation_euler', 'rotation_quaternion', 'scale']
|
||||
#filter selected bones if option is turned on
|
||||
bones = [bone.path_from_id() for bone in bpy.context.selected_pose_bones]
|
||||
if fcu_data_path.split('].')[0]+']' not in bones and fcu_data_path not in transform_types:
|
||||
return True
|
||||
|
||||
# def filter_properties(obj, fcu):
|
||||
# 'Filter the W X Y Z attributes of the transform properties'
|
||||
# transform = fcu.data_path.split('"].')[1] if obj.mode == 'POSE' else fcu.data_path
|
||||
# index = fcu.array_index
|
||||
# if 'rotation' in transform:
|
||||
# if transform == 'rotation_euler':
|
||||
# index -= 1
|
||||
# transform = 'rotation'
|
||||
# transform = 'filter_' + transform
|
||||
# if not hasattr(bpy.context.scene.multikey, transform):
|
||||
# return False
|
||||
# attr = getattr(bpy.context.scene.multikey, transform)
|
||||
# #print(fcu.data_path, index, transform, attr[index])
|
||||
# return True if attr[index] else False
|
||||
|
||||
def add_value(key, value):
|
||||
if key.select_control_point:
|
||||
#store handle values in relative to the keyframe value
|
||||
handle_r, handle_l = store_handles(key)
|
||||
|
||||
key.co[1] += value
|
||||
apply_handles(key, handle_r, handle_l)
|
||||
|
||||
#calculate the difference between current value and the fcurve value
|
||||
def add_diff(fcurves, path, current_value, eval_array):
|
||||
array_value = current_value - eval_array
|
||||
if not any(array_value):
|
||||
return
|
||||
|
||||
for i, value in enumerate(array_value):
|
||||
fcu = fcurves.find(path, index = i)
|
||||
if fcu is None or Tools.filter_properties(bpy.context.scene.animtoolbox, fcu):
|
||||
continue
|
||||
for key in fcu.keyframe_points:
|
||||
add_value(key, value)
|
||||
fcu.update()
|
||||
|
||||
class ScaleValuesOp(bpy.types.Operator):
|
||||
"""Modal operator used while scale value is running before release"""
|
||||
bl_idname = "animtoolbox.multikey_scale_value"
|
||||
bl_label = "Scale Values"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
#reset the values for dragging
|
||||
self.stop = False
|
||||
ui = context.window_manager.atb_ui
|
||||
ui['is_dragging'] = True
|
||||
self.avg_value = dict()
|
||||
#dictionary of the keyframes and their original INITIAL values
|
||||
self.keyframes_values = dict()
|
||||
self.keyframes_handle_right = dict()
|
||||
self.keyframes_handle_left = dict()
|
||||
|
||||
#the average value for each fcurve
|
||||
self.keyframes_avg_value = dict()
|
||||
|
||||
for obj in context.selected_objects:
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
for fcu in fcurves:
|
||||
if obj.mode == 'POSE':
|
||||
if selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
if Tools.filter_properties(context.scene.animtoolbox, fcu):
|
||||
continue
|
||||
|
||||
#avg and value list per fcurve
|
||||
avg_value = []
|
||||
value_list = []
|
||||
for key in fcu.keyframe_points:
|
||||
if key.select_control_point:
|
||||
value_list.append(key.co[1])
|
||||
self.keyframes_values.update({key : key.co[1]})
|
||||
self.keyframes_handle_right.update({key : key.handle_right[1]})
|
||||
self.keyframes_handle_left.update({key : key.handle_left[1]})
|
||||
|
||||
if len(value_list)>1:
|
||||
#the average value with the scale property added to it
|
||||
avg_value = sum(value_list) / len(value_list)
|
||||
|
||||
for key in fcu.keyframe_points:
|
||||
if key.select_control_point:
|
||||
self.keyframes_avg_value.update({key : avg_value})
|
||||
|
||||
if not self.keyframes_avg_value:
|
||||
ui['is_dragging'] = False
|
||||
ui.multikey['scale'] = 1
|
||||
Tools.redraw_areas(['VIEW_3D'])
|
||||
return {'CANCELLED'}
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def modal(self, context, event):
|
||||
|
||||
ui = context.window_manager.atb_ui
|
||||
scale = ui.multikey.scale
|
||||
|
||||
#Quit the modal operator when the slider is released
|
||||
if self.stop:
|
||||
ui['is_dragging'] = False
|
||||
ui.multikey['scale'] = 1
|
||||
Tools.redraw_areas(['VIEW_3D'])
|
||||
#modal is being cancelled because of undo issue with the modal running through the property
|
||||
return {'FINISHED'}
|
||||
|
||||
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
|
||||
self.stop = True
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
for key, key_value in self.keyframes_values.items():
|
||||
if not key.select_control_point:
|
||||
continue
|
||||
if key not in self.keyframes_avg_value:
|
||||
continue
|
||||
avg_value = self.keyframes_avg_value[key]
|
||||
handle_right_value = self.keyframes_handle_right[key]
|
||||
handle_left_value = self.keyframes_handle_left[key]
|
||||
|
||||
#add the value of the distance from the average * scale factor
|
||||
key.co[1] = avg_value + ((key_value - avg_value)*scale)
|
||||
key.handle_right[1] = avg_value + ((handle_right_value - avg_value)*scale)
|
||||
key.handle_left[1] = avg_value + ((handle_left_value - avg_value)*scale)
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def scale_value(self, context):
|
||||
|
||||
ui = context.window_manager.atb_ui
|
||||
if ui.is_dragging:
|
||||
return
|
||||
obj = context.object
|
||||
|
||||
if obj is None:
|
||||
self['scale'] = 1
|
||||
return
|
||||
action = obj.animation_data.action
|
||||
|
||||
if action is None:
|
||||
self['scale'] = 1
|
||||
return
|
||||
|
||||
if context.mode == 'POSE' and not context.selected_pose_bones:
|
||||
self['scale'] = 1
|
||||
return
|
||||
|
||||
bpy.ops.animtoolbox.multikey_scale_value('INVOKE_DEFAULT')
|
||||
|
||||
def random_value(self, context):
|
||||
|
||||
for obj in context.selected_objects:
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
for fcu in fcurves:
|
||||
if obj.mode == 'POSE':
|
||||
if selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
if Tools.filter_properties(context.scene.animtoolbox, fcu):
|
||||
continue
|
||||
value_list = []
|
||||
threshold = bpy.context.window_manager.atb_ui.multikey.randomness
|
||||
for key in fcu.keyframe_points:
|
||||
if key.select_control_point == True:
|
||||
value_list.append(key.co[1])
|
||||
|
||||
if len(value_list) > 0:
|
||||
value = max(value_list)- min(value_list)
|
||||
for key in fcu.keyframe_points:
|
||||
add_value(key, value * random.uniform(-threshold, threshold))
|
||||
fcu.update()
|
||||
|
||||
def evaluate_combine(data_path, added_array, eval_array, array_default, influence):
|
||||
|
||||
if 'scale' in data_path:
|
||||
eval_array = eval_array * (added_array / array_default) ** influence
|
||||
elif 'rotation_quaternion' in data_path:
|
||||
#multiply first the influence with the w separatly
|
||||
added_array[0] = added_array[0] + (1- added_array[0])*(1 - influence)
|
||||
added_array[1:] *= influence
|
||||
eval_array = np.array(Quaternion(eval_array) @ Quaternion(added_array))# ** influence
|
||||
#if it's a custom property
|
||||
elif 'rotation_euler' not in data_path and 'location' not in data_path:
|
||||
eval_array = eval_array + (added_array - array_default) * influence
|
||||
|
||||
return eval_array
|
||||
|
||||
def evaluate_array(fcurves, fcu_path, frame, array_default = [0, 0, 0]):
|
||||
'''Create an array from all the indexes'''
|
||||
|
||||
array_len = len(array_default)
|
||||
fcu_array = []
|
||||
#get the missing arrays in case quaternion is not complete
|
||||
missing_arrays = []
|
||||
for i in range(array_len):
|
||||
fcu = fcurves.find(fcu_path, index = i)
|
||||
if fcu is None:
|
||||
missing_arrays.append(i)
|
||||
continue
|
||||
|
||||
fcu_array.append(fcu.evaluate(frame))
|
||||
|
||||
#In case it's a quaternion and missing attributes, then adding from default value
|
||||
if fcu_array and array_len == 4 and missing_arrays:
|
||||
for i in missing_arrays:
|
||||
fcu_array.insert(i, array_default[i])
|
||||
|
||||
if not len(fcu_array):
|
||||
return None
|
||||
return np.array(fcu_array)
|
||||
|
||||
def evaluate_layers(context, obj, anim_data, fcu, array_default):
|
||||
'''Calculate the evaluation of all the layers when using the nla'''
|
||||
|
||||
if not hasattr(anim_data, 'nla_tracks') or not anim_data.use_nla:
|
||||
return None
|
||||
nla_tracks = anim_data.nla_tracks
|
||||
if not len(nla_tracks):
|
||||
return None
|
||||
frame = context.scene.frame_current
|
||||
blend_types = {'ADD' : '+', 'SUBTRACT' : '-', 'MULTIPLY' : '*'}
|
||||
fcu_path = fcu.data_path
|
||||
|
||||
eval_array = array_default
|
||||
|
||||
for track in nla_tracks:
|
||||
if track.mute:
|
||||
continue
|
||||
if not len(track.strips):
|
||||
continue
|
||||
for strip in track.strips:
|
||||
if not strip.frame_start < frame < strip.frame_end:
|
||||
continue
|
||||
action = strip.action
|
||||
if action is None:
|
||||
continue
|
||||
blend_type = strip.blend_type
|
||||
|
||||
#get the influence value either from the attribute or the fcurve. function coming from bake
|
||||
influence = strip.influence
|
||||
if len(strip.fcurves):
|
||||
if not strip.fcurves[0].mute and len(strip.fcurves[0].keyframe_points):
|
||||
influence = strip.fcurves[0].evaluate(frame)
|
||||
|
||||
#evaluate the frame according to the strip settings
|
||||
frame_eval = frame
|
||||
#change the frame if the strip is on hold
|
||||
if frame < strip.frame_start:
|
||||
if strip.extrapolation == 'HOLD':
|
||||
frame_eval = strip.frame_start
|
||||
elif frame >= strip.frame_end:
|
||||
if strip.extrapolation == 'HOLD' or strip.extrapolation == 'HOLD_FORWARD':
|
||||
frame_eval = strip.frame_end
|
||||
|
||||
last_frame = strip.frame_start + (strip.frame_end - strip.frame_start) / strip.repeat
|
||||
|
||||
if strip.repeat > 1 and (frame) >= last_frame:
|
||||
action_range = (strip.action_frame_end * strip.scale - strip.action_frame_start * strip.scale)
|
||||
frame_eval = (((frame_eval - strip.frame_start) % (action_range)) + strip.frame_start)
|
||||
|
||||
if strip.use_reverse:
|
||||
frame_eval = last_frame - (frame_eval - strip.frame_start)
|
||||
offset = (strip.frame_start * 1/strip.scale - strip.action_frame_start) * strip.scale
|
||||
frame_eval = strip.frame_start * 1/strip.scale + (frame_eval - strip.frame_start) * 1/strip.scale - offset * 1/strip.scale
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame_eval, influence, array_default, blend_type, blend_types)
|
||||
|
||||
#Adding an extra layer from the action outside and on top of the nla
|
||||
tweak_mode = anim_data.use_tweak_mode
|
||||
if tweak_mode:
|
||||
anim_data.use_tweak_mode = False
|
||||
action = anim_data.action
|
||||
if action:
|
||||
influence = anim_data.action_influence
|
||||
blend_type = anim_data.action_blend_type
|
||||
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence, array_default, blend_type, blend_types)
|
||||
anim_data.use_tweak_mode = tweak_mode
|
||||
|
||||
return eval_array
|
||||
|
||||
|
||||
def evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence,
|
||||
array_default, blend_type, blend_types):
|
||||
'''Calculate the value based on the blend type'''
|
||||
|
||||
fcu_array = evaluate_array(fcurves, fcu_path, frame, array_default)
|
||||
if fcu_array is None:
|
||||
return eval_array
|
||||
###EVALUATION###
|
||||
if blend_type =='COMBINE':
|
||||
if 'location' in fcu_path or 'rotation_euler' in fcu_path:
|
||||
blend_type = 'ADD'
|
||||
if blend_type =='REPLACE':
|
||||
eval_array = eval_array * (1 - influence) + fcu_array * influence
|
||||
elif blend_type =='COMBINE':
|
||||
eval_array = evaluate_combine(fcu_path, fcu_array, eval_array, array_default, influence)
|
||||
else:
|
||||
eval_array = eval('eval_array' + blend_types[blend_type] + 'fcu_array' + '*' + str(influence))
|
||||
|
||||
return eval_array
|
||||
|
||||
def evaluate_value(self, context):
|
||||
ui = context.window_manager.atb_ui
|
||||
for obj in context.selected_objects:
|
||||
|
||||
anim_data = obj.animation_data
|
||||
if anim_data is None:
|
||||
return
|
||||
if anim_data.action is None:
|
||||
return
|
||||
|
||||
action = obj.animation_data.action
|
||||
fcu_paths = []
|
||||
transformations = ["rotation_quaternion","rotation_euler", "location", "scale"]
|
||||
if obj.mode == 'POSE':
|
||||
bonelist = context.selected_pose_bones if ui.multikey.selectedbones else obj.pose.bones
|
||||
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
for fcu in fcurves:
|
||||
if fcu in fcu_paths:
|
||||
continue
|
||||
if Tools.filter_properties(context.scene.animtoolbox, fcu):
|
||||
continue
|
||||
if obj.mode == 'POSE':
|
||||
if selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
|
||||
for bone in bonelist:
|
||||
#find the fcurve of the bone
|
||||
if fcu.data_path.rfind(bone.name) != 12 or fcu.data_path[12 + len(bone.name)] != '"':
|
||||
continue
|
||||
path_split = fcu.data_path.split('"].')
|
||||
|
||||
if len(path_split) <= 1:
|
||||
continue
|
||||
else:
|
||||
transform = fcu.data_path.split('"].')[1]
|
||||
if transform not in transformations:
|
||||
continue
|
||||
current_value = getattr(obj.pose.bones[bone.name], transform)
|
||||
else:
|
||||
transform = fcu.data_path
|
||||
current_value = getattr(obj, transform)
|
||||
|
||||
array_default = np.array(attr_default(obj, (fcu.data_path, fcu.array_index)))
|
||||
# array_default = np.array([attr_default(obj, (fcu.data_path, i)) for i in range(4)
|
||||
# if fcurves.find(fcu.data_path, index = i) is not None])
|
||||
eval_array = evaluate_layers(context, obj, anim_data, fcu, array_default)
|
||||
if eval_array is None:
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
eval_array = evaluate_array(fcurves, fcu.data_path, context.scene.frame_current, array_default)
|
||||
|
||||
#calculate the difference between current value and the fcurve value
|
||||
add_diff(fcurves, fcu.data_path, np.array(current_value), eval_array)
|
||||
|
||||
class MULTIKEY_OT_Multikey(bpy.types.Operator):
|
||||
"""Edit all selected keyframes"""
|
||||
bl_label = "Edit Selected Keyframes"
|
||||
bl_idname = "animtoolbox.multikey"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object and context.active_object.animation_data and bpy.context.scene.tool_settings.use_keyframe_insert_auto == False
|
||||
|
||||
def execute(self, context):
|
||||
evaluate_value(self, context)
|
||||
return {'FINISHED'}
|
||||
|
||||
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 = scale_value)
|
||||
randomness: bpy.props.FloatProperty(name="Randomness", description="Random Threshold of keyframes", default=0.1, min=0.0, max = 1.0, update = random_value)
|
||||
|
||||
#filters
|
||||
# filter_location: bpy.props.BoolVectorProperty(name="Location", description="Filter Location properties", default=(True, True, True), size = 3, options={'HIDDEN'})
|
||||
# filter_rotation: bpy.props.BoolVectorProperty(name="Rotation", description="Filter Rotation properties", default=(True, True, True, True), size = 4, options={'HIDDEN'})
|
||||
# filter_scale: bpy.props.BoolVectorProperty(name="Scale", description="Filter Scale properties", default=(True, True, True), size = 3, options={'HIDDEN'})
|
||||
|
||||
# class FilterProperties(bpy.types.Operator):
|
||||
# """Filter Location Rotation and Scale Properties"""
|
||||
# bl_idname = "fcurves.filter"
|
||||
# bl_label = "Filter Properties W X Y Z"
|
||||
# bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
# def invoke(self, context, event):
|
||||
# wm = context.window_manager
|
||||
# return wm.invoke_props_dialog(self, width = 200)
|
||||
|
||||
# def draw(self, context):
|
||||
# layout = self.layout
|
||||
# row = layout.row()
|
||||
# row.label(text = 'Location')
|
||||
# row.prop(context.scene.multikey, 'filter_location', text = '')
|
||||
# row = layout.row()
|
||||
# row.label(text = 'Rotation')
|
||||
# row.prop(context.scene.multikey, 'filter_rotation', text = '')
|
||||
# row = layout.row()
|
||||
# row.label(text = 'Scale')
|
||||
# row.prop(context.scene.multikey, 'filter_scale', text = '')
|
||||
|
||||
# def execute(self, context):
|
||||
# return {'CANCELLED'}
|
||||
|
||||
classes = (ScaleValuesOp, MULTIKEY_OT_Multikey)
|
||||
|
||||
#register, unregister = bpy.utils.register_classes_factory(classes)
|
||||
|
||||
def register():
|
||||
from bpy.utils import register_class
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
# bpy.types.Scene.animtoolbox.multikey = bpy.props.PointerProperty(type = MultikeyProperties, options={'LIBRARY_EDITABLE'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
||||
# del bpy.types.Scene.animtoolbox.multikey
|
||||
@@ -0,0 +1,700 @@
|
||||
# ***** 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 *****
|
||||
|
||||
import bpy
|
||||
from . import Rigger_Toolbox
|
||||
from . import multikey
|
||||
from . import Rigger_Toolbox
|
||||
from pathlib import Path
|
||||
from bpy.utils import register_class
|
||||
from bpy.utils import unregister_class
|
||||
import os
|
||||
|
||||
######################################################## MENUS ########################################################
|
||||
|
||||
class ANIMTOOLBOX_MT_Copy_Paste_Matrix(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Copy_Paste_Matrix'
|
||||
bl_label = "Copy Paste Matrix"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator('anim.copy_matrix', text = 'Copy World Matrix', icon ='DUPLICATE')
|
||||
layout.operator('anim.paste_matrix', text = 'Paste World Matrix', icon ='PASTEDOWN')
|
||||
layout.separator()
|
||||
layout.operator('anim.copy_relative_matrix', text = 'Copy Relative Matrix', icon ='DUPLICATE')
|
||||
layout.operator('anim.paste_relative_matrix', text = 'Paste Relative Matrix', icon ='PASTEDOWN')
|
||||
layout.separator()
|
||||
layout.prop_menu_enum(context.scene.animtoolbox, 'range_type', text = 'Frame Range Type')
|
||||
if context.scene.animtoolbox.range_type == 'RANGE':
|
||||
# split = layout.split(factor = 0.3)
|
||||
# layout.prop(context.scene.animtoolbox, 'bake_frame_start', text = 'Start')
|
||||
# layout.prop(context.scene.animtoolbox, 'bake_frame_end', text = 'End')
|
||||
layout.operator("anim.markers_bakerange", icon = 'MARKER', text ='Frame Range Markers Widget', depress = context.scene.animtoolbox.bake_frame_range)
|
||||
|
||||
class ANIMTOOLBOX_MT_Temp_Ctrls(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Temp_Ctrls'
|
||||
bl_label = "Temp Ctrls"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
custom_icons = preview_collections["main"]
|
||||
layout.operator('anim.bake_to_ctrl', text="World Space Ctrls", icon = 'WORLD')
|
||||
layout.operator('anim.bake_to_temp_fk', text="Temp FK Ctrls", icon = 'BONE_DATA')
|
||||
layout.operator('anim.bake_to_temp_ik', text="Temp IK Ctrls", icon = 'CON_KINEMATIC')
|
||||
layout.separator()
|
||||
layout.operator('anim.link_temp_chains', text="Link Temp Chains", icon = 'DECORATE_LINKED')
|
||||
layout.operator('anim.unlink_temp_chains', text="UnLink Temp Chains", icon = 'DECORATE_LINKED')
|
||||
layout.separator()
|
||||
layout.operator('anim.bake_temp_ctrls', text="Bake Temp Ctrls", icon_value = custom_icons["oven"].icon_id)
|
||||
layout.operator('anim.remove_bones_constraints', text="Cleanup", icon_value = custom_icons["trash"].icon_id)
|
||||
layout.prop_menu_enum(context.scene.btc, 'target', text = 'Bake To', icon = 'MOD_ARMATURE')
|
||||
layout.separator()
|
||||
|
||||
ctrl = context.object.animtoolbox.controller if context.object.animtoolbox.controller else context.object
|
||||
if ctrl:
|
||||
layout.prop(ctrl.animtoolbox, 'ctrls_enabled', text ='Temp Ctrls On/Off')
|
||||
|
||||
# layout.operator('anim.enable_tempctrls', text="On" , icon = 'HIDE_OFF')#icon = 'CONSTRAINT_BONE'
|
||||
# layout.operator('anim.disable_tempctrls', text="Off", icon = 'HIDE_ON')
|
||||
layout.separator()
|
||||
layout.prop_menu_enum(context.scene.btc, 'shape_type', icon = 'MESH_DATA')
|
||||
layout.prop(context.scene.btc, 'shape_size', slider = False, icon ='CON_SIZELIKE')
|
||||
layout.prop(context.scene.btc, 'color_set', text = '')
|
||||
# layout.prop(context.scene.btc, 'shape_type', icon = 'MESH_DATA', text ='')
|
||||
|
||||
class ANIMTOOLBOX_MT_Keyframe_Offset(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Keyframe_Offset'
|
||||
bl_label = "Interactive Keyframe Offset"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(context.scene.animtoolbox, 'keyframes_offset', slider = False)
|
||||
layout.operator('anim.select_keyframes_offset', text="Select Offset Keyframes", icon ='RESTRICT_SELECT_OFF')
|
||||
layout.operator('anim.apply_keyframes_offset', text="Apply Offset", icon = 'ANIM')
|
||||
|
||||
class ANIMTOOLBOX_MT_Blendings(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Blendings'
|
||||
bl_label = "Blendings"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
ui = context.window_manager.atb_ui
|
||||
layout.prop(ui, 'inbetween_worldmatrix', slider = True)
|
||||
layout.prop(context.scene.animtoolbox, 'inbetweener', slider = True)
|
||||
layout.prop(ui, 'blend_mirror', slider = True)
|
||||
|
||||
class ANIMTOOLBOX_MT_Multikey(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Multikey'
|
||||
bl_label = "Multikey"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
ui = context.window_manager.atb_ui
|
||||
layout.operator("animtoolbox.multikey", icon = 'ACTION_TWEAK')
|
||||
layout.prop(ui.multikey, 'scale', slider = True)
|
||||
layout.prop(ui.multikey, 'randomness', slider = True)
|
||||
|
||||
class ANIMTOOLBOX_MT_Convert_Rotations(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Convert_Rotations'
|
||||
bl_label = "Convert Rotations"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator("anim.convert_rotation_mode", icon = 'DRIVER_ROTATIONAL_DIFFERENCE', text = 'Convert Rotation To')
|
||||
layout.operator("anim.find_rotation_mode", icon = 'VIEWZOOM', text = 'Recommend Euler Rotation')
|
||||
layout.prop_menu_enum(context.scene.animtoolbox, 'rotation_mode', text = 'Convert To')
|
||||
|
||||
class ANIMTOOLBOX_MT_operators(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_menu_operators'
|
||||
bl_label = "AnimToolBox"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
ui = context.window_manager.atb_ui
|
||||
custom_icons = preview_collections["main"]
|
||||
|
||||
if context.area.type == 'VIEW_3D':
|
||||
layout.menu('ANIMTOOLBOX_MT_Temp_Ctrls', text = 'Temp Ctrls', icon_value = custom_icons["puppet"].icon_id)
|
||||
layout.separator()
|
||||
layout.operator('anim.share_keyframes', text = 'Share Keyframes', icon_value = custom_icons["sharekeys"].icon_id)
|
||||
layout.operator('anim.relative_cursor', text = 'Relative Cursor Pivot', icon_value = custom_icons["relative_cursor"].icon_id, depress = ui.relative_cursor)
|
||||
layout.operator("anim.markers_retimer", icon_value = custom_icons["retime"].icon_id, text ='Markers Retimer', depress = ui.markers_retimer)
|
||||
layout.menu('ANIMTOOLBOX_MT_Convert_Rotations', text = 'Convert Rotations', icon = 'DRIVER_ROTATIONAL_DIFFERENCE')
|
||||
|
||||
layout.separator()
|
||||
layout.menu('ANIMTOOLBOX_MT_Keyframe_Offset', text = 'Interactive Keyframe Offset', icon_value = custom_icons["keyframe_offset"].icon_id)
|
||||
layout.menu('ANIMTOOLBOX_MT_Blendings', text = 'Blendings and inbetweens', icon_value = custom_icons["sliders"].icon_id)
|
||||
layout.separator()
|
||||
layout.menu('ANIMTOOLBOX_MT_Copy_Paste_Matrix', text = 'Copy Paste Matrix', icon_value = custom_icons["copy_matrix"].icon_id)
|
||||
|
||||
if bpy.context.preferences.addons[__package__].preferences.multikey:
|
||||
layout.separator()
|
||||
layout.menu('ANIMTOOLBOX_MT_Multikey', text = 'Multikey', icon_value = custom_icons["multikey"].icon_id)
|
||||
|
||||
layout.separator(factor = 0.5)
|
||||
layout.operator('anim.switch_collections_visibility', icon = 'COLLECTION_COLOR_06', text = 'Animated Collections Visibilty')
|
||||
layout.operator('anim.isolate_pose_mode', icon_value = custom_icons["isolate"].icon_id, depress = scene.animtoolbox.isolate_pose_mode)
|
||||
layout.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
layout.separator()
|
||||
layout.prop(context.preferences.addons[__package__].preferences, 'quick_menu', text = 'Use Quick Icons Menu')
|
||||
# layout.prop(context.window_manager.atb_ui, 'quick_menu', text = 'Use Quick Icons Menu')
|
||||
# elif context.area.type == 'DOPESHEET_EDITOR':
|
||||
# layout.operator('wm.call_menu_pie', text="Pie AnimOffset").name = 'ANIMAIDE_MT_pie_anim_offset'
|
||||
# layout.menu('ANIMAIDE_MT_anim_offset')
|
||||
# layout.menu('ANIMAIDE_MT_anim_offset_mask')
|
||||
|
||||
# elif context.area.type == 'GRAPH_EDITOR':
|
||||
# layout.menu('ANIMAIDE_MT_curve_tools_pie')
|
||||
|
||||
######################################################## WorkSpaceTools ########################################################
|
||||
|
||||
class KeyframeOffsetTool(bpy.types.WorkSpaceTool):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_context_mode = 'POSE'
|
||||
bl_idname = 'animtoolbox.keyframe_offset'
|
||||
bl_label = 'Keyframe Offset Tool'
|
||||
bl_description = (
|
||||
'Offset the keyframes of the selected bone\n'
|
||||
'Shift + Click to select the bones with an offset\n'
|
||||
'Ctrl + Alt + Click to Apply the offset'
|
||||
)
|
||||
bl_icon = (Path(__file__).parent / "icons" / "ops.anim.keyframe_offset").as_posix()
|
||||
bl_keymap = (
|
||||
('anim.keyframe_offset', {'type': 'LEFTMOUSE', 'value': 'CLICK_DRAG'}, None),
|
||||
# ('anim.select_keyframes_offset', {'type': 'LEFTMOUSE', 'value': 'CLICK', 'shift': True}, None),
|
||||
('anim.apply_keyframes_offset', {'type': 'LEFTMOUSE', 'value': 'CLICK', 'ctrl': True, 'alt': True}, None),
|
||||
|
||||
('view3d.select_box', {'type': 'LEFTMOUSE', 'value': 'CLICK_DRAG', 'shift': True}, {'properties': [('mode', 'ADD')]}),
|
||||
('view3d.select_box', {'type': 'LEFTMOUSE', 'value': 'CLICK_DRAG', 'ctrl': True}, {'properties': [('mode', 'SUB')]}),
|
||||
('view3d.select_box', {'type': 'LEFTMOUSE', 'value': 'CLICK_DRAG', 'shift': True, 'ctrl': True}, {'properties': [('mode', 'AND')]}),
|
||||
('view3d.select', {'type': 'LEFTMOUSE', 'value': 'CLICK'}, {'properties': [('deselect_all', True)]}),
|
||||
('view3d.select', {'type': 'LEFTMOUSE', 'value': 'CLICK', 'shift': True}, {'properties': [('toggle', True)]}),
|
||||
('view3d.select', {'type': 'LEFTMOUSE', 'value': 'CLICK', 'alt': True}, {'properties': [('enumerate', True)]}),
|
||||
('view3d.select', {'type': 'LEFTMOUSE', 'value': 'CLICK', 'shift': True, 'alt': True}, {'properties': [('toggle', True), ('enumerate', True)]})
|
||||
|
||||
)
|
||||
|
||||
######################################################## PANELS ########################################################
|
||||
class ANIMTOOLBOX_PT_Panel:
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Animation"
|
||||
#bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object is not None
|
||||
|
||||
class ANIMTOOLBOX_PT_MainPanel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
#bl_category = "Animation"
|
||||
bl_label = "AnimToolBox"
|
||||
bl_idname = "ANIMTOOLBOX_PT_MainPanel"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw_header(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
self.layout.label(text='', icon_value = custom_icons["animtoolbox"].icon_id)
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.object
|
||||
if obj is None:
|
||||
return
|
||||
# layout = self.layout
|
||||
# row = layout.row(align = True)
|
||||
|
||||
# row.label(text = 'Support me on ')
|
||||
# row.operator("wm.url_open", text="Patreon", icon = 'FUND').url = "https://www.patreon.com/animtoolbox"
|
||||
|
||||
class TEMPCTRLS_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
#bl_category = "Animation"
|
||||
bl_label = "Temp Controls"
|
||||
bl_idname = "TEMPCTRLS_PT_Panel"
|
||||
bl_parent_id = 'ANIMTOOLBOX_PT_MainPanel'
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw_header(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
self.layout.label(text="", icon_value = custom_icons["puppet"].icon_id)
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.object
|
||||
btc = context.scene.btc
|
||||
if obj is None:
|
||||
return
|
||||
custom_icons = preview_collections["main"]
|
||||
oven_icon = custom_icons["oven"]
|
||||
# row = layout.row()
|
||||
ui = context.window_manager.atb_ui
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
col = box.column()
|
||||
col.operator('anim.bake_to_ctrl', text="WorldSpace Ctrls", icon ='WORLD') #
|
||||
col.operator('anim.bake_to_temp_fk', text="Temp FK Ctrls", icon = 'BONE_DATA')
|
||||
col.operator('anim.bake_to_temp_ik', text="Temp IK Ctrls", icon = 'CON_KINEMATIC')
|
||||
|
||||
# layout.separator(factor = 0.1)
|
||||
# box = layout.box()
|
||||
row = box.row()
|
||||
row.operator('anim.link_temp_chains', text="Link Temp Chains", icon = 'DECORATE_LINKED')
|
||||
row.prop(btc, 'linksettings', text = '', icon = 'SETTINGS')
|
||||
|
||||
if btc.linksettings:
|
||||
# insidebox = box.box()
|
||||
row = box.row()
|
||||
row.label(text = 'Link to Active Chain: ')
|
||||
row.prop(btc, 'link_to', text = '')
|
||||
row = box.row()
|
||||
row.operator('anim.unlink_temp_chains', text="UnLink Temp Chains", icon = 'UNLINKED')
|
||||
# row = insidebox.row()
|
||||
# row.label(text = 'Link from Chain: ')
|
||||
# row.prop(btc, 'link_from', text = '')
|
||||
# layout.separator(factor = 0.1)
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.operator('anim.bake_to_empties', text="WorldSpace Empties")
|
||||
row.operator('anim.empties_to_bones', text="", icon = 'EMPTY_AXIS')
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
# row.operator('anim.bake_constrained_bones', text="Quick Bake", icon = 'REC')
|
||||
row.operator('anim.bake_temp_ctrls', text="Bake Temp Ctrls", icon_value = oven_icon.icon_id)
|
||||
row.prop(btc, 'bakesettings', text = '', icon = 'SETTINGS')
|
||||
|
||||
if btc.bakesettings:
|
||||
insidebox = box.box()
|
||||
# split = insidebox.split(factor = 0.9)
|
||||
# split.prop(btc, 'smartbake', text = 'Smart Bake')
|
||||
# split.operator('fcurves.filter_ui', icon ='FILTER', text = '')
|
||||
# if btc.smartbake:
|
||||
# row.prop(btc, 'inbetween_keyframes')
|
||||
split = insidebox.split(factor = 0.3)
|
||||
split.label(text = 'Bake To:')
|
||||
# split.split(factor = 0.9)
|
||||
split_2 = split.split(factor = 0.9)
|
||||
split_2.prop(btc, 'target', text = '')
|
||||
split_2.operator('fcurves.filter_ui', icon ='FILTER', text = '')
|
||||
|
||||
split = insidebox.split(factor = 0.3)
|
||||
split.label(text = 'Bake Range:')
|
||||
split.prop(btc, 'bake_range_type', text = '')
|
||||
|
||||
if btc.bake_range_type == 'KEYFRAMES':
|
||||
row = insidebox.row()
|
||||
row.label(text = 'Keyframes: ')
|
||||
row.prop(btc, 'smartbake', text = 'Smart Bake')
|
||||
row.prop(btc, 'bake_layers', text = 'All Layers')
|
||||
row = insidebox.row()
|
||||
row.label(text = 'From:')
|
||||
row.prop(btc, 'from_origin', text = 'Origin')
|
||||
row.prop(btc, 'from_ctrl', text = 'Ctrls')
|
||||
|
||||
elif btc.bake_range_type == 'CUSTOM':
|
||||
row = insidebox.row()
|
||||
row.prop(btc, 'bake_range', text = '')
|
||||
|
||||
insidebox.separator(factor=0.1)
|
||||
row = insidebox.row()
|
||||
row.label(text = 'Clean: ')
|
||||
row.prop(btc, 'clean_constraints', text = 'Constraints')
|
||||
if btc.clean_constraints:
|
||||
row.prop(btc, 'clean_ctrls', text = 'Ctrls')
|
||||
|
||||
row = box.row()
|
||||
row.operator('anim.remove_bones_constraints', text="Cleanup", icon_value = custom_icons["trash"].icon_id)
|
||||
row.prop(btc, 'cleansettings', text = '', icon = 'SETTINGS')
|
||||
|
||||
if btc.cleansettings:
|
||||
insidebox = box.box()
|
||||
split = insidebox.split(factor = 0.4)
|
||||
split.label(text = 'Target: ')
|
||||
split.prop(btc, 'target', text = '')
|
||||
row = insidebox.row()
|
||||
row.label(text = 'Clean: ')
|
||||
row.prop(btc, 'clean_constraints', text = 'Constraints')
|
||||
if btc.clean_constraints:
|
||||
row.prop(btc, 'clean_ctrls', text = 'Ctrls')
|
||||
if btc.target == 'SELECTED':
|
||||
split = insidebox.split(factor = 0.68)
|
||||
split.label(text = 'ReBake Link Ctrls to Original: ')
|
||||
split.prop(btc, 'rebake_to_org', text = '')
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
# row = layout.row()
|
||||
|
||||
# row.prop(obj.animtoolbox, 'influence', text = 'Influence', slider = True)
|
||||
ctrl = obj.animtoolbox.controller if obj.animtoolbox.controller else obj
|
||||
if ctrl:
|
||||
enabled_icon = 'HIDE_OFF' if ctrl.animtoolbox.ctrls_enabled else 'HIDE_ON'
|
||||
row.prop(ctrl.animtoolbox, 'ctrls_enabled', text ='Temp Ctrls On/Off', icon = enabled_icon)
|
||||
# row = box.row()
|
||||
# row.prop(ui, 'temp_ctrls_switch', icon = 'RESTRICT_VIEW_OFF', text = '')
|
||||
# row.label(text = 'Temp Ctrls Switch: ')
|
||||
# row.prop(btc, 'target', text = '', icon = 'MOD_ARMATURE', icon_only = True)
|
||||
# if ui.temp_ctrls_switch:
|
||||
# # split = layout.split(factor = 0.225)
|
||||
# row = box.row()
|
||||
# row.operator('anim.enable_tempctrls', text="On" , icon = 'HIDE_OFF')#icon = 'CONSTRAINT_BONE'
|
||||
# row.operator('anim.disable_tempctrls', text="Off", icon = 'HIDE_ON')
|
||||
|
||||
box.separator(factor=0.1)
|
||||
row = box.row()
|
||||
row.prop(ui, 'temp_ctrls_shapes', icon = 'MESH_DATA', text = '')
|
||||
row.label(text = 'Temp Ctrls Shapes: ')
|
||||
row.prop(btc, 'target', text = '', icon = 'MOD_ARMATURE', icon_only = True)
|
||||
if ui.temp_ctrls_shapes:
|
||||
row = box.row()
|
||||
row.prop(btc, 'shape_size', slider = False)
|
||||
row.prop(btc, 'shape_type', text = '')
|
||||
row = layout.row()
|
||||
# split.label(text = 'Theme Color: ')
|
||||
row.prop(btc, 'color_set', text = '')
|
||||
|
||||
layout.separator()
|
||||
row = layout.row()
|
||||
row.operator('anim.btc_selections', text="Select", icon = 'CONSTRAINT_BONE')
|
||||
row.prop(btc, 'selection', text = '', icon = 'CON_ARMATURE')
|
||||
|
||||
class ANIMTOOLBOX_PT_Tools(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
bl_label = "Anim Tools"
|
||||
bl_idname = "ANIMTOOLBOX_PT_Tools"
|
||||
bl_parent_id = 'ANIMTOOLBOX_PT_MainPanel'
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw_header(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
layout = self.layout
|
||||
layout.label(text="", icon_value = custom_icons["toolbox"].icon_id)
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.object
|
||||
scene = context.scene
|
||||
atb = scene.animtoolbox
|
||||
if obj is None:
|
||||
return
|
||||
custom_icons = preview_collections["main"]
|
||||
layout = self.layout
|
||||
ui = context.window_manager.atb_ui
|
||||
row = layout.row()
|
||||
filter_text = 'Filter Tools Properties' if atb.filter_name == '' else 'Filters: ' + atb.filter_name
|
||||
filter_depress = False if atb.filter_name == '' else True
|
||||
row.operator('fcurves.filter_ui', icon ='FILTER', text = filter_text, depress = filter_depress)
|
||||
layout.separator()
|
||||
#layout.operator('anim.keyframes_offset', text="Offset Keyframes", icon = 'NEXT_KEYFRAME')
|
||||
split = layout.split(factor = 0.9)
|
||||
split.prop(context.scene.animtoolbox, 'keyframes_offset', slider = True)
|
||||
split.operator('anim.apply_keyframes_offset', text="", icon_value = custom_icons["apply"].icon_id)
|
||||
split.operator('anim.select_keyframes_offset', text="", icon_value = custom_icons["select"].icon_id)
|
||||
row = layout.row()
|
||||
row.operator('anim.share_keyframes', text = 'Share Keyframes', icon_value = custom_icons["sharekeys"].icon_id)
|
||||
row = layout.row()
|
||||
row.operator("anim.relative_cursor", text ='Relative Cursor Pivot', icon_value = custom_icons["relative_cursor"].icon_id, depress = ui.relative_cursor)
|
||||
row = layout.row()
|
||||
row.operator("anim.markers_retimer", icon_value = custom_icons["retime"].icon_id, text ='Markers Retimer', depress = ui.markers_retimer)
|
||||
layout.separator(factor = 0.5)
|
||||
|
||||
box = layout.box()
|
||||
# icon = 'DOWNARROW_HLT' if ui.copy_paste_matrix is True else 'RIGHTARROW_THIN'
|
||||
# split = box.split(factor = 0.9)
|
||||
# split.prop(ui, 'copy_paste_matrix', icon_value = custom_icons["copy_matrix"].icon_id, text = 'Copy Paste Matrices')
|
||||
# split.operator('fcurves.filter', icon ='FILTER', text = '')
|
||||
row = box.row()
|
||||
row.prop(ui, 'copy_paste_matrix', icon_value = custom_icons["copy_matrix"].icon_id, text = 'Copy Paste Matrices')
|
||||
if ui.copy_paste_matrix:
|
||||
row = box.row()
|
||||
row.label(text = 'Copy World Matrix :', icon = 'WORLD')
|
||||
row.operator('anim.copy_matrix', text = '', icon ='DUPLICATE')
|
||||
row.operator('anim.paste_matrix', text = '', icon ='PASTEDOWN')
|
||||
row = box.row()
|
||||
row.label(text = 'Copy Relative Matrix :', icon = 'LINKED')
|
||||
row.operator('anim.copy_relative_matrix', text = '', icon ='DUPLICATE')
|
||||
row.operator('anim.paste_relative_matrix', text = '', icon ='PASTEDOWN')
|
||||
split = box.split(factor = 0.32)
|
||||
split.label(text = 'Paste To')
|
||||
split.prop(context.scene.animtoolbox, 'range_type', text = '')
|
||||
if context.scene.animtoolbox.range_type == 'RANGE':
|
||||
row = box.row()
|
||||
row.prop(context.scene.animtoolbox, 'bake_frame_start', text = 'Start')
|
||||
row.prop(context.scene.animtoolbox, 'bake_frame_end', text = 'End')
|
||||
row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = context.scene.animtoolbox.bake_frame_range)
|
||||
layout.separator(factor = 0.5)
|
||||
|
||||
box = layout.box()
|
||||
# icon = 'DOWNARROW_HLT' if ui.Inbetweens is True else 'RIGHTARROW_THIN'
|
||||
# split = box.split(factor = 0.9)
|
||||
# split.prop(ui, 'Inbetweens', icon_value = custom_icons["sliders"].icon_id, text = 'Blendings / Inbetweens')
|
||||
# split.operator('fcurves.filter', icon ='FILTER', text = '')
|
||||
row = box.row()
|
||||
row.prop(ui, 'Inbetweens', icon_value = custom_icons["sliders"].icon_id, text = 'Blendings / Inbetweens')
|
||||
|
||||
if ui.Inbetweens:
|
||||
col = box.column()
|
||||
col.prop(ui, 'inbetween_worldmatrix', slider = True)
|
||||
col.prop(scene.animtoolbox, 'inbetweener', slider = True)
|
||||
col.prop(ui, 'blend_mirror', slider = True)
|
||||
|
||||
layout.separator(factor = 1)
|
||||
# my_icon = custom_icons["rotations"]
|
||||
split = layout.split(factor = 0.6)
|
||||
split.operator("anim.convert_rotation_mode", icon = 'DRIVER_ROTATIONAL_DIFFERENCE', text = 'Convert Rotation To')
|
||||
# split.operator("anim.convert_rotation_mode", text = 'Convert Rotation To', icon_value = my_icon.icon_id)
|
||||
split = split.split(factor = 0.3)
|
||||
split.operator("anim.find_rotation_mode", icon = 'VIEWZOOM', text = '')
|
||||
split.prop(context.scene.animtoolbox, 'rotation_mode', text = '')
|
||||
|
||||
class MULTIKEY_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
"""Add random value to selected keyframes"""
|
||||
bl_label = "Multikey"
|
||||
bl_idname = "MULTIKEY_PT_Panel"
|
||||
# bl_space_type = 'VIEW_3D'
|
||||
# bl_region_type = 'UI'
|
||||
# bl_category= 'Animation'
|
||||
bl_parent_id = 'ANIMTOOLBOX_PT_Tools'
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw_header(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
layout = self.layout
|
||||
layout.label(text="", icon_value = custom_icons["multikey"].icon_id)
|
||||
|
||||
def draw(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
layout = self.layout
|
||||
ui = context.window_manager.atb_ui
|
||||
# layout.separator()
|
||||
split = layout.split(factor=0.1, align = True)
|
||||
split.prop(ui.multikey, 'selectedbones', icon_value = custom_icons["selected_bones"].icon_id, text = '')
|
||||
# split = split.split(factor=0.1, align = True)
|
||||
#split.operator('fcurves.filter', icon ='FILTER', text = '')
|
||||
split.operator("animtoolbox.multikey", icon = 'ACTION_TWEAK')
|
||||
layout.separator()
|
||||
row = layout.row(align = True)
|
||||
row.prop(ui.multikey, 'scale')
|
||||
row.prop(ui.multikey, 'randomness', slider = True)
|
||||
|
||||
class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
bl_label = "Display"
|
||||
bl_idname = "ANIMTOOLBOX_PT_Display"
|
||||
bl_parent_id = 'ANIMTOOLBOX_PT_MainPanel'
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw_header(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
self.layout.label(text="", icon_value = custom_icons["display"].icon_id)
|
||||
|
||||
def draw(self, context):
|
||||
scene = context.scene
|
||||
layout = self.layout
|
||||
ui = context.window_manager.atb_ui
|
||||
custom_icons = preview_collections["main"]
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
icon = 'DOWNARROW_HLT' if ui.gizmo_size is True else 'RIGHTARROW_THIN'
|
||||
row.prop(ui, 'gizmo_size', icon = icon, text = 'Add to Gizmo Size:')
|
||||
if ui.gizmo_size:
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'gizmo_size', text = '')
|
||||
row.operator('view3d.gizmo_size_up', text="", icon ='ADD')
|
||||
row.operator('view3d.gizmo_size_down', text="", icon ='REMOVE')
|
||||
layout.separator(factor = 0.5)
|
||||
|
||||
row = layout.row()
|
||||
col_vis_icon = 'OUTLINER_COLLECTION' if scene.animtoolbox.col_vis else 'COLLECTION_COLOR_06'
|
||||
row.operator('anim.switch_collections_visibility', icon = col_vis_icon, text = 'Animated Collections Visibilty', depress = scene.animtoolbox.col_vis)
|
||||
layout.separator(factor = 0.5)
|
||||
row = layout.row()
|
||||
row.operator('anim.isolate_pose_mode', icon_value = custom_icons["isolate"].icon_id, depress = scene.animtoolbox.isolate_pose_mode)
|
||||
|
||||
layout.separator(factor = 0.5)
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
row.prop(scene.animtoolbox, 'mp_settings', text = '', icon = 'SETTINGS')
|
||||
if scene.animtoolbox.mp_settings:
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_color_before', text = '')
|
||||
row.prop(scene.animtoolbox, 'mp_color_after', text = '')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_keyframe_scale', text = 'Scale Keyframes Box', icon = 'CUBE')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_points', text = 'Points')
|
||||
row.prop(scene.animtoolbox, 'mp_lines', text = 'Lines')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_handles', text = 'Handles')
|
||||
row.prop(scene.animtoolbox, 'mp_infront', text = 'In Front')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_display_frames', text = 'Display Keyframe Numbers')
|
||||
row = box.row()
|
||||
row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
|
||||
if context.object is None:
|
||||
return
|
||||
|
||||
split = box.split(factor = 0.4)
|
||||
split.label(text ='Frame Range')
|
||||
# row.prop(scene.animtoolbox, 'mp_frame_range', text = '')
|
||||
|
||||
split.prop(context.scene.animtoolbox, 'mp_frame_range', text = '')
|
||||
if context.scene.animtoolbox.mp_frame_range == 'MANUAL':
|
||||
row = box.row()
|
||||
row.prop(context.scene.animtoolbox, 'bake_frame_start', text = 'Start')
|
||||
row.prop(context.scene.animtoolbox, 'bake_frame_end', text = 'End')
|
||||
row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
|
||||
|
||||
elif context.scene.animtoolbox.mp_frame_range == 'AROUND':
|
||||
row = box.row()
|
||||
row.prop(context.scene.animtoolbox, 'mp_before', text = 'Before')
|
||||
row.prop(context.scene.animtoolbox, 'mp_after', text = 'After')
|
||||
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
|
||||
|
||||
|
||||
class RIGGERTOOLBOX_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
bl_label = "Rigger Toolbox"
|
||||
bl_idname = "RIGGERTOOLBOX_PT_Panel"
|
||||
bl_parent_id = 'ANIMTOOLBOX_PT_MainPanel'
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.object
|
||||
if obj is None:
|
||||
return
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
|
||||
col.operator("armature.add_bbone_widgets", text = "Add Bbone Widgets", icon = 'IPO_BEZIER')
|
||||
col.separator()
|
||||
col.operator("armature.add_chain_ctrls", text = "Add Chain Controls", icon = 'OUTLINER_DATA_ARMATURE')
|
||||
col.separator()
|
||||
col.operator("armature.merge", text = "Merge Rigs", icon = 'MOD_ARMATURE')
|
||||
|
||||
# Add-ons Preferences Update Panel
|
||||
# Define Panel classes for updating
|
||||
panels = [ANIMTOOLBOX_PT_MainPanel, TEMPCTRLS_PT_Panel, ANIMTOOLBOX_PT_Tools, ANIMTOOLBOX_PT_Display]
|
||||
|
||||
def update_panel(self, context):
|
||||
message = "Animtoolbox: Updating Panel locations has failed"
|
||||
try:
|
||||
for panel in panels:
|
||||
if "bl_rna" in panel.__dict__:
|
||||
unregister_class(panel)
|
||||
|
||||
for panel in panels:
|
||||
panel.bl_category = context.preferences.addons[__package__].preferences.category
|
||||
register_class(panel)
|
||||
|
||||
except Exception as e:
|
||||
print("\n[{}]\n{}\n\nError:\n{}".format(__package__, message, e))
|
||||
pass
|
||||
|
||||
def add_multikey(self, context):
|
||||
if bpy.context.preferences.addons[__package__].preferences is None:
|
||||
return
|
||||
if bpy.context.preferences.addons[__package__].preferences.multikey:
|
||||
multikey.register()
|
||||
register_class(MULTIKEY_PT_Panel)
|
||||
register_class(ANIMTOOLBOX_MT_Multikey)
|
||||
panels.append(MULTIKEY_PT_Panel)
|
||||
#panels = panels + (MULTIKEY_PT_Panel)
|
||||
elif hasattr(bpy.types, 'MULTIKEY_PT_Panel'):
|
||||
multikey.unregister()
|
||||
panels.remove(MULTIKEY_PT_Panel)
|
||||
unregister_class(MULTIKEY_PT_Panel)
|
||||
unregister_class(ANIMTOOLBOX_MT_Multikey)
|
||||
|
||||
def add_riggertoolbox(self, context):
|
||||
if bpy.context.preferences.addons[__package__].preferences is None:
|
||||
return
|
||||
if bpy.context.preferences.addons[__package__].preferences.riggertoolbox:
|
||||
Rigger_Toolbox.register()
|
||||
register_class(RIGGERTOOLBOX_PT_Panel)
|
||||
panels.append(RIGGERTOOLBOX_PT_Panel)
|
||||
#panels = panels + (RIGGERTOOLBOX_PT_Panel)
|
||||
elif hasattr(bpy.types, 'RIGGERTOOLBOX_PT_Panel'):
|
||||
Rigger_Toolbox.unregister()
|
||||
panels.remove(RIGGERTOOLBOX_PT_Panel)
|
||||
unregister_class(RIGGERTOOLBOX_PT_Panel)
|
||||
|
||||
classes = (ANIMTOOLBOX_MT_Copy_Paste_Matrix, ANIMTOOLBOX_MT_Temp_Ctrls, ANIMTOOLBOX_MT_operators, ANIMTOOLBOX_MT_Keyframe_Offset,
|
||||
ANIMTOOLBOX_MT_Blendings, ANIMTOOLBOX_MT_Convert_Rotations) + tuple(panels)
|
||||
|
||||
preview_collections = {}
|
||||
#list of all the custom icons
|
||||
icons_list = {'relative_cursor' : 'relative_cursor.png', 'puppet' : 'puppet.png', 'animtoolbox' : 'animtoolbox.png',
|
||||
'sliders' : 'slider-navigation.png', 'world_space' : 'earth-rotation.png', 'isolate' : 'isolation.png',
|
||||
'mt' : 'curve.png', 'toolbox' : 'tools.png', 'oven' : 'oven.png', 'retime' : 'retime.png', 'copy_matrix' : 'copy_world.png',
|
||||
'sharekeys' : 'sharekeys.png', 'sliders' : 'sliders.png', 'keyframe_offset' : 'keyframes_offset.png', 'display' : 'display.png',
|
||||
'switch' : 'switch.png', 'trash' : 'trash.png', 'apply' : 'apply.png', 'select' : 'select.png', 'worldspace' : 'WorldSpace.png',
|
||||
'multikey' : 'multikey.png', 'selected_bones' : 'selected_bones.png'}
|
||||
|
||||
def register_custom_icon():
|
||||
# Note that preview collections returned by bpy.utils.previews
|
||||
# are regular py objects - you can use them to store custom data.
|
||||
import bpy.utils.previews
|
||||
custom_icons = bpy.utils.previews.new()
|
||||
# path to the folder where the icon is
|
||||
# the path is calculated relative to this py file inside the addon folder
|
||||
icons_dir = os.path.join(os.path.dirname(__file__), "icons")
|
||||
# load a preview thumbnail of a file and store in the previews collection
|
||||
for iconname, icon_file in icons_list.items():
|
||||
custom_icons.load(iconname, os.path.join(icons_dir, icon_file), 'IMAGE')
|
||||
preview_collections["main"] = custom_icons
|
||||
|
||||
def draw_menu(self, context):
|
||||
if context.mode == 'OBJECT' or context.mode == 'POSE':
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
ui = context.window_manager.atb_ui
|
||||
|
||||
custom_icons = preview_collections["main"]
|
||||
layout.menu('ANIMTOOLBOX_MT_menu_operators') #, icon_value = custom_icons["toolbox"].icon_id, text =''
|
||||
|
||||
if not context.preferences.addons[__package__].preferences.quick_menu:
|
||||
return
|
||||
#Only Icons Menu
|
||||
temp_ctrls = custom_icons["puppet"]
|
||||
layout.menu('ANIMTOOLBOX_MT_Temp_Ctrls' , icon_value = temp_ctrls.icon_id, text ='')
|
||||
# layout.separator()
|
||||
layout.menu('ANIMTOOLBOX_MT_Copy_Paste_Matrix' , icon_value = custom_icons["copy_matrix"].icon_id, text ='')
|
||||
|
||||
layout.separator()
|
||||
layout.operator('anim.relative_cursor', text = '', depress = ui.relative_cursor, icon_value = custom_icons["relative_cursor"].icon_id)
|
||||
|
||||
layout.separator()
|
||||
layout.operator('anim.markers_retimer', text = '', depress = ui.markers_retimer, icon_value = custom_icons["retime"].icon_id)
|
||||
|
||||
layout.separator()
|
||||
layout.operator('anim.isolate_pose_mode', text = '', depress = scene.animtoolbox.isolate_pose_mode, icon_value = custom_icons["isolate"].icon_id)
|
||||
|
||||
layout.separator()
|
||||
layout.operator('object.motion_path_operator', text = '', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
BIN
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,527 @@
|
||||
# ***** 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 *****
|
||||
|
||||
import bpy
|
||||
import random
|
||||
import numpy as np
|
||||
from mathutils import Quaternion
|
||||
from . import Tools
|
||||
|
||||
def attr_default(obj, fcu_key):
|
||||
#check if the fcurve source belongs to a bone or obj
|
||||
if fcu_key[0][:10] == 'pose.bones':
|
||||
transform = fcu_key[0].split('.')[-1]
|
||||
attr = fcu_key[0].split('"')[-2]
|
||||
bone = fcu_key[0].split('"')[1]
|
||||
|
||||
if bone in obj.pose.bones:
|
||||
source = obj.pose.bones[bone]
|
||||
#if the bone not found still calculate the default based on the path
|
||||
elif '.rotation_quaternion' in fcu_key[0]:
|
||||
return [1.0, 0.0, 0.0, 0.0]
|
||||
elif '.scale' in fcu_key[0]:
|
||||
return [1.0, 1.0, 1.0]
|
||||
else:
|
||||
return [0]
|
||||
|
||||
#in case of shapekey animation
|
||||
elif fcu_key[0][:10] == 'key_blocks':
|
||||
attr = fcu_key[0].split('"')[1]
|
||||
if attr not in obj.data.shape_keys.key_blocks:
|
||||
return [0]
|
||||
shapekey = obj.data.shape_keys.key_blocks[attr]
|
||||
return 0 if shapekey.slider_min <= 0 else shapekey.slider_min
|
||||
#in case of transforms in object mode
|
||||
else:# fcu_key[0] in transform_types:
|
||||
source = obj
|
||||
transform = fcu_key[0]
|
||||
|
||||
#check when it's transform property of Blender
|
||||
if transform in source.bl_rna.properties.keys():
|
||||
if hasattr(source.bl_rna.properties[transform], 'default_array'):
|
||||
if len(source.bl_rna.properties[transform].default_array) > fcu_key[1]:
|
||||
attrvalue = source.bl_rna.properties[transform].default_array
|
||||
return attrvalue
|
||||
|
||||
#in case of property on object
|
||||
elif len(fcu_key[0].split('"')) > 1:
|
||||
if fcu_key[0].split('"')[1] in obj.keys():
|
||||
attr = fcu_key[0].split('"')[1]
|
||||
|
||||
if 'attr' not in locals():
|
||||
return [0]
|
||||
|
||||
#since blender 3 access to custom property settings changed
|
||||
if attr in source:
|
||||
if not isinstance(source[attr], float) and not isinstance(source[attr], int):
|
||||
return [0]
|
||||
id_attr = source.id_properties_ui(attr).as_dict()
|
||||
attrvalue = id_attr['default']
|
||||
return [attrvalue]
|
||||
|
||||
return [0]
|
||||
|
||||
def store_handles(key):
|
||||
#storing the distance between the handles bezier to the key value
|
||||
handle_r = key.handle_right[1] - key.co[1]
|
||||
handle_l = key.handle_left[1] - key.co[1]
|
||||
|
||||
return handle_r, handle_l
|
||||
|
||||
def apply_handles(key, handle_r, handle_l):
|
||||
key.handle_right[1] = key.co[1] + handle_r
|
||||
key.handle_left[1] = key.co[1] + handle_l
|
||||
|
||||
def selected_bones_filter(obj, fcu_data_path):
|
||||
if not bpy.context.window_manager.atb_ui.multikey.selectedbones:
|
||||
#if not obj.als.onlyselected:
|
||||
return False
|
||||
if obj.mode != 'POSE':
|
||||
return True
|
||||
transform_types = ['location', 'rotation_euler', 'rotation_quaternion', 'scale']
|
||||
#filter selected bones if option is turned on
|
||||
bones = [bone.path_from_id() for bone in bpy.context.selected_pose_bones]
|
||||
if fcu_data_path.split('].')[0]+']' not in bones and fcu_data_path not in transform_types:
|
||||
return True
|
||||
|
||||
# def filter_properties(obj, fcu):
|
||||
# 'Filter the W X Y Z attributes of the transform properties'
|
||||
# transform = fcu.data_path.split('"].')[1] if obj.mode == 'POSE' else fcu.data_path
|
||||
# index = fcu.array_index
|
||||
# if 'rotation' in transform:
|
||||
# if transform == 'rotation_euler':
|
||||
# index -= 1
|
||||
# transform = 'rotation'
|
||||
# transform = 'filter_' + transform
|
||||
# if not hasattr(bpy.context.scene.multikey, transform):
|
||||
# return False
|
||||
# attr = getattr(bpy.context.scene.multikey, transform)
|
||||
# #print(fcu.data_path, index, transform, attr[index])
|
||||
# return True if attr[index] else False
|
||||
|
||||
def add_value(key, value):
|
||||
if key.select_control_point:
|
||||
#store handle values in relative to the keyframe value
|
||||
handle_r, handle_l = store_handles(key)
|
||||
|
||||
key.co[1] += value
|
||||
apply_handles(key, handle_r, handle_l)
|
||||
|
||||
#calculate the difference between current value and the fcurve value
|
||||
def add_diff(fcurves, path, current_value, eval_array):
|
||||
array_value = current_value - eval_array
|
||||
if not any(array_value):
|
||||
return
|
||||
|
||||
for i, value in enumerate(array_value):
|
||||
fcu = fcurves.find(path, index = i)
|
||||
if fcu is None or Tools.filter_properties(bpy.context.scene.animtoolbox, fcu):
|
||||
continue
|
||||
for key in fcu.keyframe_points:
|
||||
add_value(key, value)
|
||||
fcu.update()
|
||||
|
||||
class ScaleValuesOp(bpy.types.Operator):
|
||||
"""Modal operator used while scale value is running before release"""
|
||||
bl_idname = "animtoolbox.multikey_scale_value"
|
||||
bl_label = "Scale Values"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
#reset the values for dragging
|
||||
self.stop = False
|
||||
ui = context.window_manager.atb_ui
|
||||
ui['is_dragging'] = True
|
||||
self.avg_value = dict()
|
||||
#dictionary of the keyframes and their original INITIAL values
|
||||
self.keyframes_values = dict()
|
||||
self.keyframes_handle_right = dict()
|
||||
self.keyframes_handle_left = dict()
|
||||
|
||||
#the average value for each fcurve
|
||||
self.keyframes_avg_value = dict()
|
||||
|
||||
for obj in context.selected_objects:
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
for fcu in fcurves:
|
||||
if obj.mode == 'POSE':
|
||||
if selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
if Tools.filter_properties(context.scene.animtoolbox, fcu):
|
||||
continue
|
||||
|
||||
#avg and value list per fcurve
|
||||
avg_value = []
|
||||
value_list = []
|
||||
for key in fcu.keyframe_points:
|
||||
if key.select_control_point:
|
||||
value_list.append(key.co[1])
|
||||
self.keyframes_values.update({key : key.co[1]})
|
||||
self.keyframes_handle_right.update({key : key.handle_right[1]})
|
||||
self.keyframes_handle_left.update({key : key.handle_left[1]})
|
||||
|
||||
if len(value_list)>1:
|
||||
#the average value with the scale property added to it
|
||||
avg_value = sum(value_list) / len(value_list)
|
||||
|
||||
for key in fcu.keyframe_points:
|
||||
if key.select_control_point:
|
||||
self.keyframes_avg_value.update({key : avg_value})
|
||||
|
||||
if not self.keyframes_avg_value:
|
||||
ui['is_dragging'] = False
|
||||
ui.multikey['scale'] = 1
|
||||
Tools.redraw_areas(['VIEW_3D'])
|
||||
return {'CANCELLED'}
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def modal(self, context, event):
|
||||
|
||||
ui = context.window_manager.atb_ui
|
||||
scale = ui.multikey.scale
|
||||
|
||||
#Quit the modal operator when the slider is released
|
||||
if self.stop:
|
||||
ui['is_dragging'] = False
|
||||
ui.multikey['scale'] = 1
|
||||
Tools.redraw_areas(['VIEW_3D'])
|
||||
#modal is being cancelled because of undo issue with the modal running through the property
|
||||
return {'FINISHED'}
|
||||
|
||||
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
|
||||
self.stop = True
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
for key, key_value in self.keyframes_values.items():
|
||||
if not key.select_control_point:
|
||||
continue
|
||||
if key not in self.keyframes_avg_value:
|
||||
continue
|
||||
avg_value = self.keyframes_avg_value[key]
|
||||
handle_right_value = self.keyframes_handle_right[key]
|
||||
handle_left_value = self.keyframes_handle_left[key]
|
||||
|
||||
#add the value of the distance from the average * scale factor
|
||||
key.co[1] = avg_value + ((key_value - avg_value)*scale)
|
||||
key.handle_right[1] = avg_value + ((handle_right_value - avg_value)*scale)
|
||||
key.handle_left[1] = avg_value + ((handle_left_value - avg_value)*scale)
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def scale_value(self, context):
|
||||
|
||||
ui = context.window_manager.atb_ui
|
||||
if ui.is_dragging:
|
||||
return
|
||||
obj = context.object
|
||||
|
||||
if obj is None:
|
||||
self['scale'] = 1
|
||||
return
|
||||
action = obj.animation_data.action
|
||||
|
||||
if action is None:
|
||||
self['scale'] = 1
|
||||
return
|
||||
|
||||
if context.mode == 'POSE' and not context.selected_pose_bones:
|
||||
self['scale'] = 1
|
||||
return
|
||||
|
||||
bpy.ops.animtoolbox.multikey_scale_value('INVOKE_DEFAULT')
|
||||
|
||||
def random_value(self, context):
|
||||
|
||||
for obj in context.selected_objects:
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
for fcu in fcurves:
|
||||
if obj.mode == 'POSE':
|
||||
if selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
if Tools.filter_properties(context.scene.animtoolbox, fcu):
|
||||
continue
|
||||
value_list = []
|
||||
threshold = bpy.context.window_manager.atb_ui.multikey.randomness
|
||||
for key in fcu.keyframe_points:
|
||||
if key.select_control_point == True:
|
||||
value_list.append(key.co[1])
|
||||
|
||||
if len(value_list) > 0:
|
||||
value = max(value_list)- min(value_list)
|
||||
for key in fcu.keyframe_points:
|
||||
add_value(key, value * random.uniform(-threshold, threshold))
|
||||
fcu.update()
|
||||
|
||||
def evaluate_combine(data_path, added_array, eval_array, array_default, influence):
|
||||
|
||||
if 'scale' in data_path:
|
||||
eval_array = eval_array * (added_array / array_default) ** influence
|
||||
elif 'rotation_quaternion' in data_path:
|
||||
#multiply first the influence with the w separatly
|
||||
added_array[0] = added_array[0] + (1- added_array[0])*(1 - influence)
|
||||
added_array[1:] *= influence
|
||||
eval_array = np.array(Quaternion(eval_array) @ Quaternion(added_array))# ** influence
|
||||
#if it's a custom property
|
||||
elif 'rotation_euler' not in data_path and 'location' not in data_path:
|
||||
eval_array = eval_array + (added_array - array_default) * influence
|
||||
|
||||
return eval_array
|
||||
|
||||
def evaluate_array(fcurves, fcu_path, frame, array_default = [0, 0, 0]):
|
||||
'''Create an array from all the indexes'''
|
||||
|
||||
array_len = len(array_default)
|
||||
fcu_array = []
|
||||
#get the missing arrays in case quaternion is not complete
|
||||
missing_arrays = []
|
||||
for i in range(array_len):
|
||||
fcu = fcurves.find(fcu_path, index = i)
|
||||
if fcu is None:
|
||||
missing_arrays.append(i)
|
||||
continue
|
||||
|
||||
fcu_array.append(fcu.evaluate(frame))
|
||||
|
||||
#In case it's a quaternion and missing attributes, then adding from default value
|
||||
if fcu_array and array_len == 4 and missing_arrays:
|
||||
for i in missing_arrays:
|
||||
fcu_array.insert(i, array_default[i])
|
||||
|
||||
if not len(fcu_array):
|
||||
return None
|
||||
return np.array(fcu_array)
|
||||
|
||||
def evaluate_layers(context, obj, anim_data, fcu, array_default):
|
||||
'''Calculate the evaluation of all the layers when using the nla'''
|
||||
|
||||
if not hasattr(anim_data, 'nla_tracks') or not anim_data.use_nla:
|
||||
return None
|
||||
nla_tracks = anim_data.nla_tracks
|
||||
if not len(nla_tracks):
|
||||
return None
|
||||
frame = context.scene.frame_current
|
||||
blend_types = {'ADD' : '+', 'SUBTRACT' : '-', 'MULTIPLY' : '*'}
|
||||
fcu_path = fcu.data_path
|
||||
|
||||
eval_array = array_default
|
||||
|
||||
for track in nla_tracks:
|
||||
if track.mute:
|
||||
continue
|
||||
if not len(track.strips):
|
||||
continue
|
||||
for strip in track.strips:
|
||||
if not strip.frame_start < frame < strip.frame_end:
|
||||
continue
|
||||
action = strip.action
|
||||
if action is None:
|
||||
continue
|
||||
blend_type = strip.blend_type
|
||||
|
||||
#get the influence value either from the attribute or the fcurve. function coming from bake
|
||||
influence = strip.influence
|
||||
if len(strip.fcurves):
|
||||
if not strip.fcurves[0].mute and len(strip.fcurves[0].keyframe_points):
|
||||
influence = strip.fcurves[0].evaluate(frame)
|
||||
|
||||
#evaluate the frame according to the strip settings
|
||||
frame_eval = frame
|
||||
#change the frame if the strip is on hold
|
||||
if frame < strip.frame_start:
|
||||
if strip.extrapolation == 'HOLD':
|
||||
frame_eval = strip.frame_start
|
||||
elif frame >= strip.frame_end:
|
||||
if strip.extrapolation == 'HOLD' or strip.extrapolation == 'HOLD_FORWARD':
|
||||
frame_eval = strip.frame_end
|
||||
|
||||
last_frame = strip.frame_start + (strip.frame_end - strip.frame_start) / strip.repeat
|
||||
|
||||
if strip.repeat > 1 and (frame) >= last_frame:
|
||||
action_range = (strip.action_frame_end * strip.scale - strip.action_frame_start * strip.scale)
|
||||
frame_eval = (((frame_eval - strip.frame_start) % (action_range)) + strip.frame_start)
|
||||
|
||||
if strip.use_reverse:
|
||||
frame_eval = last_frame - (frame_eval - strip.frame_start)
|
||||
offset = (strip.frame_start * 1/strip.scale - strip.action_frame_start) * strip.scale
|
||||
frame_eval = strip.frame_start * 1/strip.scale + (frame_eval - strip.frame_start) * 1/strip.scale - offset * 1/strip.scale
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame_eval, influence, array_default, blend_type, blend_types)
|
||||
|
||||
#Adding an extra layer from the action outside and on top of the nla
|
||||
tweak_mode = anim_data.use_tweak_mode
|
||||
if tweak_mode:
|
||||
anim_data.use_tweak_mode = False
|
||||
action = anim_data.action
|
||||
if action:
|
||||
influence = anim_data.action_influence
|
||||
blend_type = anim_data.action_blend_type
|
||||
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence, array_default, blend_type, blend_types)
|
||||
anim_data.use_tweak_mode = tweak_mode
|
||||
|
||||
return eval_array
|
||||
|
||||
|
||||
def evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence,
|
||||
array_default, blend_type, blend_types):
|
||||
'''Calculate the value based on the blend type'''
|
||||
|
||||
fcu_array = evaluate_array(fcurves, fcu_path, frame, array_default)
|
||||
if fcu_array is None:
|
||||
return eval_array
|
||||
###EVALUATION###
|
||||
if blend_type =='COMBINE':
|
||||
if 'location' in fcu_path or 'rotation_euler' in fcu_path:
|
||||
blend_type = 'ADD'
|
||||
if blend_type =='REPLACE':
|
||||
eval_array = eval_array * (1 - influence) + fcu_array * influence
|
||||
elif blend_type =='COMBINE':
|
||||
eval_array = evaluate_combine(fcu_path, fcu_array, eval_array, array_default, influence)
|
||||
else:
|
||||
eval_array = eval('eval_array' + blend_types[blend_type] + 'fcu_array' + '*' + str(influence))
|
||||
|
||||
return eval_array
|
||||
|
||||
def evaluate_value(self, context):
|
||||
ui = context.window_manager.atb_ui
|
||||
for obj in context.selected_objects:
|
||||
|
||||
anim_data = obj.animation_data
|
||||
if anim_data is None:
|
||||
return
|
||||
if anim_data.action is None:
|
||||
return
|
||||
|
||||
action = obj.animation_data.action
|
||||
fcu_paths = []
|
||||
transformations = ["rotation_quaternion","rotation_euler", "location", "scale"]
|
||||
if obj.mode == 'POSE':
|
||||
bonelist = context.selected_pose_bones if ui.multikey.selectedbones else obj.pose.bones
|
||||
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
for fcu in fcurves:
|
||||
if fcu in fcu_paths:
|
||||
continue
|
||||
if Tools.filter_properties(context.scene.animtoolbox, fcu):
|
||||
continue
|
||||
if obj.mode == 'POSE':
|
||||
if selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
|
||||
for bone in bonelist:
|
||||
#find the fcurve of the bone
|
||||
if fcu.data_path.rfind(bone.name) != 12 or fcu.data_path[12 + len(bone.name)] != '"':
|
||||
continue
|
||||
path_split = fcu.data_path.split('"].')
|
||||
|
||||
if len(path_split) <= 1:
|
||||
continue
|
||||
else:
|
||||
transform = fcu.data_path.split('"].')[1]
|
||||
if transform not in transformations:
|
||||
continue
|
||||
current_value = getattr(obj.pose.bones[bone.name], transform)
|
||||
else:
|
||||
transform = fcu.data_path
|
||||
current_value = getattr(obj, transform)
|
||||
|
||||
array_default = np.array(attr_default(obj, (fcu.data_path, fcu.array_index)))
|
||||
# array_default = np.array([attr_default(obj, (fcu.data_path, i)) for i in range(4)
|
||||
# if fcurves.find(fcu.data_path, index = i) is not None])
|
||||
eval_array = evaluate_layers(context, obj, anim_data, fcu, array_default)
|
||||
if eval_array is None:
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
eval_array = evaluate_array(fcurves, fcu.data_path, context.scene.frame_current, array_default)
|
||||
|
||||
#calculate the difference between current value and the fcurve value
|
||||
add_diff(fcurves, fcu.data_path, np.array(current_value), eval_array)
|
||||
|
||||
class MULTIKEY_OT_Multikey(bpy.types.Operator):
|
||||
"""Edit all selected keyframes"""
|
||||
bl_label = "Edit Selected Keyframes"
|
||||
bl_idname = "animtoolbox.multikey"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object and context.active_object.animation_data and bpy.context.scene.tool_settings.use_keyframe_insert_auto == False
|
||||
|
||||
def execute(self, context):
|
||||
evaluate_value(self, context)
|
||||
return {'FINISHED'}
|
||||
|
||||
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 = scale_value)
|
||||
randomness: bpy.props.FloatProperty(name="Randomness", description="Random Threshold of keyframes", default=0.1, min=0.0, max = 1.0, update = random_value)
|
||||
|
||||
#filters
|
||||
# filter_location: bpy.props.BoolVectorProperty(name="Location", description="Filter Location properties", default=(True, True, True), size = 3, options={'HIDDEN'})
|
||||
# filter_rotation: bpy.props.BoolVectorProperty(name="Rotation", description="Filter Rotation properties", default=(True, True, True, True), size = 4, options={'HIDDEN'})
|
||||
# filter_scale: bpy.props.BoolVectorProperty(name="Scale", description="Filter Scale properties", default=(True, True, True), size = 3, options={'HIDDEN'})
|
||||
|
||||
# class FilterProperties(bpy.types.Operator):
|
||||
# """Filter Location Rotation and Scale Properties"""
|
||||
# bl_idname = "fcurves.filter"
|
||||
# bl_label = "Filter Properties W X Y Z"
|
||||
# bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
# def invoke(self, context, event):
|
||||
# wm = context.window_manager
|
||||
# return wm.invoke_props_dialog(self, width = 200)
|
||||
|
||||
# def draw(self, context):
|
||||
# layout = self.layout
|
||||
# row = layout.row()
|
||||
# row.label(text = 'Location')
|
||||
# row.prop(context.scene.multikey, 'filter_location', text = '')
|
||||
# row = layout.row()
|
||||
# row.label(text = 'Rotation')
|
||||
# row.prop(context.scene.multikey, 'filter_rotation', text = '')
|
||||
# row = layout.row()
|
||||
# row.label(text = 'Scale')
|
||||
# row.prop(context.scene.multikey, 'filter_scale', text = '')
|
||||
|
||||
# def execute(self, context):
|
||||
# return {'CANCELLED'}
|
||||
|
||||
classes = (ScaleValuesOp, MULTIKEY_OT_Multikey)
|
||||
|
||||
#register, unregister = bpy.utils.register_classes_factory(classes)
|
||||
|
||||
def register():
|
||||
from bpy.utils import register_class
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
# bpy.types.Scene.animtoolbox.multikey = bpy.props.PointerProperty(type = MultikeyProperties, options={'LIBRARY_EDITABLE'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
||||
# del bpy.types.Scene.animtoolbox.multikey
|
||||
@@ -0,0 +1,700 @@
|
||||
# ***** 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 *****
|
||||
|
||||
import bpy
|
||||
from . import Rigger_Toolbox
|
||||
from . import multikey
|
||||
from . import Rigger_Toolbox
|
||||
from pathlib import Path
|
||||
from bpy.utils import register_class
|
||||
from bpy.utils import unregister_class
|
||||
import os
|
||||
|
||||
######################################################## MENUS ########################################################
|
||||
|
||||
class ANIMTOOLBOX_MT_Copy_Paste_Matrix(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Copy_Paste_Matrix'
|
||||
bl_label = "Copy Paste Matrix"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator('anim.copy_matrix', text = 'Copy World Matrix', icon ='DUPLICATE')
|
||||
layout.operator('anim.paste_matrix', text = 'Paste World Matrix', icon ='PASTEDOWN')
|
||||
layout.separator()
|
||||
layout.operator('anim.copy_relative_matrix', text = 'Copy Relative Matrix', icon ='DUPLICATE')
|
||||
layout.operator('anim.paste_relative_matrix', text = 'Paste Relative Matrix', icon ='PASTEDOWN')
|
||||
layout.separator()
|
||||
layout.prop_menu_enum(context.scene.animtoolbox, 'range_type', text = 'Frame Range Type')
|
||||
if context.scene.animtoolbox.range_type == 'RANGE':
|
||||
# split = layout.split(factor = 0.3)
|
||||
# layout.prop(context.scene.animtoolbox, 'bake_frame_start', text = 'Start')
|
||||
# layout.prop(context.scene.animtoolbox, 'bake_frame_end', text = 'End')
|
||||
layout.operator("anim.markers_bakerange", icon = 'MARKER', text ='Frame Range Markers Widget', depress = context.scene.animtoolbox.bake_frame_range)
|
||||
|
||||
class ANIMTOOLBOX_MT_Temp_Ctrls(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Temp_Ctrls'
|
||||
bl_label = "Temp Ctrls"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
custom_icons = preview_collections["main"]
|
||||
layout.operator('anim.bake_to_ctrl', text="World Space Ctrls", icon = 'WORLD')
|
||||
layout.operator('anim.bake_to_temp_fk', text="Temp FK Ctrls", icon = 'BONE_DATA')
|
||||
layout.operator('anim.bake_to_temp_ik', text="Temp IK Ctrls", icon = 'CON_KINEMATIC')
|
||||
layout.separator()
|
||||
layout.operator('anim.link_temp_chains', text="Link Temp Chains", icon = 'DECORATE_LINKED')
|
||||
layout.operator('anim.unlink_temp_chains', text="UnLink Temp Chains", icon = 'DECORATE_LINKED')
|
||||
layout.separator()
|
||||
layout.operator('anim.bake_temp_ctrls', text="Bake Temp Ctrls", icon_value = custom_icons["oven"].icon_id)
|
||||
layout.operator('anim.remove_bones_constraints', text="Cleanup", icon_value = custom_icons["trash"].icon_id)
|
||||
layout.prop_menu_enum(context.scene.btc, 'target', text = 'Bake To', icon = 'MOD_ARMATURE')
|
||||
layout.separator()
|
||||
|
||||
ctrl = context.object.animtoolbox.controller if context.object.animtoolbox.controller else context.object
|
||||
if ctrl:
|
||||
layout.prop(ctrl.animtoolbox, 'ctrls_enabled', text ='Temp Ctrls On/Off')
|
||||
|
||||
# layout.operator('anim.enable_tempctrls', text="On" , icon = 'HIDE_OFF')#icon = 'CONSTRAINT_BONE'
|
||||
# layout.operator('anim.disable_tempctrls', text="Off", icon = 'HIDE_ON')
|
||||
layout.separator()
|
||||
layout.prop_menu_enum(context.scene.btc, 'shape_type', icon = 'MESH_DATA')
|
||||
layout.prop(context.scene.btc, 'shape_size', slider = False, icon ='CON_SIZELIKE')
|
||||
layout.prop(context.scene.btc, 'color_set', text = '')
|
||||
# layout.prop(context.scene.btc, 'shape_type', icon = 'MESH_DATA', text ='')
|
||||
|
||||
class ANIMTOOLBOX_MT_Keyframe_Offset(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Keyframe_Offset'
|
||||
bl_label = "Interactive Keyframe Offset"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(context.scene.animtoolbox, 'keyframes_offset', slider = False)
|
||||
layout.operator('anim.select_keyframes_offset', text="Select Offset Keyframes", icon ='RESTRICT_SELECT_OFF')
|
||||
layout.operator('anim.apply_keyframes_offset', text="Apply Offset", icon = 'ANIM')
|
||||
|
||||
class ANIMTOOLBOX_MT_Blendings(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Blendings'
|
||||
bl_label = "Blendings"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
ui = context.window_manager.atb_ui
|
||||
layout.prop(ui, 'inbetween_worldmatrix', slider = True)
|
||||
layout.prop(context.scene.animtoolbox, 'inbetweener', slider = True)
|
||||
layout.prop(ui, 'blend_mirror', slider = True)
|
||||
|
||||
class ANIMTOOLBOX_MT_Multikey(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Multikey'
|
||||
bl_label = "Multikey"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
ui = context.window_manager.atb_ui
|
||||
layout.operator("animtoolbox.multikey", icon = 'ACTION_TWEAK')
|
||||
layout.prop(ui.multikey, 'scale', slider = True)
|
||||
layout.prop(ui.multikey, 'randomness', slider = True)
|
||||
|
||||
class ANIMTOOLBOX_MT_Convert_Rotations(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_Convert_Rotations'
|
||||
bl_label = "Convert Rotations"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator("anim.convert_rotation_mode", icon = 'DRIVER_ROTATIONAL_DIFFERENCE', text = 'Convert Rotation To')
|
||||
layout.operator("anim.find_rotation_mode", icon = 'VIEWZOOM', text = 'Recommend Euler Rotation')
|
||||
layout.prop_menu_enum(context.scene.animtoolbox, 'rotation_mode', text = 'Convert To')
|
||||
|
||||
class ANIMTOOLBOX_MT_operators(bpy.types.Menu):
|
||||
bl_idname = 'ANIMTOOLBOX_MT_menu_operators'
|
||||
bl_label = "AnimToolBox"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
ui = context.window_manager.atb_ui
|
||||
custom_icons = preview_collections["main"]
|
||||
|
||||
if context.area.type == 'VIEW_3D':
|
||||
layout.menu('ANIMTOOLBOX_MT_Temp_Ctrls', text = 'Temp Ctrls', icon_value = custom_icons["puppet"].icon_id)
|
||||
layout.separator()
|
||||
layout.operator('anim.share_keyframes', text = 'Share Keyframes', icon_value = custom_icons["sharekeys"].icon_id)
|
||||
layout.operator('anim.relative_cursor', text = 'Relative Cursor Pivot', icon_value = custom_icons["relative_cursor"].icon_id, depress = ui.relative_cursor)
|
||||
layout.operator("anim.markers_retimer", icon_value = custom_icons["retime"].icon_id, text ='Markers Retimer', depress = ui.markers_retimer)
|
||||
layout.menu('ANIMTOOLBOX_MT_Convert_Rotations', text = 'Convert Rotations', icon = 'DRIVER_ROTATIONAL_DIFFERENCE')
|
||||
|
||||
layout.separator()
|
||||
layout.menu('ANIMTOOLBOX_MT_Keyframe_Offset', text = 'Interactive Keyframe Offset', icon_value = custom_icons["keyframe_offset"].icon_id)
|
||||
layout.menu('ANIMTOOLBOX_MT_Blendings', text = 'Blendings and inbetweens', icon_value = custom_icons["sliders"].icon_id)
|
||||
layout.separator()
|
||||
layout.menu('ANIMTOOLBOX_MT_Copy_Paste_Matrix', text = 'Copy Paste Matrix', icon_value = custom_icons["copy_matrix"].icon_id)
|
||||
|
||||
if bpy.context.preferences.addons[__package__].preferences.multikey:
|
||||
layout.separator()
|
||||
layout.menu('ANIMTOOLBOX_MT_Multikey', text = 'Multikey', icon_value = custom_icons["multikey"].icon_id)
|
||||
|
||||
layout.separator(factor = 0.5)
|
||||
layout.operator('anim.switch_collections_visibility', icon = 'COLLECTION_COLOR_06', text = 'Animated Collections Visibilty')
|
||||
layout.operator('anim.isolate_pose_mode', icon_value = custom_icons["isolate"].icon_id, depress = scene.animtoolbox.isolate_pose_mode)
|
||||
layout.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
layout.separator()
|
||||
layout.prop(context.preferences.addons[__package__].preferences, 'quick_menu', text = 'Use Quick Icons Menu')
|
||||
# layout.prop(context.window_manager.atb_ui, 'quick_menu', text = 'Use Quick Icons Menu')
|
||||
# elif context.area.type == 'DOPESHEET_EDITOR':
|
||||
# layout.operator('wm.call_menu_pie', text="Pie AnimOffset").name = 'ANIMAIDE_MT_pie_anim_offset'
|
||||
# layout.menu('ANIMAIDE_MT_anim_offset')
|
||||
# layout.menu('ANIMAIDE_MT_anim_offset_mask')
|
||||
|
||||
# elif context.area.type == 'GRAPH_EDITOR':
|
||||
# layout.menu('ANIMAIDE_MT_curve_tools_pie')
|
||||
|
||||
######################################################## WorkSpaceTools ########################################################
|
||||
|
||||
class KeyframeOffsetTool(bpy.types.WorkSpaceTool):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_context_mode = 'POSE'
|
||||
bl_idname = 'animtoolbox.keyframe_offset'
|
||||
bl_label = 'Keyframe Offset Tool'
|
||||
bl_description = (
|
||||
'Offset the keyframes of the selected bone\n'
|
||||
'Shift + Click to select the bones with an offset\n'
|
||||
'Ctrl + Alt + Click to Apply the offset'
|
||||
)
|
||||
bl_icon = (Path(__file__).parent / "icons" / "ops.anim.keyframe_offset").as_posix()
|
||||
bl_keymap = (
|
||||
('anim.keyframe_offset', {'type': 'LEFTMOUSE', 'value': 'CLICK_DRAG'}, None),
|
||||
# ('anim.select_keyframes_offset', {'type': 'LEFTMOUSE', 'value': 'CLICK', 'shift': True}, None),
|
||||
('anim.apply_keyframes_offset', {'type': 'LEFTMOUSE', 'value': 'CLICK', 'ctrl': True, 'alt': True}, None),
|
||||
|
||||
('view3d.select_box', {'type': 'LEFTMOUSE', 'value': 'CLICK_DRAG', 'shift': True}, {'properties': [('mode', 'ADD')]}),
|
||||
('view3d.select_box', {'type': 'LEFTMOUSE', 'value': 'CLICK_DRAG', 'ctrl': True}, {'properties': [('mode', 'SUB')]}),
|
||||
('view3d.select_box', {'type': 'LEFTMOUSE', 'value': 'CLICK_DRAG', 'shift': True, 'ctrl': True}, {'properties': [('mode', 'AND')]}),
|
||||
('view3d.select', {'type': 'LEFTMOUSE', 'value': 'CLICK'}, {'properties': [('deselect_all', True)]}),
|
||||
('view3d.select', {'type': 'LEFTMOUSE', 'value': 'CLICK', 'shift': True}, {'properties': [('toggle', True)]}),
|
||||
('view3d.select', {'type': 'LEFTMOUSE', 'value': 'CLICK', 'alt': True}, {'properties': [('enumerate', True)]}),
|
||||
('view3d.select', {'type': 'LEFTMOUSE', 'value': 'CLICK', 'shift': True, 'alt': True}, {'properties': [('toggle', True), ('enumerate', True)]})
|
||||
|
||||
)
|
||||
|
||||
######################################################## PANELS ########################################################
|
||||
class ANIMTOOLBOX_PT_Panel:
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Animation"
|
||||
#bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object is not None
|
||||
|
||||
class ANIMTOOLBOX_PT_MainPanel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
#bl_category = "Animation"
|
||||
bl_label = "AnimToolBox"
|
||||
bl_idname = "ANIMTOOLBOX_PT_MainPanel"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw_header(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
self.layout.label(text='', icon_value = custom_icons["animtoolbox"].icon_id)
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.object
|
||||
if obj is None:
|
||||
return
|
||||
# layout = self.layout
|
||||
# row = layout.row(align = True)
|
||||
|
||||
# row.label(text = 'Support me on ')
|
||||
# row.operator("wm.url_open", text="Patreon", icon = 'FUND').url = "https://www.patreon.com/animtoolbox"
|
||||
|
||||
class TEMPCTRLS_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
#bl_category = "Animation"
|
||||
bl_label = "Temp Controls"
|
||||
bl_idname = "TEMPCTRLS_PT_Panel"
|
||||
bl_parent_id = 'ANIMTOOLBOX_PT_MainPanel'
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw_header(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
self.layout.label(text="", icon_value = custom_icons["puppet"].icon_id)
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.object
|
||||
btc = context.scene.btc
|
||||
if obj is None:
|
||||
return
|
||||
custom_icons = preview_collections["main"]
|
||||
oven_icon = custom_icons["oven"]
|
||||
# row = layout.row()
|
||||
ui = context.window_manager.atb_ui
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
col = box.column()
|
||||
col.operator('anim.bake_to_ctrl', text="WorldSpace Ctrls", icon ='WORLD') #
|
||||
col.operator('anim.bake_to_temp_fk', text="Temp FK Ctrls", icon = 'BONE_DATA')
|
||||
col.operator('anim.bake_to_temp_ik', text="Temp IK Ctrls", icon = 'CON_KINEMATIC')
|
||||
|
||||
# layout.separator(factor = 0.1)
|
||||
# box = layout.box()
|
||||
row = box.row()
|
||||
row.operator('anim.link_temp_chains', text="Link Temp Chains", icon = 'DECORATE_LINKED')
|
||||
row.prop(btc, 'linksettings', text = '', icon = 'SETTINGS')
|
||||
|
||||
if btc.linksettings:
|
||||
# insidebox = box.box()
|
||||
row = box.row()
|
||||
row.label(text = 'Link to Active Chain: ')
|
||||
row.prop(btc, 'link_to', text = '')
|
||||
row = box.row()
|
||||
row.operator('anim.unlink_temp_chains', text="UnLink Temp Chains", icon = 'UNLINKED')
|
||||
# row = insidebox.row()
|
||||
# row.label(text = 'Link from Chain: ')
|
||||
# row.prop(btc, 'link_from', text = '')
|
||||
# layout.separator(factor = 0.1)
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.operator('anim.bake_to_empties', text="WorldSpace Empties")
|
||||
row.operator('anim.empties_to_bones', text="", icon = 'EMPTY_AXIS')
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
# row.operator('anim.bake_constrained_bones', text="Quick Bake", icon = 'REC')
|
||||
row.operator('anim.bake_temp_ctrls', text="Bake Temp Ctrls", icon_value = oven_icon.icon_id)
|
||||
row.prop(btc, 'bakesettings', text = '', icon = 'SETTINGS')
|
||||
|
||||
if btc.bakesettings:
|
||||
insidebox = box.box()
|
||||
# split = insidebox.split(factor = 0.9)
|
||||
# split.prop(btc, 'smartbake', text = 'Smart Bake')
|
||||
# split.operator('fcurves.filter_ui', icon ='FILTER', text = '')
|
||||
# if btc.smartbake:
|
||||
# row.prop(btc, 'inbetween_keyframes')
|
||||
split = insidebox.split(factor = 0.3)
|
||||
split.label(text = 'Bake To:')
|
||||
# split.split(factor = 0.9)
|
||||
split_2 = split.split(factor = 0.9)
|
||||
split_2.prop(btc, 'target', text = '')
|
||||
split_2.operator('fcurves.filter_ui', icon ='FILTER', text = '')
|
||||
|
||||
split = insidebox.split(factor = 0.3)
|
||||
split.label(text = 'Bake Range:')
|
||||
split.prop(btc, 'bake_range_type', text = '')
|
||||
|
||||
if btc.bake_range_type == 'KEYFRAMES':
|
||||
row = insidebox.row()
|
||||
row.label(text = 'Keyframes: ')
|
||||
row.prop(btc, 'smartbake', text = 'Smart Bake')
|
||||
row.prop(btc, 'bake_layers', text = 'All Layers')
|
||||
row = insidebox.row()
|
||||
row.label(text = 'From:')
|
||||
row.prop(btc, 'from_origin', text = 'Origin')
|
||||
row.prop(btc, 'from_ctrl', text = 'Ctrls')
|
||||
|
||||
elif btc.bake_range_type == 'CUSTOM':
|
||||
row = insidebox.row()
|
||||
row.prop(btc, 'bake_range', text = '')
|
||||
|
||||
insidebox.separator(factor=0.1)
|
||||
row = insidebox.row()
|
||||
row.label(text = 'Clean: ')
|
||||
row.prop(btc, 'clean_constraints', text = 'Constraints')
|
||||
if btc.clean_constraints:
|
||||
row.prop(btc, 'clean_ctrls', text = 'Ctrls')
|
||||
|
||||
row = box.row()
|
||||
row.operator('anim.remove_bones_constraints', text="Cleanup", icon_value = custom_icons["trash"].icon_id)
|
||||
row.prop(btc, 'cleansettings', text = '', icon = 'SETTINGS')
|
||||
|
||||
if btc.cleansettings:
|
||||
insidebox = box.box()
|
||||
split = insidebox.split(factor = 0.4)
|
||||
split.label(text = 'Target: ')
|
||||
split.prop(btc, 'target', text = '')
|
||||
row = insidebox.row()
|
||||
row.label(text = 'Clean: ')
|
||||
row.prop(btc, 'clean_constraints', text = 'Constraints')
|
||||
if btc.clean_constraints:
|
||||
row.prop(btc, 'clean_ctrls', text = 'Ctrls')
|
||||
if btc.target == 'SELECTED':
|
||||
split = insidebox.split(factor = 0.68)
|
||||
split.label(text = 'ReBake Link Ctrls to Original: ')
|
||||
split.prop(btc, 'rebake_to_org', text = '')
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
# row = layout.row()
|
||||
|
||||
# row.prop(obj.animtoolbox, 'influence', text = 'Influence', slider = True)
|
||||
ctrl = obj.animtoolbox.controller if obj.animtoolbox.controller else obj
|
||||
if ctrl:
|
||||
enabled_icon = 'HIDE_OFF' if ctrl.animtoolbox.ctrls_enabled else 'HIDE_ON'
|
||||
row.prop(ctrl.animtoolbox, 'ctrls_enabled', text ='Temp Ctrls On/Off', icon = enabled_icon)
|
||||
# row = box.row()
|
||||
# row.prop(ui, 'temp_ctrls_switch', icon = 'RESTRICT_VIEW_OFF', text = '')
|
||||
# row.label(text = 'Temp Ctrls Switch: ')
|
||||
# row.prop(btc, 'target', text = '', icon = 'MOD_ARMATURE', icon_only = True)
|
||||
# if ui.temp_ctrls_switch:
|
||||
# # split = layout.split(factor = 0.225)
|
||||
# row = box.row()
|
||||
# row.operator('anim.enable_tempctrls', text="On" , icon = 'HIDE_OFF')#icon = 'CONSTRAINT_BONE'
|
||||
# row.operator('anim.disable_tempctrls', text="Off", icon = 'HIDE_ON')
|
||||
|
||||
box.separator(factor=0.1)
|
||||
row = box.row()
|
||||
row.prop(ui, 'temp_ctrls_shapes', icon = 'MESH_DATA', text = '')
|
||||
row.label(text = 'Temp Ctrls Shapes: ')
|
||||
row.prop(btc, 'target', text = '', icon = 'MOD_ARMATURE', icon_only = True)
|
||||
if ui.temp_ctrls_shapes:
|
||||
row = box.row()
|
||||
row.prop(btc, 'shape_size', slider = False)
|
||||
row.prop(btc, 'shape_type', text = '')
|
||||
row = layout.row()
|
||||
# split.label(text = 'Theme Color: ')
|
||||
row.prop(btc, 'color_set', text = '')
|
||||
|
||||
layout.separator()
|
||||
row = layout.row()
|
||||
row.operator('anim.btc_selections', text="Select", icon = 'CONSTRAINT_BONE')
|
||||
row.prop(btc, 'selection', text = '', icon = 'CON_ARMATURE')
|
||||
|
||||
class ANIMTOOLBOX_PT_Tools(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
bl_label = "Anim Tools"
|
||||
bl_idname = "ANIMTOOLBOX_PT_Tools"
|
||||
bl_parent_id = 'ANIMTOOLBOX_PT_MainPanel'
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw_header(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
layout = self.layout
|
||||
layout.label(text="", icon_value = custom_icons["toolbox"].icon_id)
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.object
|
||||
scene = context.scene
|
||||
atb = scene.animtoolbox
|
||||
if obj is None:
|
||||
return
|
||||
custom_icons = preview_collections["main"]
|
||||
layout = self.layout
|
||||
ui = context.window_manager.atb_ui
|
||||
row = layout.row()
|
||||
filter_text = 'Filter Tools Properties' if atb.filter_name == '' else 'Filters: ' + atb.filter_name
|
||||
filter_depress = False if atb.filter_name == '' else True
|
||||
row.operator('fcurves.filter_ui', icon ='FILTER', text = filter_text, depress = filter_depress)
|
||||
layout.separator()
|
||||
#layout.operator('anim.keyframes_offset', text="Offset Keyframes", icon = 'NEXT_KEYFRAME')
|
||||
split = layout.split(factor = 0.9)
|
||||
split.prop(context.scene.animtoolbox, 'keyframes_offset', slider = True)
|
||||
split.operator('anim.apply_keyframes_offset', text="", icon_value = custom_icons["apply"].icon_id)
|
||||
split.operator('anim.select_keyframes_offset', text="", icon_value = custom_icons["select"].icon_id)
|
||||
row = layout.row()
|
||||
row.operator('anim.share_keyframes', text = 'Share Keyframes', icon_value = custom_icons["sharekeys"].icon_id)
|
||||
row = layout.row()
|
||||
row.operator("anim.relative_cursor", text ='Relative Cursor Pivot', icon_value = custom_icons["relative_cursor"].icon_id, depress = ui.relative_cursor)
|
||||
row = layout.row()
|
||||
row.operator("anim.markers_retimer", icon_value = custom_icons["retime"].icon_id, text ='Markers Retimer', depress = ui.markers_retimer)
|
||||
layout.separator(factor = 0.5)
|
||||
|
||||
box = layout.box()
|
||||
# icon = 'DOWNARROW_HLT' if ui.copy_paste_matrix is True else 'RIGHTARROW_THIN'
|
||||
# split = box.split(factor = 0.9)
|
||||
# split.prop(ui, 'copy_paste_matrix', icon_value = custom_icons["copy_matrix"].icon_id, text = 'Copy Paste Matrices')
|
||||
# split.operator('fcurves.filter', icon ='FILTER', text = '')
|
||||
row = box.row()
|
||||
row.prop(ui, 'copy_paste_matrix', icon_value = custom_icons["copy_matrix"].icon_id, text = 'Copy Paste Matrices')
|
||||
if ui.copy_paste_matrix:
|
||||
row = box.row()
|
||||
row.label(text = 'Copy World Matrix :', icon = 'WORLD')
|
||||
row.operator('anim.copy_matrix', text = '', icon ='DUPLICATE')
|
||||
row.operator('anim.paste_matrix', text = '', icon ='PASTEDOWN')
|
||||
row = box.row()
|
||||
row.label(text = 'Copy Relative Matrix :', icon = 'LINKED')
|
||||
row.operator('anim.copy_relative_matrix', text = '', icon ='DUPLICATE')
|
||||
row.operator('anim.paste_relative_matrix', text = '', icon ='PASTEDOWN')
|
||||
split = box.split(factor = 0.32)
|
||||
split.label(text = 'Paste To')
|
||||
split.prop(context.scene.animtoolbox, 'range_type', text = '')
|
||||
if context.scene.animtoolbox.range_type == 'RANGE':
|
||||
row = box.row()
|
||||
row.prop(context.scene.animtoolbox, 'bake_frame_start', text = 'Start')
|
||||
row.prop(context.scene.animtoolbox, 'bake_frame_end', text = 'End')
|
||||
row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = context.scene.animtoolbox.bake_frame_range)
|
||||
layout.separator(factor = 0.5)
|
||||
|
||||
box = layout.box()
|
||||
# icon = 'DOWNARROW_HLT' if ui.Inbetweens is True else 'RIGHTARROW_THIN'
|
||||
# split = box.split(factor = 0.9)
|
||||
# split.prop(ui, 'Inbetweens', icon_value = custom_icons["sliders"].icon_id, text = 'Blendings / Inbetweens')
|
||||
# split.operator('fcurves.filter', icon ='FILTER', text = '')
|
||||
row = box.row()
|
||||
row.prop(ui, 'Inbetweens', icon_value = custom_icons["sliders"].icon_id, text = 'Blendings / Inbetweens')
|
||||
|
||||
if ui.Inbetweens:
|
||||
col = box.column()
|
||||
col.prop(ui, 'inbetween_worldmatrix', slider = True)
|
||||
col.prop(scene.animtoolbox, 'inbetweener', slider = True)
|
||||
col.prop(ui, 'blend_mirror', slider = True)
|
||||
|
||||
layout.separator(factor = 1)
|
||||
# my_icon = custom_icons["rotations"]
|
||||
split = layout.split(factor = 0.6)
|
||||
split.operator("anim.convert_rotation_mode", icon = 'DRIVER_ROTATIONAL_DIFFERENCE', text = 'Convert Rotation To')
|
||||
# split.operator("anim.convert_rotation_mode", text = 'Convert Rotation To', icon_value = my_icon.icon_id)
|
||||
split = split.split(factor = 0.3)
|
||||
split.operator("anim.find_rotation_mode", icon = 'VIEWZOOM', text = '')
|
||||
split.prop(context.scene.animtoolbox, 'rotation_mode', text = '')
|
||||
|
||||
class MULTIKEY_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
"""Add random value to selected keyframes"""
|
||||
bl_label = "Multikey"
|
||||
bl_idname = "MULTIKEY_PT_Panel"
|
||||
# bl_space_type = 'VIEW_3D'
|
||||
# bl_region_type = 'UI'
|
||||
# bl_category= 'Animation'
|
||||
bl_parent_id = 'ANIMTOOLBOX_PT_Tools'
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw_header(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
layout = self.layout
|
||||
layout.label(text="", icon_value = custom_icons["multikey"].icon_id)
|
||||
|
||||
def draw(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
layout = self.layout
|
||||
ui = context.window_manager.atb_ui
|
||||
# layout.separator()
|
||||
split = layout.split(factor=0.1, align = True)
|
||||
split.prop(ui.multikey, 'selectedbones', icon_value = custom_icons["selected_bones"].icon_id, text = '')
|
||||
# split = split.split(factor=0.1, align = True)
|
||||
#split.operator('fcurves.filter', icon ='FILTER', text = '')
|
||||
split.operator("animtoolbox.multikey", icon = 'ACTION_TWEAK')
|
||||
layout.separator()
|
||||
row = layout.row(align = True)
|
||||
row.prop(ui.multikey, 'scale')
|
||||
row.prop(ui.multikey, 'randomness', slider = True)
|
||||
|
||||
class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
bl_label = "Display"
|
||||
bl_idname = "ANIMTOOLBOX_PT_Display"
|
||||
bl_parent_id = 'ANIMTOOLBOX_PT_MainPanel'
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw_header(self, context):
|
||||
custom_icons = preview_collections["main"]
|
||||
self.layout.label(text="", icon_value = custom_icons["display"].icon_id)
|
||||
|
||||
def draw(self, context):
|
||||
scene = context.scene
|
||||
layout = self.layout
|
||||
ui = context.window_manager.atb_ui
|
||||
custom_icons = preview_collections["main"]
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
icon = 'DOWNARROW_HLT' if ui.gizmo_size is True else 'RIGHTARROW_THIN'
|
||||
row.prop(ui, 'gizmo_size', icon = icon, text = 'Add to Gizmo Size:')
|
||||
if ui.gizmo_size:
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'gizmo_size', text = '')
|
||||
row.operator('view3d.gizmo_size_up', text="", icon ='ADD')
|
||||
row.operator('view3d.gizmo_size_down', text="", icon ='REMOVE')
|
||||
layout.separator(factor = 0.5)
|
||||
|
||||
row = layout.row()
|
||||
col_vis_icon = 'OUTLINER_COLLECTION' if scene.animtoolbox.col_vis else 'COLLECTION_COLOR_06'
|
||||
row.operator('anim.switch_collections_visibility', icon = col_vis_icon, text = 'Animated Collections Visibilty', depress = scene.animtoolbox.col_vis)
|
||||
layout.separator(factor = 0.5)
|
||||
row = layout.row()
|
||||
row.operator('anim.isolate_pose_mode', icon_value = custom_icons["isolate"].icon_id, depress = scene.animtoolbox.isolate_pose_mode)
|
||||
|
||||
layout.separator(factor = 0.5)
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
row.prop(scene.animtoolbox, 'mp_settings', text = '', icon = 'SETTINGS')
|
||||
if scene.animtoolbox.mp_settings:
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_color_before', text = '')
|
||||
row.prop(scene.animtoolbox, 'mp_color_after', text = '')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_keyframe_scale', text = 'Scale Keyframes Box', icon = 'CUBE')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_points', text = 'Points')
|
||||
row.prop(scene.animtoolbox, 'mp_lines', text = 'Lines')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_handles', text = 'Handles')
|
||||
row.prop(scene.animtoolbox, 'mp_infront', text = 'In Front')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_display_frames', text = 'Display Keyframe Numbers')
|
||||
row = box.row()
|
||||
row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
|
||||
if context.object is None:
|
||||
return
|
||||
|
||||
split = box.split(factor = 0.4)
|
||||
split.label(text ='Frame Range')
|
||||
# row.prop(scene.animtoolbox, 'mp_frame_range', text = '')
|
||||
|
||||
split.prop(context.scene.animtoolbox, 'mp_frame_range', text = '')
|
||||
if context.scene.animtoolbox.mp_frame_range == 'MANUAL':
|
||||
row = box.row()
|
||||
row.prop(context.scene.animtoolbox, 'bake_frame_start', text = 'Start')
|
||||
row.prop(context.scene.animtoolbox, 'bake_frame_end', text = 'End')
|
||||
row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
|
||||
|
||||
elif context.scene.animtoolbox.mp_frame_range == 'AROUND':
|
||||
row = box.row()
|
||||
row.prop(context.scene.animtoolbox, 'mp_before', text = 'Before')
|
||||
row.prop(context.scene.animtoolbox, 'mp_after', text = 'After')
|
||||
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
|
||||
|
||||
|
||||
class RIGGERTOOLBOX_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
bl_label = "Rigger Toolbox"
|
||||
bl_idname = "RIGGERTOOLBOX_PT_Panel"
|
||||
bl_parent_id = 'ANIMTOOLBOX_PT_MainPanel'
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.object
|
||||
if obj is None:
|
||||
return
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
|
||||
col.operator("armature.add_bbone_widgets", text = "Add Bbone Widgets", icon = 'IPO_BEZIER')
|
||||
col.separator()
|
||||
col.operator("armature.add_chain_ctrls", text = "Add Chain Controls", icon = 'OUTLINER_DATA_ARMATURE')
|
||||
col.separator()
|
||||
col.operator("armature.merge", text = "Merge Rigs", icon = 'MOD_ARMATURE')
|
||||
|
||||
# Add-ons Preferences Update Panel
|
||||
# Define Panel classes for updating
|
||||
panels = [ANIMTOOLBOX_PT_MainPanel, TEMPCTRLS_PT_Panel, ANIMTOOLBOX_PT_Tools, ANIMTOOLBOX_PT_Display]
|
||||
|
||||
def update_panel(self, context):
|
||||
message = "Animtoolbox: Updating Panel locations has failed"
|
||||
try:
|
||||
for panel in panels:
|
||||
if "bl_rna" in panel.__dict__:
|
||||
unregister_class(panel)
|
||||
|
||||
for panel in panels:
|
||||
panel.bl_category = context.preferences.addons[__package__].preferences.category
|
||||
register_class(panel)
|
||||
|
||||
except Exception as e:
|
||||
print("\n[{}]\n{}\n\nError:\n{}".format(__package__, message, e))
|
||||
pass
|
||||
|
||||
def add_multikey(self, context):
|
||||
if bpy.context.preferences.addons[__package__].preferences is None:
|
||||
return
|
||||
if bpy.context.preferences.addons[__package__].preferences.multikey:
|
||||
multikey.register()
|
||||
register_class(MULTIKEY_PT_Panel)
|
||||
register_class(ANIMTOOLBOX_MT_Multikey)
|
||||
panels.append(MULTIKEY_PT_Panel)
|
||||
#panels = panels + (MULTIKEY_PT_Panel)
|
||||
elif hasattr(bpy.types, 'MULTIKEY_PT_Panel'):
|
||||
multikey.unregister()
|
||||
panels.remove(MULTIKEY_PT_Panel)
|
||||
unregister_class(MULTIKEY_PT_Panel)
|
||||
unregister_class(ANIMTOOLBOX_MT_Multikey)
|
||||
|
||||
def add_riggertoolbox(self, context):
|
||||
if bpy.context.preferences.addons[__package__].preferences is None:
|
||||
return
|
||||
if bpy.context.preferences.addons[__package__].preferences.riggertoolbox:
|
||||
Rigger_Toolbox.register()
|
||||
register_class(RIGGERTOOLBOX_PT_Panel)
|
||||
panels.append(RIGGERTOOLBOX_PT_Panel)
|
||||
#panels = panels + (RIGGERTOOLBOX_PT_Panel)
|
||||
elif hasattr(bpy.types, 'RIGGERTOOLBOX_PT_Panel'):
|
||||
Rigger_Toolbox.unregister()
|
||||
panels.remove(RIGGERTOOLBOX_PT_Panel)
|
||||
unregister_class(RIGGERTOOLBOX_PT_Panel)
|
||||
|
||||
classes = (ANIMTOOLBOX_MT_Copy_Paste_Matrix, ANIMTOOLBOX_MT_Temp_Ctrls, ANIMTOOLBOX_MT_operators, ANIMTOOLBOX_MT_Keyframe_Offset,
|
||||
ANIMTOOLBOX_MT_Blendings, ANIMTOOLBOX_MT_Convert_Rotations) + tuple(panels)
|
||||
|
||||
preview_collections = {}
|
||||
#list of all the custom icons
|
||||
icons_list = {'relative_cursor' : 'relative_cursor.png', 'puppet' : 'puppet.png', 'animtoolbox' : 'animtoolbox.png',
|
||||
'sliders' : 'slider-navigation.png', 'world_space' : 'earth-rotation.png', 'isolate' : 'isolation.png',
|
||||
'mt' : 'curve.png', 'toolbox' : 'tools.png', 'oven' : 'oven.png', 'retime' : 'retime.png', 'copy_matrix' : 'copy_world.png',
|
||||
'sharekeys' : 'sharekeys.png', 'sliders' : 'sliders.png', 'keyframe_offset' : 'keyframes_offset.png', 'display' : 'display.png',
|
||||
'switch' : 'switch.png', 'trash' : 'trash.png', 'apply' : 'apply.png', 'select' : 'select.png', 'worldspace' : 'WorldSpace.png',
|
||||
'multikey' : 'multikey.png', 'selected_bones' : 'selected_bones.png'}
|
||||
|
||||
def register_custom_icon():
|
||||
# Note that preview collections returned by bpy.utils.previews
|
||||
# are regular py objects - you can use them to store custom data.
|
||||
import bpy.utils.previews
|
||||
custom_icons = bpy.utils.previews.new()
|
||||
# path to the folder where the icon is
|
||||
# the path is calculated relative to this py file inside the addon folder
|
||||
icons_dir = os.path.join(os.path.dirname(__file__), "icons")
|
||||
# load a preview thumbnail of a file and store in the previews collection
|
||||
for iconname, icon_file in icons_list.items():
|
||||
custom_icons.load(iconname, os.path.join(icons_dir, icon_file), 'IMAGE')
|
||||
preview_collections["main"] = custom_icons
|
||||
|
||||
def draw_menu(self, context):
|
||||
if context.mode == 'OBJECT' or context.mode == 'POSE':
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
ui = context.window_manager.atb_ui
|
||||
|
||||
custom_icons = preview_collections["main"]
|
||||
layout.menu('ANIMTOOLBOX_MT_menu_operators') #, icon_value = custom_icons["toolbox"].icon_id, text =''
|
||||
|
||||
if not context.preferences.addons[__package__].preferences.quick_menu:
|
||||
return
|
||||
#Only Icons Menu
|
||||
temp_ctrls = custom_icons["puppet"]
|
||||
layout.menu('ANIMTOOLBOX_MT_Temp_Ctrls' , icon_value = temp_ctrls.icon_id, text ='')
|
||||
# layout.separator()
|
||||
layout.menu('ANIMTOOLBOX_MT_Copy_Paste_Matrix' , icon_value = custom_icons["copy_matrix"].icon_id, text ='')
|
||||
|
||||
layout.separator()
|
||||
layout.operator('anim.relative_cursor', text = '', depress = ui.relative_cursor, icon_value = custom_icons["relative_cursor"].icon_id)
|
||||
|
||||
layout.separator()
|
||||
layout.operator('anim.markers_retimer', text = '', depress = ui.markers_retimer, icon_value = custom_icons["retime"].icon_id)
|
||||
|
||||
layout.separator()
|
||||
layout.operator('anim.isolate_pose_mode', text = '', depress = scene.animtoolbox.isolate_pose_mode, icon_value = custom_icons["isolate"].icon_id)
|
||||
|
||||
layout.separator()
|
||||
layout.operator('object.motion_path_operator', text = '', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
Reference in New Issue
Block a user