forked from bartvdbraak/blender
3e1a5ce7a2
moved bpy into bpy.data and bpy will be eventually replace the root level 'Blender' module. currently we have bpy.library bpy.config and bpy.data
326 lines
9.2 KiB
Python
326 lines
9.2 KiB
Python
#!BPY
|
|
|
|
"""
|
|
Name: 'Armature Symmetry'
|
|
Blender: 242
|
|
Group: 'Armature'
|
|
Tooltip: 'Make an Armature symmetrical'
|
|
"""
|
|
|
|
__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 optionally 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
|
|
import bpy
|
|
Vector= Blender.Mathutils.Vector
|
|
|
|
|
|
def VecXFlip(vec):
|
|
'''
|
|
Return a copy of this vector x flipped.
|
|
'''
|
|
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 bone2's location
|
|
h2= VecXFlip(h2)
|
|
t2= VecXFlip(t2)
|
|
|
|
#return (h1-h2).length + (t1-t2).length # returns the length only
|
|
|
|
# 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 to their mirrored locations
|
|
'''
|
|
h1= editbone1.head
|
|
h2= editbone2.head
|
|
|
|
t1= editbone1.tail
|
|
t2= editbone2.tail
|
|
|
|
if PREF_MODE_L2R and PREF_MODE_R2L:
|
|
# Median, flip bone2'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 decide which side it's 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):
|
|
|
|
'''
|
|
Main function that does all the work,
|
|
return the number of
|
|
'''
|
|
arm_data= arm_ob.data
|
|
arm_data.makeEditable()
|
|
|
|
# Get the bones
|
|
bones= []
|
|
HIDDEN_EDIT= Blender.Armature.HIDDEN_EDIT
|
|
BONE_SELECTED= Blender.Armature.BONE_SELECTED
|
|
|
|
if PREF_SEL_ONLY:
|
|
for eb in arm_data.bones.values():
|
|
options= eb.options
|
|
if HIDDEN_EDIT not in options and BONE_SELECTED in options:
|
|
bones.append(eb)
|
|
else:
|
|
# All non hidden bones
|
|
for eb in arm_data.bones.values():
|
|
options= eb.options
|
|
if HIDDEN_EDIT not in options:
|
|
bones.append(eb)
|
|
|
|
del HIDDEN_EDIT # remove temp variables
|
|
del BONE_SELECTED
|
|
|
|
# Store the numder of bones we have modified for a message
|
|
tot_editbones= len(bones)
|
|
tot_editbones_modified= 0
|
|
|
|
if PREF_XMID_SNAP:
|
|
# Remove bones that are in the middle (X Zero)
|
|
# reverse loop so we can remove items in the list.
|
|
for eb_idx in xrange(len(bones)-1, -1, -1):
|
|
edit_bone= bones[eb_idx]
|
|
if abs(edit_bone.head.x) + abs(edit_bone.tail.x) <= PREF_XZERO_THRESH/2:
|
|
|
|
# This is a center bone, clamp and remove from the bone list so we dont use again.
|
|
if edit_bone.tail.x or edit_bone.head.x:
|
|
tot_editbones_modified += 1
|
|
|
|
edit_bone.tail.x= edit_bone.head.x= 0
|
|
del bones[eb_idx]
|
|
|
|
|
|
|
|
|
|
bone_comparisons= []
|
|
|
|
# Compare every bone with every other bone, shouldn't be too slow.
|
|
# These 2 "for" loops 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]
|
|
# Error float the first value from editbone_mirror_diff() so we can sort the resulting list.
|
|
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 aren't touched again
|
|
bone_mirrored[editbone1.name] = None # dummy value, would use sets in python 2.4
|
|
bone_mirrored[editbone2.name] = None
|
|
|
|
# If both options are enabled, then we have changed 2 bones
|
|
tot_editbones_modified+= PREF_MODE_L2R + PREF_MODE_R2L
|
|
|
|
arm_data.update() # get out of armature editmode
|
|
return tot_editbones, tot_editbones_modified
|
|
|
|
|
|
def main():
|
|
'''
|
|
User interface function that gets the options and calls armature_symetry()
|
|
'''
|
|
|
|
scn= bpy.data.scenes.active
|
|
arm_ob= scn.objects.active
|
|
|
|
if not arm_ob or arm_ob.type!='Armature':
|
|
Blender.Draw.PupMenu('No Armature object selected.')
|
|
return
|
|
|
|
# Cant be in editmode for armature.makeEditable()
|
|
is_editmode= Blender.Window.EditMode()
|
|
if is_editmode: Blender.Window.EditMode(0)
|
|
Draw= Blender.Draw
|
|
|
|
# Defaults for the user input
|
|
PREF_XMID_SNAP= Draw.Create(1)
|
|
PREF_MAX_DIST= Draw.Create(0.4)
|
|
PREF_XZERO_THRESH= Draw.Create(0.02)
|
|
|
|
PREF_MODE_L2R= Draw.Create(1)
|
|
PREF_MODE_R2L= Draw.Create(0)
|
|
PREF_SEL_ONLY= Draw.Create(1)
|
|
|
|
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, 'Tolerance 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.'),\
|
|
]
|
|
|
|
# Popup, exit if the user doesn't click OK
|
|
if not Draw.PupBlock("X Mirror mesh tool", pup_block):
|
|
return
|
|
|
|
# Replace the variables with their button values.
|
|
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
|
|
|
|
|
|
tot_editbones, tot_editbones_modified = armature_symetry(\
|
|
arm_ob,\
|
|
PREF_MAX_DIST,\
|
|
PREF_XMID_SNAP,\
|
|
PREF_XZERO_THRESH,\
|
|
PREF_MODE_L2R,\
|
|
PREF_MODE_R2L,\
|
|
PREF_SEL_ONLY)
|
|
|
|
if is_editmode: Blender.Window.EditMode(1)
|
|
|
|
# Redraw all views before popup
|
|
Blender.Window.RedrawAll()
|
|
|
|
# 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)
|
|
|
|
# Check for __main__ so this function can be imported by other scripts without running the script.
|
|
if __name__=='__main__':
|
|
main()
|