Modified mesh to allow writing to normals.
[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 def meshCalcNormals(me):
292         '''
293         takes a mesh and returns very high quality normals 1 normal per vertex.
294         The normals should be correct, indipendant of topology
295         '''
296         Ang= Blender.Mathutils.AngleBetweenVecs
297         Vector= Blender.Mathutils.Vector
298         SMALL_NUM=0.000001
299         # Weight the edge normals by total angle difference
300         # EDGE METHOD
301         
302         vertNormals= [ Vector() for v in xrange(len(me.verts)) ]
303         edges={}
304         for f in me.faces:
305                 for i in xrange(len(f.v)):
306                         i1, i2= f.v[i].index, f.v[i-1].index
307                         if i1<i2:
308                                 i1,i2= i2,i1
309                                 
310                         try:
311                                 edges[i1, i2].append(f.no)
312                         except:
313                                 edges[i1, i2]= [f.no]
314                                 
315         # Weight the edge normals by total angle difference
316         for fnos in edges.itervalues():
317                 
318                 len_fnos= len(fnos)
319                 if len_fnos>1:
320                         totAngDiff=0
321                         for j in reversed(xrange(len_fnos)):
322                                 for k in reversed(xrange(j)):
323                                         #print j,k
324                                         try:
325                                                 totAngDiff+= (Ang(fnos[j], fnos[k])) # /180 isnt needed, just to keeop the vert small.
326                                         except:
327                                                 pass # Zero length face
328                         
329                         # print totAngDiff
330                         if totAngDiff > SMALL_NUM:
331                                 '''
332                                 average_no= Vector()
333                                 for no in fnos:
334                                         average_no+=no
335                                 '''
336                                 average_no= reduce(lambda a,b: a+b, fnos, Vector())
337                                 fnos.append(average_no*totAngDiff) # average no * total angle diff
338                         #else:
339                         #       fnos[0]
340                 else:
341                         fnos.append(fnos[0])
342         
343         for ed, v in edges.iteritems():
344                 vertNormals[ed[0]]+= v[-1]
345                 vertNormals[ed[1]]+= v[-1]
346         for i, v in enumerate(vertNormals):
347                 me.verts[i].no= v
348
349
350
351
352 def pointInsideMesh(ob, pt):
353         Intersect = Blender.Mathutils.Intersect # 2 less dict lookups.
354         Vector = Blender.Mathutils.Vector
355         
356         def ptInFaceXYBounds(f, pt):
357                         
358                 co= f.v[0].co
359                 xmax= xmin= co.x
360                 ymax= ymin= co.y
361                 
362                 co= f.v[1].co
363                 xmax= max(xmax, co.x)
364                 xmin= min(xmin, co.x)
365                 ymax= max(ymax, co.y)
366                 ymin= min(ymin, co.y)
367                 
368                 co= f.v[2].co
369                 xmax= max(xmax, co.x)
370                 xmin= min(xmin, co.x)
371                 ymax= max(ymax, co.y)
372                 ymin= min(ymin, co.y)
373                 
374                 if len(f.v)==4: 
375                         co= f.v[3].co
376                         xmax= max(xmax, co.x)
377                         xmin= min(xmin, co.x)
378                         ymax= max(ymax, co.y)
379                         ymin= min(ymin, co.y)
380                 
381                 # Now we have the bounds, see if the point is in it.
382                 if\
383                 pt.x < xmin or\
384                 pt.y < ymin or\
385                 pt.x > xmax or\
386                 pt.y > ymax:
387                         return False # point is outside face bounds
388                 else:
389                         return True # point inside.
390                 #return xmax, ymax, xmin, ymin
391         
392         def faceIntersect(f):
393                 isect = Intersect(f.v[0].co, f.v[1].co, f.v[2].co, ray, obSpacePt, 1) # Clipped.
394                 if not isect and len(f.v) == 4:
395                         isect = Intersect(f.v[0].co, f.v[2].co, f.v[3].co, ray, obSpacePt, 1) # Clipped.
396                                 
397                 if isect and isect.z > obSpacePt.z: # This is so the ray only counts if its above the point. 
398                         return True
399                 else:
400                         return False
401         
402         
403         obImvMat = Blender.Mathutils.Matrix(ob.matrixWorld)
404         obImvMat.invert()
405         pt.resize4D()
406         obSpacePt = pt* obImvMat
407         pt.resize3D()
408         obSpacePt.resize3D()
409         ray = Vector(0,0,-1)
410         me= ob.getData(mesh=1)
411         
412         # Here we find the number on intersecting faces, return true if an odd number (inside), false (outside) if its true.
413         return len([None for f in me.faces if ptInFaceXYBounds(f, obSpacePt) if faceIntersect(f)]) % 2
414
415
416 # Get face center
417 def faceCent(f):
418         cent= Blender.Mathutils.Vector()        
419         l= len(f.v)
420         for v in f.v:
421                 cent+=v.co
422         return cent*(1.0/l)
423         
424
425 # NMesh wrapper
426 Vector= Blender.Mathutils.Vector
427 class NMesh(object):
428         __slots__= 'verts', 'faces', 'edges', 'faceUV', 'materials', 'realmesh'
429         def __init__(self, mesh):
430                 '''
431                 This is an NMesh wrapper that
432                 mesh is an Mesh as returned by Blender.Mesh.New()
433                 This class wraps NMesh like access into Mesh
434                 
435                 Running NMesh.update() - with this wrapper,
436                 Will update the realmesh.
437                 '''
438                 self.verts= []
439                 self.faces= []
440                 self.edges= []
441                 self.faceUV= False
442                 self.materials= []
443                 self.realmesh= mesh
444         
445         def addFace(self, nmf):
446                 self.faces.append(nmf)
447         
448         def Face(self, v=[]):
449                 return NMFace(v)
450         def Vert(self, x,y,z):
451                 return NMVert(x,y,z)
452         
453         def hasFaceUV(self, flag):
454                 if flag:
455                         self.faceUV= True
456                 else:
457                         self.faceUV= False
458         
459         def addMaterial(self, mat):
460                 self.materials.append(mat)
461         
462         def update(self, recalc_normals=False): # recalc_normals is dummy
463                 mesh= self.realmesh
464                 mesh.verts= None # Clears the 
465                 
466                 # Add in any verts from faces we may have not added.
467                 for nmf in self.faces:
468                         for nmv in nmf.v:
469                                 if nmv.index==-1:
470                                         nmv.index= len(self.verts)
471                                         self.verts.append(nmv)
472                                         
473                 
474                 mesh.verts.extend([nmv.co for nmv in self.verts])
475                 for i, nmv in enumerate(self.verts):
476                         nmv.index= i
477                         mv= mesh.verts[i]
478                         mv.sel= nmv.sel
479                 
480                 good_faces= [nmf for nmf in self.faces if len(nmf.v) in (3,4)]
481                 #print len(good_faces), 'AAA'
482                 
483                 
484                 #mesh.faces.extend([nmf.v for nmf in self.faces])
485                 mesh.faces.extend([[mesh.verts[nmv.index] for nmv in nmf.v] for nmf in good_faces])
486                 if len(mesh.faces):
487                         if self.faceUV:
488                                 mesh.faceUV= 1
489                         
490                         #for i, nmf in enumerate(self.faces):
491                         for i, nmf in enumerate(good_faces):
492                                 mf= mesh.faces[i]
493                                 if self.faceUV:
494                                         if len(nmf.uv) == len(mf.v):
495                                                 mf.uv= [Vector(uv[0], uv[1]) for uv in nmf.uv]
496                                         if len(nmf.col) == len(mf.v):
497                                                 for c, i in enumerate(mf.col):
498                                                         c.r, c.g, c.b= nmf.col[i].r, nmf.col[i].g, nmf.col[i].b
499                                         if nmf.image:
500                                                 mf.image= nmf.image
501                 
502                 mesh.materials= self.materials[:16]
503
504 class NMVert(object):
505         __slots__= 'co', 'index', 'no', 'sel', 'uvco'
506         def __init__(self, x,y,z):
507                 self.co= Vector(x,y,z)
508                 self.index= None # set on appending.
509                 self.no= Vector(0,0,1) # dummy
510                 self.sel= 0
511                 self.uvco= None
512 class NMFace(object):
513         __slots__= 'col', 'flag', 'hide', 'image', 'mat', 'materialIndex', 'mode', 'normal',\
514         'sel', 'smooth', 'transp', 'uv', 'v'
515         
516         def __init__(self, v=[]):
517                 self.col= []
518                 self.flag= 0
519                 self.hide= 0
520                 self.image= None
521                 self.mat= 0 # materialIndex needs support too.
522                 self.mode= 0
523                 self.normal= Vector(0,0,1)
524                 self.uv= []
525                 self.sel= 0
526                 self.smooth= 0
527                 self.transp= 0
528                 self.uv= []
529                 self.v= [] # a list of nmverts.
530         
531 class NMCol(object):
532         __slots__ = 'r', 'g', 'b', 'a'
533         def __init__(self):
534                 self.r= 255
535                 self.g= 255
536                 self.b= 255
537                 self.a= 255