2009-12-10 12:58:03 +00:00
|
|
|
# ##### 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,
|
2010-02-12 13:34:04 +00:00
|
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
2009-12-10 12:58:03 +00:00
|
|
|
#
|
|
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
|
|
|
|
# <pep8 compliant>
|
|
|
|
|
|
|
|
# rigify its self does not depend on this module, however some of the
|
|
|
|
# rigify templates use these utility functions.
|
|
|
|
#
|
|
|
|
# So even though this can be for general purpose use, this module was created
|
|
|
|
# for rigify so in some cases seemingly generic functions make assumptions
|
|
|
|
# that a generic function would need to check for.
|
|
|
|
|
2009-12-10 14:32:37 +00:00
|
|
|
import bpy
|
2010-04-11 14:22:27 +00:00
|
|
|
from mathutils import Vector
|
2009-12-10 14:32:37 +00:00
|
|
|
from rna_prop_ui import rna_idprop_ui_prop_get
|
|
|
|
|
|
|
|
DELIMITER = '-._'
|
|
|
|
EMPTY_LAYER = [False] * 32
|
|
|
|
|
2009-12-13 13:59:16 +00:00
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
def add_stretch_to(obj, from_name, to_name, name):
|
|
|
|
'''
|
|
|
|
Adds a bone that stretches from one to another
|
|
|
|
'''
|
|
|
|
|
|
|
|
mode_orig = obj.mode
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
|
|
|
|
arm = obj.data
|
|
|
|
stretch_ebone = arm.edit_bones.new(name)
|
|
|
|
stretch_name = stretch_ebone.name
|
|
|
|
del name
|
|
|
|
|
|
|
|
head = stretch_ebone.head = arm.edit_bones[from_name].head.copy()
|
|
|
|
#tail = stretch_ebone.tail = arm.edit_bones[to_name].head.copy()
|
|
|
|
|
|
|
|
# annoying exception for zero length bones, since its using stretch_to the rest pose doesnt really matter
|
|
|
|
#if (head - tail).length < 0.1:
|
|
|
|
if 1:
|
|
|
|
tail = stretch_ebone.tail = arm.edit_bones[from_name].tail.copy()
|
|
|
|
|
|
|
|
|
|
|
|
# Now for the constraint
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
|
|
|
|
stretch_pbone = obj.pose.bones[stretch_name]
|
|
|
|
|
|
|
|
con = stretch_pbone.constraints.new('COPY_LOCATION')
|
|
|
|
con.target = obj
|
|
|
|
con.subtarget = from_name
|
|
|
|
|
|
|
|
con = stretch_pbone.constraints.new('STRETCH_TO')
|
|
|
|
con.target = obj
|
|
|
|
con.subtarget = to_name
|
|
|
|
con.original_length = (head - tail).length
|
|
|
|
con.keep_axis = 'PLANE_X'
|
|
|
|
con.volume = 'NO_VOLUME'
|
|
|
|
|
|
|
|
bpy.ops.object.mode_set(mode=mode_orig)
|
2009-12-13 13:59:16 +00:00
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
return stretch_name
|
|
|
|
|
|
|
|
|
|
|
|
def copy_bone_simple(arm, from_bone, name, parent=False):
|
|
|
|
ebone = arm.edit_bones[from_bone]
|
|
|
|
ebone_new = arm.edit_bones.new(name)
|
|
|
|
|
|
|
|
if parent:
|
|
|
|
ebone_new.connected = ebone.connected
|
|
|
|
ebone_new.parent = ebone.parent
|
|
|
|
|
|
|
|
ebone_new.head = ebone.head
|
|
|
|
ebone_new.tail = ebone.tail
|
|
|
|
ebone_new.roll = ebone.roll
|
2010-01-10 18:53:15 +00:00
|
|
|
ebone_new.layer = list(ebone.layer)
|
2009-12-10 12:58:03 +00:00
|
|
|
return ebone_new
|
|
|
|
|
|
|
|
|
|
|
|
def copy_bone_simple_list(arm, from_bones, to_bones, parent=False):
|
|
|
|
|
|
|
|
if len(from_bones) != len(to_bones):
|
|
|
|
raise Exception("bone list sizes must match")
|
|
|
|
|
|
|
|
copy_bones = [copy_bone_simple(arm, bone_name, to_bones[i], True) for i, bone_name in enumerate(from_bones)]
|
|
|
|
|
|
|
|
# now we need to re-parent
|
|
|
|
for ebone in copy_bones:
|
|
|
|
parent = ebone.parent
|
|
|
|
if parent:
|
|
|
|
try:
|
|
|
|
i = from_bones.index(parent.name)
|
|
|
|
except:
|
|
|
|
i = -1
|
|
|
|
|
|
|
|
if i == -1:
|
|
|
|
ebone.parent = None
|
|
|
|
else:
|
|
|
|
ebone.parent = copy_bones[i]
|
|
|
|
|
|
|
|
return copy_bones
|
|
|
|
|
|
|
|
|
2009-12-11 16:30:27 +00:00
|
|
|
def blend_bone_list(obj, apply_bones, from_bones, to_bones, target_bone=None, target_prop="blend", blend_default=0.5):
|
2009-12-10 12:58:03 +00:00
|
|
|
|
|
|
|
if obj.mode == 'EDIT':
|
|
|
|
raise Exception("blending cant be called in editmode")
|
|
|
|
|
|
|
|
if len(apply_bones) != len(from_bones):
|
|
|
|
raise Exception("lists differ in length (from -> apply): \n\t%s\n\t%s" % (from_bones, apply_bones))
|
|
|
|
if len(apply_bones) != len(to_bones):
|
|
|
|
raise Exception("lists differ in length (to -> apply): \n\t%s\n\t%s" % (to_bones, apply_bones))
|
|
|
|
|
|
|
|
# setup the blend property
|
|
|
|
if target_bone is None:
|
|
|
|
target_bone = apply_bones[-1] # default to the last bone
|
|
|
|
|
|
|
|
prop_pbone = obj.pose.bones[target_bone]
|
2010-01-04 21:10:51 +00:00
|
|
|
if prop_pbone.get(target_bone) is None:
|
2009-12-10 12:58:03 +00:00
|
|
|
prop = rna_idprop_ui_prop_get(prop_pbone, target_prop, create=True)
|
2009-12-11 16:30:27 +00:00
|
|
|
prop_pbone[target_prop] = blend_default
|
2009-12-10 12:58:03 +00:00
|
|
|
prop["soft_min"] = 0.0
|
|
|
|
prop["soft_max"] = 1.0
|
|
|
|
|
2010-04-06 07:49:10 +00:00
|
|
|
driver_path = prop_pbone.path_from_id() + ('["%s"]' % target_prop)
|
2009-12-10 12:58:03 +00:00
|
|
|
|
|
|
|
def blend_target(driver):
|
Durian Request: Drivers Recode
Highlights:
* Support for Multi-Target Variables
This was the main reason for this recode. Previously, variables could only be used to give some RNA property used as an input source to the driver a name. However, this meant that effects such as Rotational Difference couldn't be used in conjunction with other effects and/or settings to achieve the powerful results. Now, a variable can take several input targets, perform some interesting operations on them, and spit out a representative value based on that.
* New Variable Types
With the introduction of multi-target variables, there are now 3 types of variable that can be used: single property (i.e. the only type previously), Rotational Difference (angle between two bones), and Distance (distance between two objects or bones).
* New Driver Types
In addition to the existing 'Average', 'Sum', and 'Expression' types, there is now the additional options of 'Minimum' and 'Maximum'. These take the smallest/largest value that one of the variables evaluates to.
* Fix for Driver F-Curve colouring bug
Newly added drivers did not get automatically coloured in the Graph Editor properly. Was caused by inappropriate notifiers being used.
Notes:
* This commit breaks existing 2.5 files with drivers (in other words, they are lost forever).
* Rigify has been corrected to work with the new system. The PyAPI for accessing targets used for the variables could still be made nicer (using subclassing to directly access?), but that is left for later.
* Version patching for 2.49 files still needs to be put back in place.
2010-01-04 21:15:45 +00:00
|
|
|
var = driver.variables.new()
|
|
|
|
var.name = target_bone
|
|
|
|
var.targets[0].id_type = 'OBJECT'
|
|
|
|
var.targets[0].id = obj
|
|
|
|
var.targets[0].data_path = driver_path
|
2009-12-10 12:58:03 +00:00
|
|
|
|
2010-01-19 19:07:09 +00:00
|
|
|
def blend_transforms(new_pbone, from_bone_name, to_bone_name):
|
|
|
|
con = new_pbone.constraints.new('COPY_TRANSFORMS')
|
2009-12-10 12:58:03 +00:00
|
|
|
con.target = obj
|
|
|
|
con.subtarget = from_bone_name
|
|
|
|
|
2010-01-19 19:07:09 +00:00
|
|
|
con = new_pbone.constraints.new('COPY_TRANSFORMS')
|
2009-12-10 12:58:03 +00:00
|
|
|
con.target = obj
|
|
|
|
con.subtarget = to_bone_name
|
|
|
|
|
2010-05-03 19:12:11 +00:00
|
|
|
fcurve = con.driver_add("influence")
|
2009-12-10 12:58:03 +00:00
|
|
|
driver = fcurve.driver
|
|
|
|
driver.type = 'AVERAGE'
|
|
|
|
fcurve.modifiers.remove(0) # grr dont need a modifier
|
|
|
|
|
|
|
|
blend_target(driver)
|
|
|
|
|
|
|
|
for i, new_bone_name in enumerate(apply_bones):
|
|
|
|
from_bone_name = from_bones[i]
|
|
|
|
to_bone_name = to_bones[i]
|
|
|
|
|
|
|
|
# allow skipping some bones by having None in the list
|
|
|
|
if None in (new_bone_name, from_bone_name, to_bone_name):
|
|
|
|
continue
|
|
|
|
|
|
|
|
new_pbone = obj.pose.bones[new_bone_name]
|
|
|
|
|
2010-01-19 19:07:09 +00:00
|
|
|
blend_transforms(new_pbone, from_bone_name, to_bone_name)
|
2009-12-10 12:58:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
def add_pole_target_bone(obj, base_bone_name, name, mode='CROSS'):
|
|
|
|
'''
|
|
|
|
Does not actually create a poll target, just the bone to use as a poll target
|
|
|
|
'''
|
|
|
|
mode_orig = obj.mode
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
|
|
|
|
arm = obj.data
|
|
|
|
|
|
|
|
poll_ebone = arm.edit_bones.new(name)
|
|
|
|
base_ebone = arm.edit_bones[base_bone_name]
|
|
|
|
poll_name = poll_ebone.name
|
|
|
|
parent_ebone = base_ebone.parent
|
|
|
|
|
|
|
|
base_head = base_ebone.head.copy()
|
|
|
|
base_tail = base_ebone.tail.copy()
|
|
|
|
base_dir = base_head - base_tail
|
|
|
|
|
|
|
|
parent_head = parent_ebone.head.copy()
|
|
|
|
parent_tail = parent_ebone.tail.copy()
|
|
|
|
parent_dir = parent_head - parent_tail
|
|
|
|
|
|
|
|
distance = (base_dir.length + parent_dir.length)
|
2009-12-13 13:59:16 +00:00
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
if mode == 'CROSS':
|
|
|
|
# direction from the angle of the joint
|
|
|
|
offset = base_dir.copy().normalize() - parent_dir.copy().normalize()
|
|
|
|
offset.length = distance
|
|
|
|
elif mode == 'ZAVERAGE':
|
|
|
|
# between both bones Z axis
|
2010-05-03 19:12:11 +00:00
|
|
|
z_axis_a = base_ebone.matrix.copy().rotation_part() * Vector((0.0, 0.0, -1.0))
|
|
|
|
z_axis_b = parent_ebone.matrix.copy().rotation_part() * Vector((0.0, 0.0, -1.0))
|
2009-12-10 12:58:03 +00:00
|
|
|
offset = (z_axis_a + z_axis_b).normalize() * distance
|
|
|
|
else:
|
|
|
|
# preset axis
|
2010-05-03 19:12:11 +00:00
|
|
|
offset = Vector((0.0, 0.0, 0.0))
|
2009-12-10 12:58:03 +00:00
|
|
|
if mode[0] == "+":
|
|
|
|
val = distance
|
|
|
|
else:
|
|
|
|
val = - distance
|
|
|
|
|
|
|
|
setattr(offset, mode[1].lower(), val)
|
|
|
|
|
|
|
|
poll_ebone.head = base_head + offset
|
|
|
|
poll_ebone.tail = base_head + (offset * (1.0 - (1.0 / 4.0)))
|
|
|
|
|
|
|
|
bpy.ops.object.mode_set(mode=mode_orig)
|
|
|
|
|
|
|
|
return poll_name
|
|
|
|
|
|
|
|
|
|
|
|
def get_side_name(name):
|
|
|
|
'''
|
|
|
|
Returns the last part of a string (typically a bone's name) indicating
|
|
|
|
whether it is a a left or right (or center, or whatever) bone.
|
|
|
|
Returns an empty string if nothing is found.
|
|
|
|
'''
|
|
|
|
if name[-2] in DELIMITER:
|
|
|
|
return name[-2:]
|
|
|
|
else:
|
|
|
|
return ""
|
|
|
|
|
2009-12-13 13:59:16 +00:00
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
def get_base_name(name):
|
|
|
|
'''
|
|
|
|
Returns the part of a string (typically a bone's name) corresponding to it's
|
|
|
|
base name (no sidedness, no ORG prefix).
|
|
|
|
'''
|
|
|
|
if name[-2] in DELIMITER:
|
|
|
|
return name[:-2]
|
|
|
|
else:
|
|
|
|
return name
|
|
|
|
|
|
|
|
|
|
|
|
def write_meta_rig(obj, func_name="metarig_template"):
|
|
|
|
'''
|
|
|
|
Write a metarig as a python script, this rig is to have all info needed for
|
|
|
|
generating the real rig with rigify.
|
|
|
|
'''
|
|
|
|
code = []
|
|
|
|
|
|
|
|
code.append("def %s():" % func_name)
|
|
|
|
code.append(" # generated by rigify.write_meta_rig")
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
code.append(" bpy.ops.object.mode_set(mode='EDIT')")
|
|
|
|
|
|
|
|
code.append(" obj = bpy.context.active_object")
|
|
|
|
code.append(" arm = obj.data")
|
|
|
|
|
|
|
|
arm = obj.data
|
|
|
|
# write parents first
|
|
|
|
bones = [(len(bone.parent_recursive), bone.name) for bone in arm.edit_bones]
|
|
|
|
bones.sort(key=lambda item: item[0])
|
|
|
|
bones = [item[1] for item in bones]
|
|
|
|
|
|
|
|
|
|
|
|
for bone_name in bones:
|
|
|
|
bone = arm.edit_bones[bone_name]
|
|
|
|
code.append(" bone = arm.edit_bones.new('%s')" % bone.name)
|
Mathutils refactor & include in sphinx generated docs, (TODO, include getset'ers in docs)
- Mathutils.MidpointVecs --> vector.lerp(other, fac)
- Mathutils.AngleBetweenVecs --> vector.angle(other)
- Mathutils.ProjectVecs --> vector.project(other)
- Mathutils.DifferenceQuats --> quat.difference(other)
- Mathutils.Slerp --> quat.slerp(other, fac)
- Mathutils.Rand: removed, use pythons random module
- Mathutils.RotationMatrix(angle, size, axis_flag, axis) --> Mathutils.RotationMatrix(angle, size, axis); merge axis & axis_flag args
- Matrix.scalePart --> Matrix.scale_part
- Matrix.translationPart --> Matrix.translation_part
- Matrix.rotationPart --> Matrix.rotation_part
- toMatrix --> to_matrix
- toEuler --> to_euler
- toQuat --> to_quat
- Vector.toTrackQuat --> Vector.to_track_quat
2010-01-25 09:44:04 +00:00
|
|
|
code.append(" bone.head[:] = %.4f, %.4f, %.4f" % bone.head.to_tuple(4))
|
|
|
|
code.append(" bone.tail[:] = %.4f, %.4f, %.4f" % bone.tail.to_tuple(4))
|
2009-12-10 12:58:03 +00:00
|
|
|
code.append(" bone.roll = %.4f" % bone.roll)
|
|
|
|
code.append(" bone.connected = %s" % str(bone.connected))
|
|
|
|
if bone.parent:
|
|
|
|
code.append(" bone.parent = arm.edit_bones['%s']" % bone.parent.name)
|
|
|
|
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
code.append("")
|
|
|
|
code.append(" bpy.ops.object.mode_set(mode='OBJECT')")
|
|
|
|
|
|
|
|
for bone_name in bones:
|
|
|
|
pbone = obj.pose.bones[bone_name]
|
|
|
|
pbone_written = False
|
|
|
|
|
|
|
|
# Only 1 level of props, simple types supported
|
|
|
|
for key, value in pbone.items():
|
|
|
|
if key.startswith("_"):
|
|
|
|
continue
|
|
|
|
|
|
|
|
if type(value) not in (float, str, int):
|
|
|
|
print("Unsupported ID Prop:", str((key, value)))
|
|
|
|
continue
|
|
|
|
|
|
|
|
if type(value) == str:
|
|
|
|
value = "'" + value + "'"
|
|
|
|
|
|
|
|
if not pbone_written: # only write bones we need
|
|
|
|
code.append(" pbone = obj.pose.bones['%s']" % bone_name)
|
|
|
|
|
|
|
|
code.append(" pbone['%s'] = %s" % (key, value))
|
|
|
|
|
|
|
|
return "\n".join(code)
|
|
|
|
|
|
|
|
|
|
|
|
# *** bone class collection ***
|
2009-12-13 13:59:16 +00:00
|
|
|
|
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
def bone_class_instance(obj, slots, name="BoneContainer"):
|
|
|
|
'''
|
|
|
|
bone collection utility class to help manage cases with
|
|
|
|
edit/pose/bone bones where switching modes can invalidate some of the members.
|
2009-12-13 13:59:16 +00:00
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
there are also utility functions for manipulating all members.
|
|
|
|
'''
|
2009-12-13 13:59:16 +00:00
|
|
|
|
2010-01-31 14:33:27 +00:00
|
|
|
attr_names = tuple(slots) # dont modify the original
|
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
if len(slots) != len(set(slots)):
|
|
|
|
raise Exception("duplicate entries found %s" % attr_names)
|
|
|
|
|
|
|
|
slots = list(slots) # dont modify the original
|
|
|
|
for i in range(len(slots)):
|
|
|
|
member = slots[i]
|
|
|
|
slots.append(member + "_b") # bone bone
|
|
|
|
slots.append(member + "_p") # pose bone
|
|
|
|
slots.append(member + "_e") # edit bone
|
|
|
|
|
|
|
|
class_dict = { \
|
|
|
|
"obj": obj, \
|
|
|
|
"attr_names": attr_names, \
|
2009-12-14 20:56:19 +00:00
|
|
|
"attr_initialize": _bone_class_instance_attr_initialize, \
|
2009-12-10 12:58:03 +00:00
|
|
|
"update": _bone_class_instance_update, \
|
|
|
|
"rename": _bone_class_instance_rename, \
|
|
|
|
"names": _bone_class_instance_names, \
|
|
|
|
"copy": _bone_class_instance_copy, \
|
|
|
|
"blend": _bone_class_instance_blend, \
|
|
|
|
}
|
|
|
|
|
|
|
|
instance = auto_class_instance(slots, name, class_dict)
|
|
|
|
return instance
|
|
|
|
|
2009-12-13 13:59:16 +00:00
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
def auto_class(slots, name="ContainerClass", class_dict=None):
|
|
|
|
|
|
|
|
if class_dict:
|
|
|
|
class_dict = class_dict.copy()
|
|
|
|
else:
|
|
|
|
class_dict = {}
|
|
|
|
|
|
|
|
class_dict["__slots__"] = tuple(slots)
|
|
|
|
|
|
|
|
return type(name, (object,), class_dict)
|
|
|
|
|
|
|
|
|
|
|
|
def auto_class_instance(slots, name="ContainerClass", class_dict=None):
|
|
|
|
return auto_class(slots, name, class_dict)()
|
|
|
|
|
|
|
|
|
2009-12-14 20:56:19 +00:00
|
|
|
def _bone_class_instance_attr_initialize(self, attr_names, bone_names):
|
|
|
|
''' Initializes attributes, both lists must be aligned
|
|
|
|
'''
|
|
|
|
for attr in self.attr_names:
|
|
|
|
i = attr_names.index(attr)
|
|
|
|
setattr(self, attr, bone_names[i])
|
|
|
|
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
def _bone_class_instance_update(self):
|
|
|
|
''' Re-Assigns bones from the blender data
|
|
|
|
'''
|
|
|
|
arm = self.obj.data
|
|
|
|
bbones = arm.bones
|
|
|
|
pbones = self.obj.pose.bones
|
|
|
|
ebones = arm.edit_bones
|
|
|
|
|
|
|
|
for member in self.attr_names:
|
|
|
|
name = getattr(self, member, None)
|
|
|
|
if name is not None:
|
2010-01-04 21:10:51 +00:00
|
|
|
setattr(self, member + "_b", bbones.get(name))
|
|
|
|
setattr(self, member + "_p", pbones.get(name))
|
|
|
|
setattr(self, member + "_e", ebones.get(name))
|
2009-12-10 12:58:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _bone_class_instance_rename(self, attr, new_name):
|
|
|
|
''' Rename bones, editmode only
|
|
|
|
'''
|
|
|
|
|
|
|
|
if self.obj.mode != 'EDIT':
|
|
|
|
raise Exception("Only rename in editmode supported")
|
|
|
|
|
|
|
|
ebone = getattr(self, attr + "_e")
|
|
|
|
ebone.name = new_name
|
|
|
|
|
|
|
|
# we may not get what is asked for so get the name from the editbone
|
|
|
|
setattr(self, attr, ebone.name)
|
|
|
|
|
|
|
|
|
|
|
|
def _bone_class_instance_copy(self, from_fmt="%s", to_fmt="%s", exclude_attrs=(), base_names=None):
|
|
|
|
from_name_ls = []
|
|
|
|
new_name_ls = []
|
|
|
|
new_slot_ls = []
|
|
|
|
|
|
|
|
for attr in self.attr_names:
|
2009-12-13 13:59:16 +00:00
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
if attr in exclude_attrs:
|
|
|
|
continue
|
2009-12-13 13:59:16 +00:00
|
|
|
|
2009-12-10 12:58:03 +00:00
|
|
|
bone_name_orig = getattr(self, attr)
|
|
|
|
ebone = getattr(self, attr + "_e")
|
|
|
|
# orig_names[attr] = bone_name_orig
|
|
|
|
|
|
|
|
# insert formatting
|
|
|
|
if from_fmt != "%s":
|
|
|
|
bone_name = from_fmt % bone_name_orig
|
|
|
|
ebone.name = bone_name
|
|
|
|
bone_name = ebone.name # cant be sure we get what we ask for
|
|
|
|
else:
|
|
|
|
bone_name = bone_name_orig
|
|
|
|
|
|
|
|
setattr(self, attr, bone_name)
|
|
|
|
|
|
|
|
new_slot_ls.append(attr)
|
|
|
|
from_name_ls.append(bone_name)
|
|
|
|
if base_names:
|
|
|
|
bone_name_orig = base_names[bone_name_orig]
|
|
|
|
new_name_ls.append(to_fmt % bone_name_orig)
|
|
|
|
|
|
|
|
new_bones = copy_bone_simple_list(self.obj.data, from_name_ls, new_name_ls, True)
|
|
|
|
new_bc = bone_class_instance(self.obj, new_slot_ls)
|
|
|
|
|
|
|
|
for i, attr in enumerate(new_slot_ls):
|
|
|
|
ebone = new_bones[i]
|
|
|
|
setattr(new_bc, attr + "_e", ebone)
|
|
|
|
setattr(new_bc, attr, ebone.name)
|
|
|
|
|
|
|
|
return new_bc
|
|
|
|
|
|
|
|
|
|
|
|
def _bone_class_instance_names(self):
|
|
|
|
return [getattr(self, attr) for attr in self.attr_names]
|
|
|
|
|
|
|
|
|
|
|
|
def _bone_class_instance_blend(self, from_bc, to_bc, target_bone=None, target_prop="blend"):
|
|
|
|
'''
|
|
|
|
Use for blending bone chains.
|
|
|
|
|
|
|
|
blend_target = (bone_name, bone_property)
|
|
|
|
default to the last bone, blend prop
|
|
|
|
|
|
|
|
XXX - toggles editmode, need to re-validate all editbones :(
|
|
|
|
'''
|
|
|
|
|
|
|
|
if self.attr_names != from_bc.attr_names or self.attr_names != to_bc.attr_names:
|
|
|
|
raise Exception("can only blend between matching chains")
|
|
|
|
|
|
|
|
apply_bones = [getattr(self, attr) for attr in self.attr_names]
|
|
|
|
from_bones = [getattr(from_bc, attr) for attr in from_bc.attr_names]
|
|
|
|
to_bones = [getattr(to_bc, attr) for attr in to_bc.attr_names]
|
|
|
|
|
|
|
|
blend_bone_list(self.obj, apply_bones, from_bones, to_bones, target_bone, target_prop)
|