diff --git a/release/scripts/armature_symetry.py b/release/scripts/armature_symetry.py index be4f109ccce..f97b2633360 100644 --- a/release/scripts/armature_symetry.py +++ b/release/scripts/armature_symetry.py @@ -2,54 +2,24 @@ """ Name: 'Armature Symmetry' -Blender: 234 +Blender: 242 Group: 'Animation' -Tooltip: 'Make an armature symetrical' +Tooltip: 'Make an Armature symetrical' """ -__author__ = "Jonas Petersen" -__url__ = ("blender", "elysiun", "Script's homepage, http://www.mindfloaters.de/blender/", "thread at blender.org, http://www.blender.org/modules.php?op=modload&name=phpBB2&file=viewtopic&t=4858") -__version__ = "0.9 2004-11-10" +__author__ = "Campbell Barton" +__url__ = ("blender", "blenderartist") +__version__ = "1.0 2006-7-26" __doc__ = """\ This script creates perfectly symmetrical armatures. - -With default configuration it will:
- - Look for bones that have the reference suffix (".L") and -adjust/create the according opposite bone (suffix ".R");
- - Center align all bones that _don't_ have the suffix ".X". - -Please check the script's homepage and the thread at blender.org (last link button above) for more info. - -For this version users need to edit the script code to change default options. +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 """ -# -------------------------------------------------------------------------- -# "Armature Symmetry" by Jonas Petersen -# Version 0.9 - 10th November 2004 - first public release -# -------------------------------------------------------------------------- -# -# A script for creating perfectly symmetrical armatures. -# -# It is available in Object Mode via the menu item: -# -# Object -> Scripts -> Armature Symmetry -# -# With default configuration it will: -# -# - Look for bones that have the reference suffix (".L") and -# adjust/create the according opposite bone (suffix ".R"). -# -# - Center align all bones that _don't_ have the suffix ".X" -# -# Find the latest version at: http://www.mindfloaters.de/blender/ -# -# -------------------------------------------------------------------------- -# $Id$ -# -------------------------------------------------------------------------- # ***** BEGIN GPL LICENSE BLOCK ***** # -# Copyright (C) 2004: Jonas Petersen, jonas at mindfloaters dot de +# 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 @@ -66,132 +36,251 @@ For this version users need to edit the script code to change default options. # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # ***** END GPL LICENCE BLOCK ***** - -# -------------------------------------------------------------------------- -# CONFIGURATION -# -------------------------------------------------------------------------- - -# Note: Theses values will later be editable via a gui interface -# within Blender. - -# CENTER_SUFFIX is the suffix for bones that should (or shouldn't) get -# center aligned. The default is '.X'. -CENTER_SUFFIX = '.X' - -# CENTER_SUFFIX_MODE: -# -# 'include' only bones with the CENTER_SUFFIX appended -# get center aligned. -# -# 'exclude' (default) -# all bones except those with the CENTER_SUFFIX -# appended get center aligned. -# -# -# 'off' bones will not get center aligned at all. -# -CENTER_SUFFIX_MODE = 'exclude' - -# The suffix for the reference and opposite side of the -# armature. Bone positions of the opposite side will be overwritten by -# the mirrored values of the reference side. -# The default is REF_SUFFIX = '.L' and OPP_SUFFIX = '.R'. -REF_SUFFIX = '.L' -OPP_SUFFIX = '.R' - -# MIRROR_AXIS defines the axis in which bones are mirrored/aligned. -# Values: -# 0 for X (default) -# 1 for Y -# 2 for Z -MIRROR_AXIS = 0 - -# -------------------------------------------------------------------------- -# END OF CONFIGURATION # -------------------------------------------------------------------------- import Blender +from Blender import Scene +Vector= Blender.Mathutils.Vector -def splitName(bone): - name = bone.getName() - base = name[0:len(name)-ref_suffix_len] - rsuff = name[-ref_suffix_len:len(name)] - csuff = name[-center_suffix_len:len(name)] - return name, base, rsuff, csuff -ref_suffix_len = len(REF_SUFFIX); -center_suffix_len = len(CENTER_SUFFIX); -armature_selected = False +def VecXFlip(vec): + x,y,z= vec + return Vector(-x,y,z) -obj_list = Blender.Object.GetSelected() -for obj in obj_list: - if obj.getType() == "Armature": - armature_selected = True - arm = obj.getData() - bones = arm.getBones() - bonehash = {} +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 - for bone in bones: - bonehash[bone.getName()] = bone +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 + - for bone in bones: - name, base, rsuff, csuff = splitName(bone) +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) - # reference bone? - if (rsuff == REF_SUFFIX): - oppname = base + OPP_SUFFIX + +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) - # create opposite bone if necessary - if not bonehash.has_key(oppname): - bonehash[oppname]=Blender.Armature.Bone.New(oppname) - parent = bone.getParent() - if parent: - pname, pbase, prsuff, pcsuff = splitName(parent) - if prsuff == REF_SUFFIX: - poppname = pbase + OPP_SUFFIX - if bonehash.has_key(poppname): - bonehash[oppname].setParent(bonehash[poppname]) - else: - bonehash[oppname].setParent(parent) - arm.addBone(bonehash[oppname]) +if __name__=='__main__': + main() - # mirror bone coords - tail = bone.getTail() - tail[MIRROR_AXIS] *= -1; - bonehash[oppname].setTail(tail) - head = bone.getHead() - head[MIRROR_AXIS] *= -1; - bonehash[oppname].setHead(head) - - roll = -bone.getRoll() - bonehash[oppname].setRoll(roll) - - # Write access to ik flag not (yet?) supported in Blender (2.34) - #if bone.hasParent(): - # bonehash[oppname].setIK(not bone.getIK()) - - # center bone? - elif (rsuff != OPP_SUFFIX) and \ - (CENTER_SUFFIX_MODE != 'off') and \ - ((CENTER_SUFFIX_MODE == 'exclude' and csuff != CENTER_SUFFIX) or \ - (CENTER_SUFFIX_MODE == 'include' and csuff == CENTER_SUFFIX)): - - # center bone coords - - tail = bone.getTail() - tail[MIRROR_AXIS] = 0.0; - bone.setTail(tail) - - head = bone.getHead() - head[MIRROR_AXIS] = 0.0; - bone.setHead(head) - - # Setting set roll in python rotates all child bones. - # Not so if set via the Transform Properties in Blender. - # Bug? - bone.setRoll(0.0) - -if not armature_selected: - Blender.Draw.PupMenu("Armature Symmetry%t|Please select an Armature object!") + \ No newline at end of file