made mesh's getFromObject pick the materials from object or obdata as set by the...
[blender.git] / release / scripts / bpymodules / BPyMesh.py
1 import Blender
2
3 def meshWeight2Dict(me):
4         ''' Takes a mesh and return its group names and a list of dicts, one dict per vertex.
5         using the group as a key and a float value for the weight.
6         These 2 lists can be modified and then used with dict2MeshWeight to apply the changes.
7         '''
8         
9         vWeightDict= [dict() for i in xrange(len(me.verts))] # Sync with vertlist.
10         
11         # Clear the vert group.
12         groupNames= me.getVertGroupNames()
13         
14         for group in groupNames:
15                 for index, weight in me.getVertsFromGroup(group, 1): # (i,w)  tuples.
16                         vWeightDict[index][group]= weight
17         
18         # removed this because me may be copying teh vertex groups.
19         #for group in groupNames:
20         #       me.removeVertGroup(group)
21         
22         return groupNames, vWeightDict
23
24
25 def dict2MeshWeight(me, groupNames, vWeightDict):
26         ''' Takes a list of groups and a list of vertex Weight dicts as created by meshWeight2Dict
27         and applys it to the mesh.'''
28         
29         if len(vWeightDict) != len(me.verts):
30                 raise 'Error, Lists Differ in size, do not modify your mesh.verts before updating the weights'
31         
32         # Clear the vert group.
33         currentGroupNames= me.getVertGroupNames()
34         for group in currentGroupNames:
35                 if group not in groupNames:
36                         me.removeVertGroup(group) # messes up the active group.
37                 else:
38                         me.removeVertsFromGroup(group)
39         
40         # Add clean unused vert groupNames back
41         currentGroupNames= me.getVertGroupNames()
42         for group in groupNames:
43                 if group not in currentGroupNames:
44                         me.addVertGroup(group)
45         
46         add_ = Blender.Mesh.AssignModes.ADD
47         
48         vertList= [None]
49         for i, v in enumerate(me.verts):
50                 vertList[0]= i
51                 for group, weight in vWeightDict[i].iteritems():
52                         try:
53                                 me.assignVertsToGroup(group, vertList, min(1, max(0, weight)), add_)
54                         except:
55                                 pass # vert group is not used anymore.
56         
57         me.update()
58
59 def dictWeightMerge(dict_weights):
60         '''
61         Takes dict weight list and merges into 1 weight dict item and returns it
62         '''
63         
64         if not dict_weights:
65                 return {}
66         
67         keys= []
68         for weight in dict_weights:
69                 keys.extend([ (k, 0.0) for k in weight.iterkeys() ])
70         
71         new_wdict = dict(keys)
72         
73         len_dict_weights= len(dict_weights)
74         
75         for weight in dict_weights:
76                 for group, value in weight.iteritems():
77                         new_wdict[group] += value/len_dict_weights
78         
79         return new_wdict
80
81
82 FLIPNAMES=[\
83 ('Left','Right'),\
84 ('_L','_R'),\
85 ('-L','-R'),\
86 ('.L','.R'),\
87 ]
88
89 def dictWeightFlipGroups(dict_weight, groupNames, createNewGroups):
90         '''
91         Returns a weight with flip names
92         dict_weight - 1 vert weight.
93         groupNames - because we may need to add new group names.
94         dict_weight - Weather to make new groups where needed.
95         '''
96         
97         def flipName(name):
98                 for n1,n2 in FLIPNAMES:
99                         for nA, nB in ( (n1,n2), (n1.lower(),n2.lower()), (n1.upper(),n2.upper()) ):
100                                 if createNewGroups:
101                                         newName= name.replace(nA,nB)
102                                         if newName!=name:
103                                                 if newName not in groupNames:
104                                                         groupNames.append(newName)
105                                                 return newName
106                                         
107                                         newName= name.replace(nB,nA)
108                                         if newName!=name:
109                                                 if newName not in groupNames:
110                                                         groupNames.append(newName)
111                                                 return newName
112                                 
113                                 else:
114                                         newName= name.replace(nA,nB)
115                                         if newName!=name and newName in groupNames:
116                                                 return newName
117                                         
118                                         newName= name.replace(nB,nA)
119                                         if newName!=name and newName in groupNames:
120                                                 return newName
121                 
122                 return name
123                 
124         if not dict_weight:
125                 return dict_weight, groupNames
126         
127         
128         new_wdict = {}
129         for group, weight in dict_weight.iteritems():
130                 flipname= flipName(group)
131                 new_wdict[flipname]= weight
132         
133         return new_wdict, groupNames
134         
135
136 def getMeshFromObject(ob, container_mesh=None, apply_modifiers=True, vgroups=True, scn=None):
137         '''
138         ob - the object that you want to get the mesh from
139         container_mesh - a Blender.Mesh type mesh that is reused to avoid a new datablock per call to getMeshFromObject
140         apply_modifiers - if enabled, subsurf bones etc. will be applied to the returned mesh. disable to get a copy of the mesh.
141         vgroup - For mesh objects only, apply the vgroup to the the copied mesh. (slower)
142         scn - Scene type. avoids getting the current scene each time getMeshFromObject is called.
143         
144         Returns Mesh or None
145         '''
146         
147         if not scn:
148                 scn= Blender.Scene.GetCurrent()
149         if not container_mesh:
150                 mesh = Blender.Mesh.New()       
151         else:
152                 mesh= container_mesh
153                 mesh.verts= None
154         
155         
156         type = ob.getType()
157         dataname = ob.getData(1)
158         tempob= None
159         if apply_modifiers or type != 'Mesh':
160                 try:
161                         mesh.getFromObject(ob.name)
162                 except:
163                         return None
164         
165         else:
166                 '''
167                 Dont apply modifiers, copy the mesh. 
168                 So we can transform the data. its easiest just to get a copy of the mesh. 
169                 '''
170                 tempob= Blender.Object.New('Mesh')
171                 tempob.shareFrom(ob)
172                 scn.link(tempob)
173                 mesh.getFromObject(tempob.name)
174                 scn.unlink(tempob)
175         
176         if type == 'Mesh':
177                 if vgroups:
178                         if tempob==None:
179                                 tempob= Blender.Object.New('Mesh')
180                         tempob.link(mesh)
181                         try:
182                                 # Copy the influences if possible.
183                                 groupNames, vWeightDict= meshWeight2Dict(tempMe)
184                                 dict2MeshWeight(mesh, groupNames, vWeightDict)
185                         except:
186                                 # if the modifier changes the vert count then it messes it up for us.
187                                 pass
188         
189         return mesh
190
191 type_tuple= type( (0,) )
192 type_list= type( [] )
193 def ngon(from_data, indices):
194         '''
195         takes a polyline of indices (fgon)
196         and returns a list of face indicie lists.
197         Designed to be used for importers that need indices for an fgon to create from existing verts.
198         
199         from_data is either a mesh, or a list/tuple of vectors.
200         '''
201         Mesh= Blender.Mesh
202         Window= Blender.Window
203         Scene= Blender.Scene
204         Object= Blender.Object
205         
206         if len(indices) < 4:
207                 return [indices]
208         temp_mesh_name= '~NGON_TEMP~'
209         is_editmode= Window.EditMode()
210         if is_editmode:
211                 Window.EditMode(0)
212         try:
213                 temp_mesh = Mesh.Get(temp_mesh_name)
214                 if temp_mesh.users!=0:
215                         temp_mesh = Mesh.New(temp_mesh_name)
216         except:
217                 temp_mesh = Mesh.New(temp_mesh_name)
218                 
219         if type(from_data) in (type_tuple, type_list):
220                 # From a list/tuple of vectors
221                 temp_mesh.verts.extend( [from_data[i] for i in indices] )
222                 temp_mesh.edges.extend( [(temp_mesh.verts[i], temp_mesh.verts[i-1]) for i in xrange(len(temp_mesh.verts))] )
223         else:
224                 # From a mesh
225                 temp_mesh.verts.extend( [from_data.verts[i].co for i in indices] )
226                 temp_mesh.edges.extend( [(temp_mesh.verts[i], temp_mesh.verts[i-1]) for i in xrange(len(temp_mesh.verts))] )
227         
228         
229         oldmode = Mesh.Mode()
230         Mesh.Mode(Mesh.SelectModes['VERTEX'])
231         for v in temp_mesh.verts:
232                 v.sel= 1
233         
234         # Must link to scene
235         scn= Scene.GetCurrent()
236         temp_ob= Object.New('Mesh')
237         temp_ob.link(temp_mesh)
238         scn.link(temp_ob)
239         temp_mesh.fill()
240         scn.unlink(temp_ob)
241         Mesh.Mode(oldmode)
242         
243         new_indices= [ [v.index for v in f.v]  for f in temp_mesh.faces ]
244         
245         if not new_indices: # JUST DO A FAN, Cant Scanfill
246                 print 'Warning Cannot scanfill!- Fallback on a triangle fan.'
247                 new_indices = [ [indices[0], indices[i-1], indices[i]] for i in xrange(2, len(indices)) ]
248         else:
249                 # Use real scanfill.
250                 # See if its flipped the wrong way.
251                 flip= None
252                 for fi in new_indices:
253                         if flip != None:
254                                 break
255                         for i, vi in enumerate(fi):
256                                 if vi==0 and fi[i-1]==1:
257                                         flip= False
258                                         break
259                                 elif vi==1 and fi[i-1]==0:
260                                         flip= True
261                                         break
262                 
263                 if not flip:
264                         for fi in new_indices:
265                                 fi.reverse()
266         
267         if is_editmode:
268                 Window.EditMode(1)
269                 
270         # Save some memory and forget about the verts.
271         # since we cant unlink the mesh.
272         temp_mesh.verts= None 
273         
274         return new_indices
275         
276
277
278 # EG
279 '''
280 scn= Scene.GetCurrent()
281 me = scn.getActiveObject().getData(mesh=1)
282 ind= [v.index for v in me.verts if v.sel] # Get indices
283
284 indices = ngon(me, ind) # fill the ngon.
285
286 # Extand the faces to show what the scanfill looked like.
287 print len(indices)
288 me.faces.extend([[me.verts[ii] for ii in i] for i in indices])
289 '''
290
291
292 def meshPrettyNormals(me):
293         '''
294         takes a mesh and returns very high quality normals 1 normal per vertex.
295         The normals should be correct, indipendant of topology
296         '''
297         Ang= Blender.Mathutils.AngleBetweenVecs
298         Vector= Blender.Mathutils.Vector
299         SMALL_NUM=0.000001
300         # Weight the edge normals by total angle difference
301         # EDGE METHOD
302         
303         vertNormals= [ Vector() for v in xrange(len(me.verts)) ]
304         edges={}
305         for f in me.faces:
306                 for i in xrange(len(f.v)):
307                         i1, i2= f.v[i].index, f.v[i-1].index
308                         if i1<i2:
309                                 i1,i2= i2,i1
310                                 
311                         try:
312                                 edges[i1, i2].append(f.no)
313                         except:
314                                 edges[i1, i2]= [f.no]
315                                 
316         # Weight the edge normals by total angle difference
317         for fnos in edges.itervalues():
318                 
319                 len_fnos= len(fnos)
320                 if len_fnos>1:
321                         totAngDiff=0
322                         for j in reversed(xrange(len_fnos)):
323                                 for k in reversed(xrange(j)):
324                                         #print j,k
325                                         try:
326                                                 totAngDiff+= (Ang(fnos[j], fnos[k])) # /180 isnt needed, just to keeop the vert small.
327                                         except:
328                                                 pass # Zero length face
329                         
330                         # print totAngDiff
331                         if totAngDiff > SMALL_NUM:
332                                 
333                                 '''
334                                 average_no= Vector()
335                                 for no in fnos:
336                                         average_no+=no
337                                 '''
338                                 average_no= reduce(lambda a,b: a+b, fnos, Vector())
339                                 fnos.append(average_no*totAngDiff) # average no * total angle diff
340                         else:
341                                 fnos[0]
342                 else: 
343                         fnos.append(fnos[0])
344         
345         for ed, v in edges.iteritems():
346                 vertNormals[ed[0]]+= v[-1]
347                 vertNormals[ed[1]]+= v[-1]
348         for v in vertNormals:
349                 v.normalize()
350         return vertNormals
351
352
353
354
355 def pointInsideMesh(ob, pt):
356         Intersect = Blender.Mathutils.Intersect # 2 less dict lookups.
357         Vector = Blender.Mathutils.Vector
358         
359         def ptInFaceXYBounds(f, pt):
360                         
361                 co= f.v[0].co
362                 xmax= xmin= co.x
363                 ymax= ymin= co.y
364                 
365                 co= f.v[1].co
366                 xmax= max(xmax, co.x)
367                 xmin= min(xmin, co.x)
368                 ymax= max(ymax, co.y)
369                 ymin= min(ymin, co.y)
370                 
371                 co= f.v[2].co
372                 xmax= max(xmax, co.x)
373                 xmin= min(xmin, co.x)
374                 ymax= max(ymax, co.y)
375                 ymin= min(ymin, co.y)
376                 
377                 if len(f.v)==4: 
378                         co= f.v[3].co
379                         xmax= max(xmax, co.x)
380                         xmin= min(xmin, co.x)
381                         ymax= max(ymax, co.y)
382                         ymin= min(ymin, co.y)
383                 
384                 # Now we have the bounds, see if the point is in it.
385                 if\
386                 pt.x < xmin or\
387                 pt.y < ymin or\
388                 pt.x > xmax or\
389                 pt.y > ymax:
390                         return False # point is outside face bounds
391                 else:
392                         return True # point inside.
393                 #return xmax, ymax, xmin, ymin
394         
395         def faceIntersect(f):
396                 isect = Intersect(f.v[0].co, f.v[1].co, f.v[2].co, ray, obSpacePt, 1) # Clipped.
397                 if not isect and len(f.v) == 4:
398                         isect = Intersect(f.v[0].co, f.v[2].co, f.v[3].co, ray, obSpacePt, 1) # Clipped.
399                                 
400                 if isect and isect.z > obSpacePt.z: # This is so the ray only counts if its above the point. 
401                         return True
402                 else:
403                         return False
404         
405         
406         obImvMat = Blender.Mathutils.Matrix(ob.matrixWorld)
407         obImvMat.invert()
408         pt.resize4D()
409         obSpacePt = pt* obImvMat
410         pt.resize3D()
411         obSpacePt.resize3D()
412         ray = Vector(0,0,-1)
413         me= ob.getData(mesh=1)
414         
415         # Here we find the number on intersecting faces, return true if an odd number (inside), false (outside) if its true.
416         return len([None for f in me.faces if ptInFaceXYBounds(f, obSpacePt) if faceIntersect(f)]) % 2
417
418
419 # Get face center
420 def faceCent(f):
421         cent= Blender.Mathutils.Vector()        
422         l= len(f.v)
423         for v in f.v:
424                 cent+=v.co
425         return cent*(1.0/l)
426         
427
428 # NMesh wrapper
429 Vector= Blender.Mathutils.Vector
430 class NMesh(object):
431         __slots__= 'verts', 'faces', 'edges', 'faceUV', 'materials', 'realmesh'
432         def __init__(self, mesh):
433                 '''
434                 This is an NMesh wrapper that
435                 mesh is an Mesh as returned by Blender.Mesh.New()
436                 This class wraps NMesh like access into Mesh
437                 
438                 Running NMesh.update() - with this wrapper,
439                 Will update the realmesh.
440                 '''
441                 self.verts= []
442                 self.faces= []
443                 self.edges= []
444                 self.faceUV= False
445                 self.materials= []
446                 self.realmesh= mesh
447         
448         def addFace(self, nmf):
449                 self.faces.append(nmf)
450         
451         def Face(self, v=[]):
452                 return NMFace(v)
453         def Vert(self, x,y,z):
454                 return NMVert(x,y,z)
455         
456         def hasFaceUV(self, flag):
457                 if flag:
458                         self.faceUV= True
459                 else:
460                         self.faceUV= False
461         
462         def addMaterial(self, mat):
463                 self.materials.append(mat)
464         
465         def update(self, recalc_normals=False): # recalc_normals is dummy
466                 mesh= self.realmesh
467                 mesh.verts= None # Clears the 
468                 
469                 # Add in any verts from faces we may have not added.
470                 for nmf in self.faces:
471                         for nmv in nmf.v:
472                                 if nmv.index==-1:
473                                         nmv.index= len(self.verts)
474                                         self.verts.append(nmv)
475                                         
476                 
477                 mesh.verts.extend([nmv.co for nmv in self.verts])
478                 for i, nmv in enumerate(self.verts):
479                         nmv.index= i
480                         mv= mesh.verts[i]
481                         mv.sel= nmv.sel
482                 
483                 good_faces= [nmf for nmf in self.faces if len(nmf.v) in (3,4)]
484                 #print len(good_faces), 'AAA'
485                 
486                 
487                 #mesh.faces.extend([nmf.v for nmf in self.faces])
488                 mesh.faces.extend([[mesh.verts[nmv.index] for nmv in nmf.v] for nmf in good_faces])
489                 if len(mesh.faces):
490                         if self.faceUV:
491                                 mesh.faceUV= 1
492                         
493                         #for i, nmf in enumerate(self.faces):
494                         for i, nmf in enumerate(good_faces):
495                                 mf= mesh.faces[i]
496                                 if self.faceUV:
497                                         if len(nmf.uv) == len(mf.v):
498                                                 mf.uv= [Vector(uv[0], uv[1]) for uv in nmf.uv]
499                                         if len(nmf.col) == len(mf.v):
500                                                 for c, i in enumerate(mf.col):
501                                                         c.r, c.g, c.b= nmf.col[i].r, nmf.col[i].g, nmf.col[i].b
502                                         if nmf.image:
503                                                 mf.image= nmf.image
504                 
505                 mesh.materials= self.materials[:16]
506
507 class NMVert(object):
508         __slots__= 'co', 'index', 'no', 'sel', 'uvco'
509         def __init__(self, x,y,z):
510                 self.co= Vector(x,y,z)
511                 self.index= None # set on appending.
512                 self.no= Vector(0,0,1) # dummy
513                 self.sel= 0
514                 self.uvco= None
515 class NMFace(object):
516         __slots__= 'col', 'flag', 'hide', 'image', 'mat', 'materialIndex', 'mode', 'normal',\
517         'sel', 'smooth', 'transp', 'uv', 'v'
518         
519         def __init__(self, v=[]):
520                 self.col= []
521                 self.flag= 0
522                 self.hide= 0
523                 self.image= None
524                 self.mat= 0 # materialIndex needs support too.
525                 self.mode= 0
526                 self.normal= Vector(0,0,1)
527                 self.uv= []
528                 self.sel= 0
529                 self.smooth= 0
530                 self.transp= 0
531                 self.uv= []
532                 self.v= [] # a list of nmverts.
533         
534 class NMCol(object):
535         __slots__ = 'r', 'g', 'b', 'a'
536         def __init__(self):
537                 self.r= 255
538                 self.g= 255
539                 self.b= 255
540                 self.a= 255