Welcome to mirror list, hosted at ThFree Co, Russian Federation.

armature_symmetry.py « scripts « release - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 5ee4e086fb0eb4a00256c468398e76e9530de12c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
#!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()