blender/release/scripts/armature_symetry.py
Campbell Barton f36fc4fe21 removed reload,
BPyRender accedentaly left the scene rather then removing it.
2006-07-26 11:39:32 +00:00

284 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()