549 lines
26 KiB
Python
549 lines
26 KiB
Python
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
|
|
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() |