Added utility functions to BPyMesh.py
[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                 print flipname, group
132                 new_wdict[flipname]= weight
133         
134         return new_wdict, groupNames
135         
136
137 def getMeshFromObject(ob, container_mesh=None, apply_modifiers=True, vgroups=True, scn=None):
138         '''
139         ob - the object that you want to get the mesh from
140         container_mesh - a Blender.Mesh type mesh that is reused to avoid a new datablock per call to getMeshFromObject
141         apply_modifiers - if enabled, subsurf bones etc. will be applied to the returned mesh. disable to get a copy of the mesh.
142         vgroup - For mesh objects only, apply the vgroup to the the copied mesh. (slower)
143         scn - Scene type. avoids getting the current scene each time getMeshFromObject is called.
144         
145         Returns Mesh or None
146         '''
147         
148         if not scn:
149                 scn= Blender.Scene.GetCurrent()
150         if not container_mesh:
151                 mesh = Blender.Mesh.New()       
152         else:
153                 mesh= container_mesh
154                 mesh.verts= None
155         
156         
157         type = ob.getType()
158         dataname = ob.getData(1)
159         tempob= None
160         if apply_modifiers or type != 'Mesh':
161                 try:
162                         mesh.getFromObject(ob.name)
163                 except:
164                         return None
165         
166         else:
167                 '''
168                 Dont apply modifiers, copy the mesh. 
169                 So we can transform the data. its easiest just to get a copy of the mesh. 
170                 '''
171                 tempob= Blender.Object.New('Mesh')
172                 tempob.shareFrom(ob)
173                 scn.link(tempob)
174                 mesh.getFromObject(tempob.name)
175                 scn.unlink(tempob)
176         
177         if type == 'Mesh':
178                 tempMe = ob.getData(mesh=1)
179                 mesh.materials = tempMe.materials
180                 mesh.degr = tempMe.degr
181                 try: mesh.mode = tempMe.mode # Mesh module needs fixing.
182                 except: pass
183                 if vgroups:
184                         if tempob==None:
185                                 tempob= Blender.Object.New('Mesh')
186                         tempob.link(mesh)
187                         try:
188                                 # Copy the influences if possible.
189                                 groupNames, vWeightDict= meshWeight2Dict(tempMe)
190                                 dict2MeshWeight(mesh, groupNames, vWeightDict)
191                         except:
192                                 # if the modifier changes the vert count then it messes it up for us.
193                                 pass
194                 
195         else:
196                 try:
197                         # Will only work for curves!!
198                         # Text- no material access in python interface.
199                         # Surf- no python interface
200                         # MBall- no material access in python interface.
201                         
202                         data = ob.getData()
203                         materials = data.getMaterials()
204                         mesh.materials = materials
205                         print 'assigning materials for non mesh'
206                 except:
207                         print 'Cant assign materials to', type
208         
209         return mesh
210
211 type_tuple= type( (0,) )
212 type_list= type( [] )
213 def ngon(from_data, indices):
214         '''
215         takes a polyline of indices (fgon)
216         and returns a list of face indicie lists.
217         Designed to be used for importers that need indices for an fgon to create from existing verts.
218         
219         from_data is either a mesh, or a list/tuple of vectors.
220         '''
221         Mesh= Blender.Mesh
222         Window= Blender.Window
223         Scene= Blender.Scene
224         Object= Blender.Object
225         
226         if len(indices) < 4:
227                 return [indices]
228         temp_mesh_name= '~NGON_TEMP~'
229         is_editmode= Window.EditMode()
230         if is_editmode:
231                 Window.EditMode(0)
232         try:
233                 temp_mesh = Mesh.Get(temp_mesh_name)
234                 if temp_mesh.users!=0:
235                         temp_mesh = Mesh.New(temp_mesh_name)
236         except:
237                 temp_mesh = Mesh.New(temp_mesh_name)
238                 
239         if type(from_data) in (type_tuple, type_list):
240                 # From a list/tuple of vectors
241                 temp_mesh.verts.extend( [from_data[i] for i in indices] )
242                 temp_mesh.edges.extend( [(temp_mesh.verts[i], temp_mesh.verts[i-1]) for i in xrange(len(temp_mesh.verts))] )
243         else:
244                 # From a mesh
245                 temp_mesh.verts.extend( [from_data.verts[i].co for i in indices] )
246                 temp_mesh.edges.extend( [(temp_mesh.verts[i], temp_mesh.verts[i-1]) for i in xrange(len(temp_mesh.verts))] )
247         
248         
249         oldmode = Mesh.Mode()
250         Mesh.Mode(Mesh.SelectModes['VERTEX'])
251         for v in temp_mesh.verts:
252                 v.sel= 1
253         
254         # Must link to scene
255         scn= Scene.GetCurrent()
256         temp_ob= Object.New('Mesh')
257         temp_ob.link(temp_mesh)
258         scn.link(temp_ob)
259         temp_mesh.fill()
260         scn.unlink(temp_ob)
261         Mesh.Mode(oldmode)
262         
263         new_indices= [ [v.index for v in f.v]  for f in temp_mesh.faces ]
264         
265         if not new_indices: # JUST DO A FAN, Cant Scanfill
266                 print 'Warning Cannot scanfill!- Fallback on a triangle fan.'
267                 new_indices = [ [indices[0], indices[i-1], indices[i]] for i in xrange(2, len(indices)) ]
268         else:
269                 # Use real scanfill.
270                 # See if its flipped the wrong way.
271                 flip= None
272                 for fi in new_indices:
273                         if flip != None:
274                                 break
275                         for i, vi in enumerate(fi):
276                                 if vi==0 and fi[i-1]==1:
277                                         flip= False
278                                         break
279                                 elif vi==1 and fi[i-1]==0:
280                                         flip= True
281                                         break
282                 
283                 if not flip:
284                         for fi in new_indices:
285                                 fi.reverse()
286         
287         if is_editmode:
288                 Window.EditMode(1)
289                 
290         # Save some memory and forget about the verts.
291         # since we cant unlink the mesh.
292         temp_mesh.verts= None 
293         
294         return new_indices
295         
296
297
298 # EG
299 '''
300 scn= Scene.GetCurrent()
301 me = scn.getActiveObject().getData(mesh=1)
302 ind= [v.index for v in me.verts if v.sel] # Get indices
303
304 indices = ngon(me, ind) # fill the ngon.
305
306 # Extand the faces to show what the scanfill looked like.
307 print len(indices)
308 me.faces.extend([[me.verts[ii] for ii in i] for i in indices])
309 '''
310
311
312
313
314
315
316
317
318 from Blender import *
319
320 def pointInsideMesh(ob, pt):
321         Intersect = Mathutils.Intersect # 2 less dict lookups.
322         Vector = Mathutils.Vector
323         
324         def ptInFaceXYBounds(f, pt):
325                         
326                 co= f.v[0].co
327                 xmax= xmin= co.x
328                 ymax= ymin= co.y
329                 
330                 co= f.v[1].co
331                 xmax= max(xmax, co.x)
332                 xmin= min(xmin, co.x)
333                 ymax= max(ymax, co.y)
334                 ymin= min(ymin, co.y)
335                 
336                 co= f.v[2].co
337                 xmax= max(xmax, co.x)
338                 xmin= min(xmin, co.x)
339                 ymax= max(ymax, co.y)
340                 ymin= min(ymin, co.y)
341                 
342                 if len(f.v)==4: 
343                         co= f.v[3].co
344                         xmax= max(xmax, co.x)
345                         xmin= min(xmin, co.x)
346                         ymax= max(ymax, co.y)
347                         ymin= min(ymin, co.y)
348                 
349                 # Now we have the bounds, see if the point is in it.
350                 if\
351                 pt.x < xmin or\
352                 pt.y < ymin or\
353                 pt.x > xmax or\
354                 pt.y > ymax:
355                         return False # point is outside face bounds
356                 else:
357                         return True # point inside.
358                 #return xmax, ymax, xmin, ymin
359         
360         def faceIntersect(f):
361                 isect = Intersect(f.v[0].co, f.v[1].co, f.v[2].co, ray, obSpacePt, 1) # Clipped.
362                 if not isect and len(f.v) == 4:
363                         isect = Intersect(f.v[0].co, f.v[2].co, f.v[3].co, ray, obSpacePt, 1) # Clipped.
364                                 
365                 if isect and isect.z > obSpacePt.z: # This is so the ray only counts if its above the point. 
366                         return True
367                 else:
368                         return False
369         
370         
371         obImvMat = Mathutils.Matrix(ob.matrixWorld)
372         obImvMat.invert()
373         pt.resize4D()
374         obSpacePt = pt* obImvMat
375         pt.resize3D()
376         obSpacePt.resize3D()
377         ray = Vector(0,0,-1)
378         me= ob.getData(mesh=1)
379         
380         # Here we find the number on intersecting faces, return true if an odd number (inside), false (outside) if its true.
381         return len([None for f in me.faces if ptInFaceXYBounds(f, obSpacePt) if faceIntersect(f)]) % 2
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398 # NMesh wrapper
399 Vector= Blender.Mathutils.Vector
400 class NMesh(object):
401         __slots__= 'verts', 'faces', 'edges', 'faceUV', 'materials', 'realmesh'
402         def __init__(self, mesh):
403                 '''
404                 This is an NMesh wrapper that
405                 mesh is an Mesh as returned by Blender.Mesh.New()
406                 This class wraps NMesh like access into Mesh
407                 
408                 Running NMesh.update() - with this wrapper,
409                 Will update the realmesh.
410                 '''
411                 self.verts= []
412                 self.faces= []
413                 self.edges= []
414                 self.faceUV= False
415                 self.materials= []
416                 self.realmesh= mesh
417         
418         def addFace(self, nmf):
419                 self.faces.append(nmf)
420         
421         def Face(self, v=[]):
422                 return NMFace(v)
423         def Vert(self, x,y,z):
424                 return NMVert(x,y,z)
425         
426         def hasFaceUV(self, flag):
427                 if flag:
428                         self.faceUV= True
429                 else:
430                         self.faceUV= False
431         
432         def addMaterial(self, mat):
433                 self.materials.append(mat)
434         
435         def update(self, recalc_normals=False): # recalc_normals is dummy
436                 mesh= self.realmesh
437                 mesh.verts= None # Clears the 
438                 
439                 # Add in any verts from faces we may have not added.
440                 for nmf in self.faces:
441                         for nmv in nmf.v:
442                                 if nmv.index==-1:
443                                         nmv.index= len(self.verts)
444                                         self.verts.append(nmv)
445                                         
446                 
447                 mesh.verts.extend([nmv.co for nmv in self.verts])
448                 for i, nmv in enumerate(self.verts):
449                         nmv.index= i
450                         mv= mesh.verts[i]
451                         mv.sel= nmv.sel
452                 
453                 good_faces= [nmf for nmf in self.faces if len(nmf.v) in (3,4)]
454                 #print len(good_faces), 'AAA'
455                 
456                 
457                 #mesh.faces.extend([nmf.v for nmf in self.faces])
458                 mesh.faces.extend([[mesh.verts[nmv.index] for nmv in nmf.v] for nmf in good_faces])
459                 if len(mesh.faces):
460                         if self.faceUV:
461                                 mesh.faceUV= 1
462                         
463                         #for i, nmf in enumerate(self.faces):
464                         for i, nmf in enumerate(good_faces):
465                                 mf= mesh.faces[i]
466                                 if self.faceUV:
467                                         if len(nmf.uv) == len(mf.v):
468                                                 mf.uv= [Vector(uv[0], uv[1]) for uv in nmf.uv]
469                                         if len(nmf.col) == len(mf.v):
470                                                 for c, i in enumerate(mf.col):
471                                                         c.r, c.g, c.b= nmf.col[i].r, nmf.col[i].g, nmf.col[i].b
472                                         if nmf.image:
473                                                 mf.image= nmf.image
474                 
475                 mesh.materials= self.materials[:16]
476
477 class NMVert(object):
478         __slots__= 'co', 'index', 'no', 'sel', 'uvco'
479         def __init__(self, x,y,z):
480                 self.co= Vector(x,y,z)
481                 self.index= None # set on appending.
482                 self.no= Vector(0,0,1) # dummy
483                 self.sel= 0
484                 self.uvco= None
485 class NMFace(object):
486         __slots__= 'col', 'flag', 'hide', 'image', 'mat', 'materialIndex', 'mode', 'normal',\
487         'sel', 'smooth', 'transp', 'uv', 'v'
488         
489         def __init__(self, v=[]):
490                 self.col= []
491                 self.flag= 0
492                 self.hide= 0
493                 self.image= None
494                 self.mat= 0 # materialIndex needs support too.
495                 self.mode= 0
496                 self.normal= Vector(0,0,1)
497                 self.uv= []
498                 self.sel= 0
499                 self.smooth= 0
500                 self.transp= 0
501                 self.uv= []
502                 self.v= [] # a list of nmverts.
503         
504 class NMCol(object):
505         __slots__ = 'r', 'g', 'b', 'a'
506         def __init__(self):
507                 self.r= 255
508                 self.g= 255
509                 self.b= 255
510                 self.a= 255