Integrated Freestyle to rendering pipeline
[blender.git] / release / scripts / mesh_boneweight_copy.py
1 #!BPY
2 """
3 Name: 'Bone Weight Copy'
4 Blender: 245
5 Group: 'Object'
6 Tooltip: 'Copy Bone Weights from 1 mesh, to all other selected meshes.'
7 """
8
9 __author__ = "Campbell Barton aka ideasman42"
10 __url__ = ["www.blender.org", "blenderartists.org", "www.python.org"]
11 __version__ = "0.1"
12 __bpydoc__ = """\
13
14 Bone Weight Copy
15
16 This script is used to copy bone weights from 1 mesh with weights (the source mesh) to many (the target meshes).
17 Weights are copied from 1 mesh to another based on how close they are together.
18
19 For normal operation, select 1 source mesh with vertex weights and any number of unweighted meshes that overlap the source mesh.
20 Then run this script using default options and check the new weigh.
21
22
23 A differnt way to use this script is to update the weights an an alredy weighted mesh.
24 this is done using the "Copy to Selected" option enabled and works a bit differently,
25 With the target mesh, select the verts you want to update.
26 since all meshes have weights we cant just use the weighted mesh as the source,
27 so the Active Object is used for the source mesh.
28 Run the script and the selected verts on all non active meshes will be updated.
29 """
30
31 # ***** BEGIN GPL LICENSE BLOCK *****
32 #
33 # Script copyright (C) Campbell J Barton
34 #
35 # This program is free software; you can redistribute it and/or
36 # modify it under the terms of the GNU General Public License
37 # as published by the Free Software Foundation; either version 2
38 # of the License, or (at your option) any later version.
39 #
40 # This program is distributed in the hope that it will be useful,
41 # but WITHOUT ANY WARRANTY; without even the implied warranty of
42 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
43 # GNU General Public License for more details.
44 #
45 # You should have received a copy of the GNU General Public License
46 # along with this program; if not, write to the Free Software Foundation,
47 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
48 #
49 # ***** END GPL LICENCE BLOCK *****
50 # --------------------------------------------------------------------------
51
52 import Blender
53 from Blender import Armature, Object, Mathutils, Window, Mesh
54 Vector= Mathutils.Vector
55 SMALL_NUM= 0.000001
56 def copy_bone_influences(_from, _to, PREF_SEL_ONLY, PREF_NO_XCROSS):
57         ob_from, me_from, world_verts_from, from_groups=  _from
58         ob_to, me_to, world_verts_to, dummy=  _to       
59         del dummy
60         
61         def getSnapIdx(seek_vec, vecs):
62                 '''
63                 Returns the closest vec to snap_points
64                 '''
65                 
66                 # First seek the closest Z axis vert idx/v
67                 seek_vec_x,seek_vec_y,seek_vec_z= seek_vec
68                 
69                 from_vec_idx= 0
70                 
71                 len_vecs= len(vecs)
72                 
73                 upidx= len_vecs-1
74                 loidx= 0
75                 
76                 while from_vec_idx < len_vecs and vecs[from_vec_idx][1].z < seek_vec_z:
77                         from_vec_idx+=1
78                 
79                 # Clamp if we overstepped.
80                 if from_vec_idx  >= len_vecs:
81                         from_vec_idx-=1
82                 
83                 close_dist= (vecs[from_vec_idx][1]-seek_vec).length
84                 close_idx= vecs[from_vec_idx][0]
85                 
86                 upidx= from_vec_idx+1
87                 loidx= from_vec_idx-1
88                 
89                 # Set uselo/useup. This means we can keep seeking up/down.
90                 if upidx >= len_vecs:   useup= False
91                 else:                                   useup= True
92                         
93                 if loidx < 0:                   uselo= False
94                 else:                                   uselo= True
95                 
96                 # Seek up/down to find the closest v to seek vec.
97                 while uselo or useup:
98                         if useup:
99                                 if upidx >= len_vecs:
100                                         useup= False
101                                 else:
102                                         i,v= vecs[upidx]
103                                         if (not PREF_NO_XCROSS) or ((v.x >= -SMALL_NUM and seek_vec_x >= -SMALL_NUM) or (v.x <= SMALL_NUM and seek_vec_x <= SMALL_NUM)): # enfoce  xcrossing
104                                                 if v.z-seek_vec_z > close_dist:
105                                                         # the verticle distance is greater then the best distance sofar. we can stop looking up.
106                                                         useup= False
107                                                 elif abs(seek_vec_y-v.y) < close_dist and abs(seek_vec_x-v.x) < close_dist:
108                                                         # This is in the limit measure it.
109                                                         l= (seek_vec-v).length
110                                                         if l<close_dist:
111                                                                 close_dist= l
112                                                                 close_idx= i
113                                         upidx+=1
114                         
115                         if uselo:
116                                 
117                                 if loidx == 0:
118                                         uselo= False
119                                 else:
120                                         i,v= vecs[loidx]
121                                         if (not PREF_NO_XCROSS) or ((v.x >= -SMALL_NUM and seek_vec_x >= -SMALL_NUM) or (v.x <= SMALL_NUM and seek_vec_x  <= SMALL_NUM)): # enfoce  xcrossing
122                                                 if seek_vec_z-v.z > close_dist:
123                                                         # the verticle distance is greater then the best distance sofar. we can stop looking up.
124                                                         uselo= False
125                                                 elif abs(seek_vec_y-v.y) < close_dist and abs(seek_vec_x-v.x) < close_dist:
126                                                         # This is in the limit measure it.
127                                                         l= (seek_vec-v).length
128                                                         if l<close_dist:
129                                                                 close_dist= l
130                                                                 close_idx= i
131                                         loidx-=1
132                                 
133                 return close_idx
134         
135         
136         to_groups= me_to.getVertGroupNames() # if not PREF_SEL_ONLY will always be []
137         from_groups= me_from.getVertGroupNames()
138         
139         if PREF_SEL_ONLY: # remove selected verts from all groups.
140                 vsel= [v.index for v in me_to.verts if v.sel]
141                 for group in to_groups:
142                         me_to.removeVertsFromGroup(group, vsel)  
143         else: # Add all groups.
144                 for group in from_groups:
145                         me_to.addVertGroup(group)
146         
147         add_ = Mesh.AssignModes.ADD
148         
149         for i, co in enumerate(world_verts_to):
150                 if (not PREF_SEL_ONLY) or (PREF_SEL_ONLY and me_to.verts[i].sel):
151                         
152                         Window.DrawProgressBar(0.99 * (i/float(len(world_verts_to))), 'Copy "%s" -> "%s" ' % (ob_from.name, ob_to.name))
153                         
154                         from_idx= getSnapIdx(co, world_verts_from)
155                         from_infs= me_from.getVertexInfluences(from_idx)
156                         
157                         for group, weight in from_infs:
158                                 
159                                 # Add where needed.
160                                 if PREF_SEL_ONLY and group not in to_groups:
161                                         me_to.addVertGroup(group)
162                                         to_groups.append(group)
163                                         
164                                 me_to.assignVertsToGroup(group, [i], weight, add_)
165         
166         me_to.update()
167         
168 # ZSORT return (i/co) tuples, used for fast seeking of the snapvert.
169 def worldspace_verts_idx(me, ob):
170         mat= ob.matrixWorld
171         verts_zsort= [ (i, v.co*mat) for i, v in enumerate(me.verts) ]
172         
173         # Sorts along the Z Axis so we can optimize the getsnap.
174         try:    verts_zsort.sort(key = lambda a: a[1].z)
175         except: verts_zsort.sort(lambda a,b: cmp(a[1].z, b[1].z,))
176         
177         return verts_zsort
178
179
180 def worldspace_verts(me, ob):
181         mat= ob.matrixWorld
182         return [ v.co*mat for v in me.verts ]
183         
184 def subdivMesh(me, subdivs):
185         oldmode = Mesh.Mode()
186         Mesh.Mode(Mesh.SelectModes['FACE'])
187         me.sel= 1
188         for i in xrange(subdivs):
189                 me.subdivide(0)
190         Mesh.Mode(oldmode)
191
192
193 def main():
194         print '\nStarting BoneWeight Copy...'
195         scn= Blender.Scene.GetCurrent()
196         contextSel= Object.GetSelected()
197         if not contextSel:
198                 Blender.Draw.PupMenu('Error%t|2 or more mesh objects need to be selected.|aborting.')
199                 return
200         
201         PREF_QUALITY= Blender.Draw.Create(0)
202         PREF_NO_XCROSS= Blender.Draw.Create(0)
203         PREF_SEL_ONLY= Blender.Draw.Create(0)
204         
205         pup_block = [\
206         ('Quality:', PREF_QUALITY, 0, 4, 'Generate interpolated verts for a higher quality result.'),\
207         ('No X Crossing', PREF_NO_XCROSS, 'Do not snap across the zero X axis'),\
208         '',\
209         '"Update Selected" copies',\
210         'active object weights to',\
211         'selected verts on the other',\
212         'selected mesh objects.',\
213         ('Update Selected', PREF_SEL_ONLY, 'Only copy new weights to selected verts on the target mesh. (use active object as source)'),\
214         ]
215         
216         
217         if not Blender.Draw.PupBlock("Copy Weights for %i Meshs" % len(contextSel), pup_block):
218                 return
219         
220         PREF_SEL_ONLY= PREF_SEL_ONLY.val
221         PREF_NO_XCROSS= PREF_NO_XCROSS.val
222         quality=  PREF_QUALITY.val
223         
224         act_ob= scn.objects.active
225         if PREF_SEL_ONLY and act_ob==None:
226                 Blender.Draw.PupMenu('Error%t|When dealing with 2 or more meshes with vgroups|There must be an active object|to be used as a source|aborting.')
227                 return
228
229         sel=[]
230         from_data= None
231         
232         for ob in contextSel:
233                 if ob.type=='Mesh':
234                         me= ob.getData(mesh=1)
235                         groups= me.getVertGroupNames()
236                         
237                         # If this is the only mesh with a group OR if its one of many, but its active.
238                         if groups and ((ob==act_ob and PREF_SEL_ONLY) or (not PREF_SEL_ONLY)):
239                                 if from_data:
240                                         Blender.Draw.PupMenu('More then 1 mesh has vertex weights, only select 1 mesh with weights. aborting.')
241                                         return
242                                 else:
243                                         # This uses worldspace_verts_idx which gets (idx,co) pairs, then zsorts.
244                                         if quality:
245                                                 for _ob in contextSel:
246                                                         _ob.sel=0
247                                                 ob.sel=1
248                                                 Object.Duplicate(mesh=1)
249                                                 ob= scn.objects.active
250                                                 me= ob.getData(mesh=1)
251                                                 # groups will be the same
252                                                 print '\tGenerating higher %ix quality weights.' % quality
253                                                 subdivMesh(me, quality)
254                                                 scn.unlink(ob)
255                                         from_data= (ob, me, worldspace_verts_idx(me, ob), groups)
256                                         
257                         else:
258                                 data= (ob, me, worldspace_verts(me, ob), groups)
259                                 sel.append(data)
260         
261         if not from_data:
262                 Blender.Draw.PupMenu('Error%t|No mesh with vertex groups found.')
263                 return
264         
265         if not sel:
266                 Blender.Draw.PupMenu('Error%t|Select 2 or more mesh objects, aborting.')
267                 if quality:     from_data[1].verts= None
268                 return
269         
270         t= Blender.sys.time()
271         Window.WaitCursor(1)
272         
273         # Now do the copy.
274         print '\tCopying from "%s" to %i other mesh(es).' % (from_data[0].name, len(sel))
275         for data in sel:
276                 copy_bone_influences(from_data, data, PREF_SEL_ONLY, PREF_NO_XCROSS)
277         
278         # We cant unlink the mesh, but at least remove its data.
279         if quality:
280                 from_data[1].verts= None
281         
282         print 'Copy Complete in %.6f sec' % (Blender.sys.time()-t)
283         Window.DrawProgressBar(1.0, '')
284         Window.WaitCursor(0)
285
286 if __name__ == '__main__':
287         main()