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