Use a best fit mirror location to match pairs rather then bone names only. Location matching works well, but name matching method can be added in also if its needed.
286 lines
8.3 KiB
Python
286 lines
8.3 KiB
Python
#!BPY
|
|
|
|
"""
|
|
Name: 'Armature Symmetry'
|
|
Blender: 242
|
|
Group: 'Animation'
|
|
Tooltip: 'Make an Armature symetrical'
|
|
"""
|
|
|
|
__author__ = "Campbell Barton"
|
|
__url__ = ("blender", "blenderartist")
|
|
__version__ = "1.0 2006-7-26"
|
|
|
|
__doc__ = """\
|
|
This script creates perfectly symmetrical armatures.
|
|
based on the best fit when comparing the mirrored locations of 2 bones.
|
|
Hidden bones are ignored, and you can optionaly only operate on selected bones
|
|
"""
|
|
|
|
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
# Script copyright (C) Campbell J Barton 2006
|
|
#
|
|
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
# --------------------------------------------------------------------------
|
|
|
|
import Blender
|
|
from Blender import Scene
|
|
Vector= Blender.Mathutils.Vector
|
|
|
|
|
|
def VecXFlip(vec):
|
|
x,y,z= vec
|
|
return Vector(-x,y,z)
|
|
|
|
def editbone_mirror_diff(editbone1, editbone2):
|
|
'''
|
|
X Mirror bone compare
|
|
return a float representing the difference between the 2 bones
|
|
the smaller the better the match
|
|
'''
|
|
h1= editbone1.head
|
|
h2= editbone2.head
|
|
|
|
t1= editbone1.tail
|
|
t2= editbone2.tail
|
|
|
|
# Mirror bone 2's location
|
|
h2= VecXFlip(h2)
|
|
t2= VecXFlip(t2)
|
|
|
|
#return (h1-h2).length + (t1-t2).length
|
|
|
|
# For this function its easier to return the bones also
|
|
return ((h1-h2).length + (t1-t2).length)/2, editbone1, editbone2
|
|
|
|
def editbone_mirror_merge(editbone1, editbone2, PREF_MODE_L2R, PREF_MODE_R2L):
|
|
'''
|
|
Merge these 2 bones mirror
|
|
'''
|
|
h1= editbone1.head
|
|
h2= editbone2.head
|
|
|
|
t1= editbone1.tail
|
|
t2= editbone2.tail
|
|
|
|
if PREF_MODE_L2R and PREF_MODE_R2L:
|
|
# Median, flip bone 2's locations and average, then apply to editbone1, flip and apply to editbone2
|
|
h2_f= VecXFlip(h2)
|
|
t2_f= VecXFlip(t2)
|
|
|
|
h_med= (h1+h2_f)*0.5 # middle between t1 and flipped t2
|
|
t_med= (t1+t2_f)*0.5 # middle between h1 and flipped h2
|
|
|
|
# Apply the median to editbone1
|
|
editbone1.head= h_med
|
|
editbone1.tail= t_med
|
|
|
|
# Flip in place for editbone2
|
|
h_med.x= -h_med.x
|
|
t_med.x= -t_med.x
|
|
|
|
# Apply the median to editbone2
|
|
editbone2.head= h_med
|
|
editbone2.tail= t_med
|
|
|
|
# Average the roll, this might need some logical work, but looks good for now.
|
|
r1= editbone1.roll
|
|
r2= -editbone2.roll
|
|
# print 'rolls are', r1,r2
|
|
r_med= (r1+r2)/2
|
|
# print 'new roll is', r_med
|
|
editbone1.roll= r_med
|
|
editbone2.roll= -r_med # mirror roll
|
|
|
|
else: # Copy from 1 side to another
|
|
|
|
# Crafty function we can use so L>R and R>L can use the same code
|
|
def IS_XMIRROR_SOURCE(xval):
|
|
'''Source means is this the value we want to copy from'''
|
|
|
|
if PREF_MODE_L2R:
|
|
if xval<0: return True
|
|
else: return False
|
|
else: # PREF_MODE_R2L
|
|
if xval<0: return False
|
|
else: return True
|
|
|
|
if IS_XMIRROR_SOURCE( h1.x ):# head bone 1s negative, so copy it to h2
|
|
editbone2.head= VecXFlip(h1)
|
|
else: # assume h2.x<0 - not a big deal if were wrong, its unlikely to ever happen because the bones would both be on the same side.
|
|
# head bone 2s negative, so copy it to h1
|
|
editbone1.head= VecXFlip(h2)
|
|
|
|
# Same as above for tail
|
|
if IS_XMIRROR_SOURCE(t1.x):
|
|
editbone2.tail= VecXFlip(t1)
|
|
else:
|
|
editbone1.tail= VecXFlip(t2)
|
|
|
|
|
|
|
|
# Copy roll from 1 bone to another, use the head's location to deciede which side were on.
|
|
if IS_XMIRROR_SOURCE(editbone1.head):
|
|
editbone2.roll= -editbone1.roll
|
|
else:
|
|
editbone1.roll= -editbone2.roll
|
|
|
|
|
|
def armature_symetry(arm_ob, PREF_MAX_DIST, PREF_XMID_SNAP, PREF_XZERO_THRESH, PREF_MODE_L2R, PREF_MODE_R2L, PREF_SEL_ONLY):
|
|
arm_data= arm_ob.data
|
|
arm_data.makeEditable()
|
|
|
|
# Get the bones
|
|
bones= []
|
|
H= Blender.Armature.HIDDEN_EDIT
|
|
S= Blender.Armature.BONE_SELECTED
|
|
|
|
if PREF_SEL_ONLY:
|
|
for eb in arm_data.bones.values():
|
|
options= eb.options
|
|
if H not in options and S in options:
|
|
bones.append(eb)
|
|
else:
|
|
# All non hidden bones
|
|
for eb in arm_data.bones.values():
|
|
options= eb.options
|
|
if H not in options:
|
|
bones.append(eb)
|
|
del H
|
|
del S
|
|
|
|
|
|
tot_editbones= len(bones)
|
|
tot_editbones_modified= 0
|
|
|
|
if PREF_XMID_SNAP:
|
|
# Remove middle bones
|
|
# reverse loop so we can pop
|
|
for eb_idx in xrange(len(bones)-1, -1, -1):
|
|
edit_bone= bones[eb_idx]
|
|
#print edit_bone.options
|
|
if abs(edit_bone.head.x) + abs(edit_bone.tail.x) <= PREF_XZERO_THRESH/2:
|
|
# print 'Found Middle Bone'
|
|
# This is a center bone, clamp and remove
|
|
edit_bone.tail.x= edit_bone.head.x= 0
|
|
del bones[eb_idx]
|
|
|
|
tot_editbones_modified+=1
|
|
|
|
|
|
bone_comparisons= []
|
|
|
|
# Compare every bone with every other bone, shouldent be too slow, though we may want to cache head/tale values
|
|
# The 2 for's only compare once
|
|
for eb_idx_a in xrange(len(bones)-1, -1, -1):
|
|
edit_bone_a= bones[eb_idx_a]
|
|
for eb_idx_b in xrange(eb_idx_a-1, -1, -1):
|
|
edit_bone_b= bones[eb_idx_b]
|
|
# print 'Adding comparison', eb_idx_a, eb_idx_b
|
|
# Error float is first so we can sort it
|
|
bone_comparisons.append(editbone_mirror_diff(edit_bone_a, edit_bone_b))
|
|
|
|
|
|
bone_comparisons.sort() # best matches first
|
|
|
|
# Make a dict of bone names that have been used so we dont mirror more then once
|
|
bone_mirrored= {}
|
|
|
|
for error, editbone1, editbone2 in bone_comparisons:
|
|
# print 'Trying to merge at error %.3f' % error
|
|
if error > PREF_MAX_DIST:
|
|
# print 'breaking, max error limit reached PREF_MAX_DIST: %.3f' % PREF_MAX_DIST
|
|
break
|
|
|
|
if not bone_mirrored.has_key(editbone1.name) and not bone_mirrored.has_key(editbone2.name):
|
|
# Were not used- execute the mirror
|
|
editbone_mirror_merge(editbone1, editbone2, PREF_MODE_L2R, PREF_MODE_R2L)
|
|
# print 'Merging bones'
|
|
# Add ourselves so we arnt touced again
|
|
bone_mirrored[editbone1.name] = None # dummy value
|
|
bone_mirrored[editbone2.name] = None # dummy value
|
|
|
|
# If both are true then we changed 2 bones
|
|
tot_editbones_modified+= PREF_MODE_L2R + PREF_MODE_R2L
|
|
|
|
arm_data.update() # out of editmode
|
|
|
|
# Print results
|
|
if PREF_SEL_ONLY:
|
|
msg= 'moved %i bones of %i selected' % (tot_editbones_modified, tot_editbones)
|
|
else:
|
|
msg= 'moved %i bones of %i visible' % (tot_editbones_modified, tot_editbones)
|
|
|
|
Blender.Draw.PupMenu(msg)
|
|
|
|
|
|
def main():
|
|
# Cant be in editmode for armature.makeEditable()
|
|
scn= Scene.GetCurrent()
|
|
arm_ob= scn.getActiveObject()
|
|
|
|
if not arm_ob or arm_ob.getType()!='Armature':
|
|
Blender.Draw.PupMenu('No Armature object selected.')
|
|
return
|
|
|
|
Blender.Window.EditMode(0)
|
|
Draw= Blender.Draw
|
|
# Defaults
|
|
PREF_XMID_SNAP= Draw.Create(1)
|
|
PREF_MAX_DIST= Draw.Create(0.4)
|
|
PREF_XZERO_THRESH= Draw.Create(0.02)
|
|
|
|
#PREF_MODE= Draw.Create(0) # THIS IS TOOO CONFUSING, HAVE 2 BUTTONS AND MAKE THE MODE FROM THEM.
|
|
PREF_MODE_L2R= Draw.Create(1)
|
|
PREF_MODE_R2L= Draw.Create(0)
|
|
PREF_SEL_ONLY= Draw.Create(0)
|
|
|
|
pup_block = [\
|
|
'Left (-), Right (+)',\
|
|
('Left > Right', PREF_MODE_L2R, 'Copy from the Left to Right of the mesh. Enable Both for a mid loc.'),\
|
|
('Right > Left', PREF_MODE_R2L, 'Copy from the Right to Left of the mesh. Enable Both for a mid loc.'),\
|
|
'',\
|
|
('MaxDist:', PREF_MAX_DIST, 0.0, 4.0, 'Maximum difference in mirror bones to match up pairs.'),\
|
|
('XZero limit:', PREF_XZERO_THRESH, 0.0, 2.0, 'Tolorence for locking bones into the middle (X/zero).'),\
|
|
('XMidSnap Bones', PREF_XMID_SNAP, 'Snap middle verts to X Zero (uses XZero limit)'),\
|
|
('Selected Only', PREF_SEL_ONLY, 'Only xmirror selected bones.'),\
|
|
]
|
|
|
|
if not Draw.PupBlock("X Mirror mesh tool", pup_block):
|
|
return
|
|
|
|
|
|
PREF_XMID_SNAP= PREF_XMID_SNAP.val
|
|
PREF_MAX_DIST= PREF_MAX_DIST.val
|
|
PREF_MODE_L2R= PREF_MODE_L2R.val
|
|
PREF_MODE_R2L= PREF_MODE_R2L.val
|
|
PREF_XZERO_THRESH= PREF_XZERO_THRESH.val
|
|
PREF_SEL_ONLY= PREF_SEL_ONLY.val
|
|
|
|
# If both are off assume mid-point and enable both
|
|
if not PREF_MODE_R2L and not PREF_MODE_L2R:
|
|
PREF_MODE_R2L= PREF_MODE_L2R= True
|
|
|
|
armature_symetry(arm_ob, PREF_MAX_DIST, PREF_XMID_SNAP, PREF_XZERO_THRESH, PREF_MODE_L2R, PREF_MODE_R2L, PREF_SEL_ONLY)
|
|
|
|
if __name__=='__main__':
|
|
main()
|
|
|
|
|
|
|
|
|