Integrated Freestyle to rendering pipeline
[blender.git] / release / scripts / export_obj.py
1 #!BPY
2
3 """
4 Name: 'Wavefront (.obj)...'
5 Blender: 249
6 Group: 'Export'
7 Tooltip: 'Save a Wavefront OBJ File'
8 """
9
10 __author__ = "Campbell Barton, Jiri Hnidek, Paolo Ciccone"
11 __url__ = ['http://wiki.blender.org/index.php/Scripts/Manual/Export/wavefront_obj', 'www.blender.org', 'blenderartists.org']
12 __version__ = "1.22"
13
14 __bpydoc__ = """\
15 This script is an exporter to OBJ file format.
16
17 Usage:
18
19 Select the objects you wish to export and run this script from "File->Export" menu.
20 Selecting the default options from the popup box will be good in most cases.
21 All objects that can be represented as a mesh (mesh, curve, metaball, surface, text3d)
22 will be exported as mesh data.
23 """
24
25
26 # ***** BEGIN GPL LICENSE BLOCK *****
27 #
28 # Script copyright (C) Campbell J Barton 2007-2009
29 # - V1.22- bspline import/export added (funded by PolyDimensions GmbH)
30 #
31 # This program is free software; you can redistribute it and/or
32 # modify it under the terms of the GNU General Public License
33 # as published by the Free Software Foundation; either version 2
34 # of the License, or (at your option) any later version.
35 #
36 # This program is distributed in the hope that it will be useful,
37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
39 # GNU General Public License for more details.
40 #
41 # You should have received a copy of the GNU General Public License
42 # along with this program; if not, write to the Free Software Foundation,
43 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
44 #
45 # ***** END GPL LICENCE BLOCK *****
46 # --------------------------------------------------------------------------
47
48
49 import Blender
50 from Blender import Mesh, Scene, Window, sys, Image, Draw
51 import BPyMesh
52 import BPyObject
53 import BPySys
54 import BPyMessages
55
56 # Returns a tuple - path,extension.
57 # 'hello.obj' >  ('hello', '.obj')
58 def splitExt(path):
59         dotidx = path.rfind('.')
60         if dotidx == -1:
61                 return path, ''
62         else:
63                 return path[:dotidx], path[dotidx:] 
64
65 def fixName(name):
66         if name == None:
67                 return 'None'
68         else:
69                 return name.replace(' ', '_')
70
71 # A Dict of Materials
72 # (material.name, image.name):matname_imagename # matname_imagename has gaps removed.
73 MTL_DICT = {} 
74
75 def write_mtl(filename):
76         
77         world = Blender.World.GetCurrent()
78         if world:
79                 worldAmb = world.getAmb()
80         else:
81                 worldAmb = (0,0,0) # Default value
82         
83         file = open(filename, "w")
84         file.write('# Blender3D MTL File: %s\n' % Blender.Get('filename').split('\\')[-1].split('/')[-1])
85         file.write('# Material Count: %i\n' % len(MTL_DICT))
86         # Write material/image combinations we have used.
87         for key, (mtl_mat_name, mat, img) in MTL_DICT.iteritems():
88                 
89                 # Get the Blender data for the material and the image.
90                 # Having an image named None will make a bug, dont do it :)
91                 
92                 file.write('newmtl %s\n' % mtl_mat_name) # Define a new material: matname_imgname
93                 
94                 if mat:
95                         file.write('Ns %.6f\n' % ((mat.getHardness()-1) * 1.9607843137254901) ) # Hardness, convert blenders 1-511 to MTL's 
96                         file.write('Ka %.6f %.6f %.6f\n' %  tuple([c*mat.amb for c in worldAmb])  ) # Ambient, uses mirror colour,
97                         file.write('Kd %.6f %.6f %.6f\n' % tuple([c*mat.ref for c in mat.rgbCol]) ) # Diffuse
98                         file.write('Ks %.6f %.6f %.6f\n' % tuple([c*mat.spec for c in mat.specCol]) ) # Specular
99                         file.write('Ni %.6f\n' % mat.IOR) # Refraction index
100                         file.write('d %.6f\n' % mat.alpha) # Alpha (obj uses 'd' for dissolve)
101                         
102                         # 0 to disable lighting, 1 for ambient & diffuse only (specular color set to black), 2 for full lighting.
103                         if mat.getMode() & Blender.Material.Modes['SHADELESS']:
104                                 file.write('illum 0\n') # ignore lighting
105                         elif mat.getSpec() == 0:
106                                 file.write('illum 1\n') # no specular.
107                         else:
108                                 file.write('illum 2\n') # light normaly 
109                 
110                 else:
111                         #write a dummy material here?
112                         file.write('Ns 0\n')
113                         file.write('Ka %.6f %.6f %.6f\n' %  tuple([c for c in worldAmb])  ) # Ambient, uses mirror colour,
114                         file.write('Kd 0.8 0.8 0.8\n')
115                         file.write('Ks 0.8 0.8 0.8\n')
116                         file.write('d 1\n') # No alpha
117                         file.write('illum 2\n') # light normaly
118                 
119                 # Write images!
120                 if img:  # We have an image on the face!
121                         file.write('map_Kd %s\n' % img.filename.split('\\')[-1].split('/')[-1]) # Diffuse mapping image                 
122                 
123                 elif mat: # No face image. if we havea material search for MTex image.
124                         for mtex in mat.getTextures():
125                                 if mtex and mtex.tex.type == Blender.Texture.Types.IMAGE:
126                                         try:
127                                                 filename = mtex.tex.image.filename.split('\\')[-1].split('/')[-1]
128                                                 file.write('map_Kd %s\n' % filename) # Diffuse mapping image
129                                                 break
130                                         except:
131                                                 # Texture has no image though its an image type, best ignore.
132                                                 pass
133                 
134                 file.write('\n\n')
135         
136         file.close()
137
138 def copy_file(source, dest):
139         file = open(source, 'rb')
140         data = file.read()
141         file.close()
142         
143         file = open(dest, 'wb')
144         file.write(data)
145         file.close()
146
147
148 def copy_images(dest_dir):
149         if dest_dir[-1] != sys.sep:
150                 dest_dir += sys.sep
151         
152         # Get unique image names
153         uniqueImages = {}
154         for matname, mat, image in MTL_DICT.itervalues(): # Only use image name
155                 # Get Texface images
156                 if image:
157                         uniqueImages[image] = image # Should use sets here. wait until Python 2.4 is default.
158                 
159                 # Get MTex images
160                 if mat:
161                         for mtex in mat.getTextures():
162                                 if mtex and mtex.tex.type == Blender.Texture.Types.IMAGE:
163                                         image_tex = mtex.tex.image
164                                         if image_tex:
165                                                 try:
166                                                         uniqueImages[image_tex] = image_tex
167                                                 except:
168                                                         pass
169         
170         # Now copy images
171         copyCount = 0
172         
173         for bImage in uniqueImages.itervalues():
174                 image_path = sys.expandpath(bImage.filename)
175                 if sys.exists(image_path):
176                         # Make a name for the target path.
177                         dest_image_path = dest_dir + image_path.split('\\')[-1].split('/')[-1]
178                         if not sys.exists(dest_image_path): # Image isnt alredy there
179                                 print '\tCopying "%s" > "%s"' % (image_path, dest_image_path)
180                                 copy_file(image_path, dest_image_path)
181                                 copyCount+=1
182         print '\tCopied %d images' % copyCount
183
184
185 def test_nurbs_compat(ob):
186         if ob.type != 'Curve':
187                 return False
188         
189         for nu in ob.data:
190                 if (not nu.knotsV) and nu.type != 1: # not a surface and not bezier
191                         return True
192         
193         return False
194
195 def write_nurb(file, ob, ob_mat):
196         tot_verts = 0
197         cu = ob.data
198         
199         # use negative indices
200         Vector = Blender.Mathutils.Vector
201         for nu in cu:
202                 
203                 if nu.type==0:          DEG_ORDER_U = 1
204                 else:                           DEG_ORDER_U = nu.orderU-1  # Tested to be correct
205                 
206                 if nu.type==1:
207                         print "\tWarning, bezier curve:", ob.name, "only poly and nurbs curves supported"
208                         continue
209                 
210                 if nu.knotsV:
211                         print "\tWarning, surface:", ob.name, "only poly and nurbs curves supported"
212                         continue
213                 
214                 if len(nu) <= DEG_ORDER_U:
215                         print "\tWarning, orderU is lower then vert count, skipping:", ob.name
216                         continue
217                 
218                 pt_num = 0
219                 do_closed = (nu.flagU & 1)
220                 do_endpoints = (do_closed==0) and (nu.flagU & 2)
221                 
222                 for pt in nu:
223                         pt = Vector(pt[0], pt[1], pt[2]) * ob_mat
224                         file.write('v %.6f %.6f %.6f\n' % (pt[0], pt[1], pt[2]))
225                         pt_num += 1
226                 tot_verts += pt_num
227                 
228                 file.write('g %s\n' % (fixName(ob.name))) # fixName(ob.getData(1)) could use the data name too
229                 file.write('cstype bspline\n') # not ideal, hard coded
230                 file.write('deg %d\n' % DEG_ORDER_U) # not used for curves but most files have it still
231                 
232                 curve_ls = [-(i+1) for i in xrange(pt_num)]
233                 
234                 # 'curv' keyword
235                 if do_closed:
236                         if DEG_ORDER_U == 1:
237                                 pt_num += 1
238                                 curve_ls.append(-1)
239                         else:
240                                 pt_num += DEG_ORDER_U
241                                 curve_ls = curve_ls + curve_ls[0:DEG_ORDER_U]
242                 
243                 file.write('curv 0.0 1.0 %s\n' % (' '.join( [str(i) for i in curve_ls] ))) # Blender has no U and V values for the curve
244                 
245                 # 'parm' keyword
246                 tot_parm = (DEG_ORDER_U + 1) + pt_num
247                 tot_parm_div = float(tot_parm-1)
248                 parm_ls = [(i/tot_parm_div) for i in xrange(tot_parm)]
249                 
250                 if do_endpoints: # end points, force param
251                         for i in xrange(DEG_ORDER_U+1):
252                                 parm_ls[i] = 0.0
253                                 parm_ls[-(1+i)] = 1.0
254                 
255                 file.write('parm u %s\n' % ' '.join( [str(i) for i in parm_ls] ))
256
257                 file.write('end\n')
258         
259         return tot_verts
260
261 def write(filename, objects,\
262 EXPORT_TRI=False,  EXPORT_EDGES=False,  EXPORT_NORMALS=False,  EXPORT_NORMALS_HQ=False,\
263 EXPORT_UV=True,  EXPORT_MTL=True,  EXPORT_COPY_IMAGES=False,\
264 EXPORT_APPLY_MODIFIERS=True, EXPORT_ROTX90=True, EXPORT_BLEN_OBS=True,\
265 EXPORT_GROUP_BY_OB=False,  EXPORT_GROUP_BY_MAT=False, EXPORT_KEEP_VERT_ORDER=False,\
266 EXPORT_POLYGROUPS=False, EXPORT_CURVE_AS_NURBS=True):
267         '''
268         Basic write function. The context and options must be alredy set
269         This can be accessed externaly
270         eg.
271         write( 'c:\\test\\foobar.obj', Blender.Object.GetSelected() ) # Using default options.
272         '''
273         
274         def veckey3d(v):
275                 return round(v.x, 6), round(v.y, 6), round(v.z, 6)
276                 
277         def veckey2d(v):
278                 return round(v.x, 6), round(v.y, 6)
279         
280         def findVertexGroupName(face, vWeightMap):
281                 """
282                 Searches the vertexDict to see what groups is assigned to a given face.
283                 We use a frequency system in order to sort out the name because a given vetex can
284                 belong to two or more groups at the same time. To find the right name for the face
285                 we list all the possible vertex group names with their frequency and then sort by
286                 frequency in descend order. The top element is the one shared by the highest number
287                 of vertices is the face's group 
288                 """
289                 weightDict = {}
290                 for vert in face:
291                         vWeights = vWeightMap[vert.index]
292                         for vGroupName, weight in vWeights:
293                                 weightDict[vGroupName] = weightDict.get(vGroupName, 0) + weight
294                 
295                 if weightDict:
296                         alist = [(weight,vGroupName) for vGroupName, weight in weightDict.iteritems()] # sort least to greatest amount of weight
297                         alist.sort()
298                         return(alist[-1][1]) # highest value last
299                 else:
300                         return '(null)'
301
302
303         print 'OBJ Export path: "%s"' % filename
304         temp_mesh_name = '~tmp-mesh'
305
306         time1 = sys.time()
307         scn = Scene.GetCurrent()
308
309         file = open(filename, "w")
310         
311         # Write Header
312         file.write('# Blender3D v%s OBJ File: %s\n' % (Blender.Get('version'), Blender.Get('filename').split('/')[-1].split('\\')[-1] ))
313         file.write('# www.blender3d.org\n')
314
315         # Tell the obj file what material file to use.
316         if EXPORT_MTL:
317                 mtlfilename = '%s.mtl' % '.'.join(filename.split('.')[:-1])
318                 file.write('mtllib %s\n' % ( mtlfilename.split('\\')[-1].split('/')[-1] ))
319         
320         # Get the container mesh. - used for applying modifiers and non mesh objects.
321         containerMesh = meshName = tempMesh = None
322         for meshName in Blender.NMesh.GetNames():
323                 if meshName.startswith(temp_mesh_name):
324                         tempMesh = Mesh.Get(meshName)
325                         if not tempMesh.users:
326                                 containerMesh = tempMesh
327         if not containerMesh:
328                 containerMesh = Mesh.New(temp_mesh_name)
329         
330         if EXPORT_ROTX90:
331                 mat_xrot90= Blender.Mathutils.RotationMatrix(-90, 4, 'x')
332                 
333         del meshName
334         del tempMesh
335         
336         # Initialize totals, these are updated each object
337         totverts = totuvco = totno = 1
338         
339         face_vert_index = 1
340         
341         globalNormals = {}
342         
343         # Get all meshes
344         for ob_main in objects:
345                 for ob, ob_mat in BPyObject.getDerivedObjects(ob_main):
346                         
347                         # Nurbs curve support
348                         if EXPORT_CURVE_AS_NURBS and test_nurbs_compat(ob):
349                                 if EXPORT_ROTX90:
350                                         ob_mat = ob_mat * mat_xrot90
351                                 
352                                 totverts += write_nurb(file, ob, ob_mat)
353                                 
354                                 continue
355                         # end nurbs
356                         
357                         # Will work for non meshes now! :)
358                         # getMeshFromObject(ob, container_mesh=None, apply_modifiers=True, vgroups=True, scn=None)
359                         me= BPyMesh.getMeshFromObject(ob, containerMesh, EXPORT_APPLY_MODIFIERS, EXPORT_POLYGROUPS, scn)
360                         if not me:
361                                 continue
362                         
363                         if EXPORT_UV:
364                                 faceuv= me.faceUV
365                         else:
366                                 faceuv = False
367                         
368                         # We have a valid mesh
369                         if EXPORT_TRI and me.faces:
370                                 # Add a dummy object to it.
371                                 has_quads = False
372                                 for f in me.faces:
373                                         if len(f) == 4:
374                                                 has_quads = True
375                                                 break
376                                 
377                                 if has_quads:
378                                         oldmode = Mesh.Mode()
379                                         Mesh.Mode(Mesh.SelectModes['FACE'])
380                                         
381                                         me.sel = True
382                                         tempob = scn.objects.new(me)
383                                         me.quadToTriangle(0) # more=0 shortest length
384                                         oldmode = Mesh.Mode(oldmode)
385                                         scn.objects.unlink(tempob)
386                                         
387                                         Mesh.Mode(oldmode)
388                         
389                         # Make our own list so it can be sorted to reduce context switching
390                         faces = [ f for f in me.faces ]
391                         
392                         if EXPORT_EDGES:
393                                 edges = me.edges
394                         else:
395                                 edges = []
396                         
397                         if not (len(faces)+len(edges)+len(me.verts)): # Make sure there is somthing to write
398                                 continue # dont bother with this mesh.
399                         
400                         if EXPORT_ROTX90:
401                                 me.transform(ob_mat*mat_xrot90)
402                         else:
403                                 me.transform(ob_mat)
404                         
405                         # High Quality Normals
406                         if EXPORT_NORMALS and faces:
407                                 if EXPORT_NORMALS_HQ:
408                                         BPyMesh.meshCalcNormals(me)
409                                 else:
410                                         # transforming normals is incorrect
411                                         # when the matrix is scaled,
412                                         # better to recalculate them
413                                         me.calcNormals()
414                         
415                         # # Crash Blender
416                         #materials = me.getMaterials(1) # 1 == will return None in the list.
417                         materials = me.materials
418                         
419                         materialNames = []
420                         materialItems = materials[:]
421                         if materials:
422                                 for mat in materials:
423                                         if mat: # !=None
424                                                 materialNames.append(mat.name)
425                                         else:
426                                                 materialNames.append(None)
427                                 # Cant use LC because some materials are None.
428                                 # materialNames = map(lambda mat: mat.name, materials) # Bug Blender, dosent account for null materials, still broken.  
429                         
430                         # Possible there null materials, will mess up indicies
431                         # but at least it will export, wait until Blender gets fixed.
432                         materialNames.extend((16-len(materialNames)) * [None])
433                         materialItems.extend((16-len(materialItems)) * [None])
434                         
435                         # Sort by Material, then images
436                         # so we dont over context switch in the obj file.
437                         if EXPORT_KEEP_VERT_ORDER:
438                                 pass
439                         elif faceuv:
440                                 try:    faces.sort(key = lambda a: (a.mat, a.image, a.smooth))
441                                 except: faces.sort(lambda a,b: cmp((a.mat, a.image, a.smooth), (b.mat, b.image, b.smooth)))
442                         elif len(materials) > 1:
443                                 try:    faces.sort(key = lambda a: (a.mat, a.smooth))
444                                 except: faces.sort(lambda a,b: cmp((a.mat, a.smooth), (b.mat, b.smooth)))
445                         else:
446                                 # no materials
447                                 try:    faces.sort(key = lambda a: a.smooth)
448                                 except: faces.sort(lambda a,b: cmp(a.smooth, b.smooth))
449                         
450                         # Set the default mat to no material and no image.
451                         contextMat = (0, 0) # Can never be this, so we will label a new material teh first chance we get.
452                         contextSmooth = None # Will either be true or false,  set bad to force initialization switch.
453                         
454                         if EXPORT_BLEN_OBS or EXPORT_GROUP_BY_OB:
455                                 name1 = ob.name
456                                 name2 = ob.getData(1)
457                                 if name1 == name2:
458                                         obnamestring = fixName(name1)
459                                 else:
460                                         obnamestring = '%s_%s' % (fixName(name1), fixName(name2))
461                                 
462                                 if EXPORT_BLEN_OBS:
463                                         file.write('o %s\n' % obnamestring) # Write Object name
464                                 else: # if EXPORT_GROUP_BY_OB:
465                                         file.write('g %s\n' % obnamestring)
466                         
467                         
468                         # Vert
469                         for v in me.verts:
470                                 file.write('v %.6f %.6f %.6f\n' % tuple(v.co))
471                         
472                         # UV
473                         if faceuv:
474                                 uv_face_mapping = [[0,0,0,0] for f in faces] # a bit of a waste for tri's :/
475                                 
476                                 uv_dict = {} # could use a set() here
477                                 for f_index, f in enumerate(faces):
478                                         
479                                         for uv_index, uv in enumerate(f.uv):
480                                                 uvkey = veckey2d(uv)
481                                                 try:
482                                                         uv_face_mapping[f_index][uv_index] = uv_dict[uvkey]
483                                                 except:
484                                                         uv_face_mapping[f_index][uv_index] = uv_dict[uvkey] = len(uv_dict)
485                                                         file.write('vt %.6f %.6f\n' % tuple(uv))
486                                 
487                                 uv_unique_count = len(uv_dict)
488                                 del uv, uvkey, uv_dict, f_index, uv_index
489                                 # Only need uv_unique_count and uv_face_mapping
490                         
491                         # NORMAL, Smooth/Non smoothed.
492                         if EXPORT_NORMALS:
493                                 for f in faces:
494                                         if f.smooth:
495                                                 for v in f:
496                                                         noKey = veckey3d(v.no)
497                                                         if not globalNormals.has_key( noKey ):
498                                                                 globalNormals[noKey] = totno
499                                                                 totno +=1
500                                                                 file.write('vn %.6f %.6f %.6f\n' % noKey)
501                                         else:
502                                                 # Hard, 1 normal from the face.
503                                                 noKey = veckey3d(f.no)
504                                                 if not globalNormals.has_key( noKey ):
505                                                         globalNormals[noKey] = totno
506                                                         totno +=1
507                                                         file.write('vn %.6f %.6f %.6f\n' % noKey)
508                         
509                         if not faceuv:
510                                 f_image = None
511                         
512                         if EXPORT_POLYGROUPS:
513                                 # Retrieve the list of vertex groups
514                                 vertGroupNames = me.getVertGroupNames()
515
516                                 currentVGroup = ''
517                                 # Create a dictionary keyed by face id and listing, for each vertex, the vertex groups it belongs to
518                                 vgroupsMap = [[] for _i in xrange(len(me.verts))]
519                                 for vertexGroupName in vertGroupNames:
520                                         for vIdx, vWeight in me.getVertsFromGroup(vertexGroupName, 1):
521                                                 vgroupsMap[vIdx].append((vertexGroupName, vWeight))
522
523                         for f_index, f in enumerate(faces):
524                                 f_v= f.v
525                                 f_smooth= f.smooth
526                                 f_mat = min(f.mat, len(materialNames)-1)
527                                 if faceuv:
528                                         f_image = f.image
529                                         f_uv= f.uv
530                                 
531                                 # MAKE KEY
532                                 if faceuv and f_image: # Object is always true.
533                                         key = materialNames[f_mat],  f_image.name
534                                 else:
535                                         key = materialNames[f_mat],  None # No image, use None instead.
536                                 
537                                 # Write the vertex group
538                                 if EXPORT_POLYGROUPS:
539                                         if vertGroupNames:
540                                                 # find what vertext group the face belongs to
541                                                 theVGroup = findVertexGroupName(f,vgroupsMap)
542                                                 if      theVGroup != currentVGroup:
543                                                         currentVGroup = theVGroup
544                                                         file.write('g %s\n' % theVGroup)
545
546                                 # CHECK FOR CONTEXT SWITCH
547                                 if key == contextMat:
548                                         pass # Context alredy switched, dont do anything
549                                 else:
550                                         if key[0] == None and key[1] == None:
551                                                 # Write a null material, since we know the context has changed.
552                                                 if EXPORT_GROUP_BY_MAT:
553                                                         file.write('g %s_%s\n' % (fixName(ob.name), fixName(ob.getData(1))) ) # can be mat_image or (null)
554                                                 file.write('usemtl (null)\n') # mat, image
555                                                 
556                                         else:
557                                                 mat_data= MTL_DICT.get(key)
558                                                 if not mat_data:
559                                                         # First add to global dict so we can export to mtl
560                                                         # Then write mtl
561                                                         
562                                                         # Make a new names from the mat and image name,
563                                                         # converting any spaces to underscores with fixName.
564                                                         
565                                                         # If none image dont bother adding it to the name
566                                                         if key[1] == None:
567                                                                 mat_data = MTL_DICT[key] = ('%s'%fixName(key[0])), materialItems[f_mat], f_image
568                                                         else:
569                                                                 mat_data = MTL_DICT[key] = ('%s_%s' % (fixName(key[0]), fixName(key[1]))), materialItems[f_mat], f_image
570                                                 
571                                                 if EXPORT_GROUP_BY_MAT:
572                                                         file.write('g %s_%s_%s\n' % (fixName(ob.name), fixName(ob.getData(1)), mat_data[0]) ) # can be mat_image or (null)
573
574                                                 file.write('usemtl %s\n' % mat_data[0]) # can be mat_image or (null)
575                                         
576                                 contextMat = key
577                                 if f_smooth != contextSmooth:
578                                         if f_smooth: # on now off
579                                                 file.write('s 1\n')
580                                                 contextSmooth = f_smooth
581                                         else: # was off now on
582                                                 file.write('s off\n')
583                                                 contextSmooth = f_smooth
584                                 
585                                 file.write('f')
586                                 if faceuv:
587                                         if EXPORT_NORMALS:
588                                                 if f_smooth: # Smoothed, use vertex normals
589                                                         for vi, v in enumerate(f_v):
590                                                                 file.write( ' %d/%d/%d' % (\
591                                                                   v.index+totverts,\
592                                                                   totuvco + uv_face_mapping[f_index][vi],\
593                                                                   globalNormals[ veckey3d(v.no) ])) # vert, uv, normal
594                                                         
595                                                 else: # No smoothing, face normals
596                                                         no = globalNormals[ veckey3d(f.no) ]
597                                                         for vi, v in enumerate(f_v):
598                                                                 file.write( ' %d/%d/%d' % (\
599                                                                   v.index+totverts,\
600                                                                   totuvco + uv_face_mapping[f_index][vi],\
601                                                                   no)) # vert, uv, normal
602                                         
603                                         else: # No Normals
604                                                 for vi, v in enumerate(f_v):
605                                                         file.write( ' %d/%d' % (\
606                                                           v.index+totverts,\
607                                                           totuvco + uv_face_mapping[f_index][vi])) # vert, uv
608                                         
609                                         face_vert_index += len(f_v)
610                                 
611                                 else: # No UV's
612                                         if EXPORT_NORMALS:
613                                                 if f_smooth: # Smoothed, use vertex normals
614                                                         for v in f_v:
615                                                                 file.write( ' %d//%d' % (\
616                                                                   v.index+totverts,\
617                                                                   globalNormals[ veckey3d(v.no) ]))
618                                                 else: # No smoothing, face normals
619                                                         no = globalNormals[ veckey3d(f.no) ]
620                                                         for v in f_v:
621                                                                 file.write( ' %d//%d' % (\
622                                                                   v.index+totverts,\
623                                                                   no))
624                                         else: # No Normals
625                                                 for v in f_v:
626                                                         file.write( ' %d' % (\
627                                                           v.index+totverts))
628                                                 
629                                 file.write('\n')
630                         
631                         # Write edges.
632                         if EXPORT_EDGES:
633                                 LOOSE= Mesh.EdgeFlags.LOOSE
634                                 for ed in edges:
635                                         if ed.flag & LOOSE:
636                                                 file.write('f %d %d\n' % (ed.v1.index+totverts, ed.v2.index+totverts))
637                                 
638                         # Make the indicies global rather then per mesh
639                         totverts += len(me.verts)
640                         if faceuv:
641                                 totuvco += uv_unique_count
642                         me.verts= None
643         file.close()
644         
645         
646         # Now we have all our materials, save them
647         if EXPORT_MTL:
648                 write_mtl(mtlfilename)
649         if EXPORT_COPY_IMAGES:
650                 dest_dir = filename
651                 # Remove chars until we are just the path.
652                 while dest_dir and dest_dir[-1] not in '\\/':
653                         dest_dir = dest_dir[:-1]
654                 if dest_dir:
655                         copy_images(dest_dir)
656                 else:
657                         print '\tError: "%s" could not be used as a base for an image path.' % filename
658         
659         print "OBJ Export time: %.2f" % (sys.time() - time1)
660         
661         
662
663 def write_ui(filename):
664         
665         if not filename.lower().endswith('.obj'):
666                 filename += '.obj'
667         
668         if not BPyMessages.Warning_SaveOver(filename):
669                 return
670         
671         global EXPORT_APPLY_MODIFIERS, EXPORT_ROTX90, EXPORT_TRI, EXPORT_EDGES,\
672                 EXPORT_NORMALS, EXPORT_NORMALS_HQ, EXPORT_UV,\
673                 EXPORT_MTL, EXPORT_SEL_ONLY, EXPORT_ALL_SCENES,\
674                 EXPORT_ANIMATION, EXPORT_COPY_IMAGES, EXPORT_BLEN_OBS,\
675                 EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_KEEP_VERT_ORDER,\
676                 EXPORT_POLYGROUPS, EXPORT_CURVE_AS_NURBS
677         
678         EXPORT_APPLY_MODIFIERS = Draw.Create(0)
679         EXPORT_ROTX90 = Draw.Create(1)
680         EXPORT_TRI = Draw.Create(0)
681         EXPORT_EDGES = Draw.Create(1)
682         EXPORT_NORMALS = Draw.Create(0)
683         EXPORT_NORMALS_HQ = Draw.Create(0)
684         EXPORT_UV = Draw.Create(1)
685         EXPORT_MTL = Draw.Create(1)
686         EXPORT_SEL_ONLY = Draw.Create(1)
687         EXPORT_ALL_SCENES = Draw.Create(0)
688         EXPORT_ANIMATION = Draw.Create(0)
689         EXPORT_COPY_IMAGES = Draw.Create(0)
690         EXPORT_BLEN_OBS = Draw.Create(0)
691         EXPORT_GROUP_BY_OB = Draw.Create(0)
692         EXPORT_GROUP_BY_MAT = Draw.Create(0)
693         EXPORT_KEEP_VERT_ORDER = Draw.Create(1)
694         EXPORT_POLYGROUPS = Draw.Create(0)
695         EXPORT_CURVE_AS_NURBS = Draw.Create(1)
696         
697         
698         # Old UI
699         '''
700         # removed too many options are bad!
701         
702         # Get USER Options
703         pup_block = [\
704         ('Context...'),\
705         ('Selection Only', EXPORT_SEL_ONLY, 'Only export objects in visible selection. Else export whole scene.'),\
706         ('All Scenes', EXPORT_ALL_SCENES, 'Each scene as a separate OBJ file.'),\
707         ('Animation', EXPORT_ANIMATION, 'Each frame as a numbered OBJ file.'),\
708         ('Object Prefs...'),\
709         ('Apply Modifiers', EXPORT_APPLY_MODIFIERS, 'Use transformed mesh data from each object. May break vert order for morph targets.'),\
710         ('Rotate X90', EXPORT_ROTX90 , 'Rotate on export so Blenders UP is translated into OBJs UP'),\
711         ('Keep Vert Order', EXPORT_KEEP_VERT_ORDER, 'Keep vert and face order, disables some other options.'),\
712         ('Extra Data...'),\
713         ('Edges', EXPORT_EDGES, 'Edges not connected to faces.'),\
714         ('Normals', EXPORT_NORMALS, 'Export vertex normal data (Ignored on import).'),\
715         ('High Quality Normals', EXPORT_NORMALS_HQ, 'Calculate high quality normals for rendering.'),\
716         ('UVs', EXPORT_UV, 'Export texface UV coords.'),\
717         ('Materials', EXPORT_MTL, 'Write a separate MTL file with the OBJ.'),\
718         ('Copy Images', EXPORT_COPY_IMAGES, 'Copy image files to the export directory, never overwrite.'),\
719         ('Triangulate', EXPORT_TRI, 'Triangulate quads.'),\
720         ('Grouping...'),\
721         ('Objects', EXPORT_BLEN_OBS, 'Export blender objects as "OBJ objects".'),\
722         ('Object Groups', EXPORT_GROUP_BY_OB, 'Export blender objects as "OBJ Groups".'),\
723         ('Material Groups', EXPORT_GROUP_BY_MAT, 'Group by materials.'),\
724         ]
725         
726         if not Draw.PupBlock('Export...', pup_block):
727                 return
728         '''
729         
730         # BEGIN ALTERNATIVE UI *******************
731         if True: 
732                 
733                 EVENT_NONE = 0
734                 EVENT_EXIT = 1
735                 EVENT_REDRAW = 2
736                 EVENT_EXPORT = 3
737                 
738                 GLOBALS = {}
739                 GLOBALS['EVENT'] = EVENT_REDRAW
740                 #GLOBALS['MOUSE'] = Window.GetMouseCoords()
741                 GLOBALS['MOUSE'] = [i/2 for i in Window.GetScreenSize()]
742                 
743                 def obj_ui_set_event(e,v):
744                         GLOBALS['EVENT'] = e
745                 
746                 def do_split(e,v):
747                         global EXPORT_BLEN_OBS, EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_APPLY_MODIFIERS, KEEP_VERT_ORDER, EXPORT_POLYGROUPS
748                         if EXPORT_BLEN_OBS.val or EXPORT_GROUP_BY_OB.val or EXPORT_GROUP_BY_MAT.val or EXPORT_APPLY_MODIFIERS.val:
749                                 EXPORT_KEEP_VERT_ORDER.val = 0
750                         else:
751                                 EXPORT_KEEP_VERT_ORDER.val = 1
752                         
753                 def do_vertorder(e,v):
754                         global EXPORT_BLEN_OBS, EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_APPLY_MODIFIERS, KEEP_VERT_ORDER
755                         if EXPORT_KEEP_VERT_ORDER.val:
756                                 EXPORT_BLEN_OBS.val = EXPORT_GROUP_BY_OB.val = EXPORT_GROUP_BY_MAT.val = EXPORT_APPLY_MODIFIERS.val = 0
757                         else:
758                                 if not (EXPORT_BLEN_OBS.val or EXPORT_GROUP_BY_OB.val or EXPORT_GROUP_BY_MAT.val or EXPORT_APPLY_MODIFIERS.val):
759                                         EXPORT_KEEP_VERT_ORDER.val = 1
760                         
761                         
762                 def do_help(e,v):
763                         url = __url__[0]
764                         print 'Trying to open web browser with documentation at this address...'
765                         print '\t' + url
766                         
767                         try:
768                                 import webbrowser
769                                 webbrowser.open(url)
770                         except:
771                                 print '...could not open a browser window.'
772                 
773                 def obj_ui():
774                         ui_x, ui_y = GLOBALS['MOUSE']
775                         
776                         # Center based on overall pup size
777                         ui_x -= 165
778                         ui_y -= 140
779                         
780                         global EXPORT_APPLY_MODIFIERS, EXPORT_ROTX90, EXPORT_TRI, EXPORT_EDGES,\
781                                 EXPORT_NORMALS, EXPORT_NORMALS_HQ, EXPORT_UV,\
782                                 EXPORT_MTL, EXPORT_SEL_ONLY, EXPORT_ALL_SCENES,\
783                                 EXPORT_ANIMATION, EXPORT_COPY_IMAGES, EXPORT_BLEN_OBS,\
784                                 EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_KEEP_VERT_ORDER,\
785                                 EXPORT_POLYGROUPS, EXPORT_CURVE_AS_NURBS
786
787                         Draw.Label('Context...', ui_x+9, ui_y+239, 220, 20)
788                         Draw.BeginAlign()
789                         EXPORT_SEL_ONLY = Draw.Toggle('Selection Only', EVENT_NONE, ui_x+9, ui_y+219, 110, 20, EXPORT_SEL_ONLY.val, 'Only export objects in visible selection. Else export whole scene.')
790                         EXPORT_ALL_SCENES = Draw.Toggle('All Scenes', EVENT_NONE, ui_x+119, ui_y+219, 110, 20, EXPORT_ALL_SCENES.val, 'Each scene as a separate OBJ file.')
791                         EXPORT_ANIMATION = Draw.Toggle('Animation', EVENT_NONE, ui_x+229, ui_y+219, 110, 20, EXPORT_ANIMATION.val, 'Each frame as a numbered OBJ file.')
792                         Draw.EndAlign()
793                         
794                         
795                         Draw.Label('Output Options...', ui_x+9, ui_y+189, 220, 20)
796                         Draw.BeginAlign()
797                         EXPORT_APPLY_MODIFIERS = Draw.Toggle('Apply Modifiers', EVENT_REDRAW, ui_x+9, ui_y+170, 110, 20, EXPORT_APPLY_MODIFIERS.val, 'Use transformed mesh data from each object. May break vert order for morph targets.', do_split)
798                         EXPORT_ROTX90 = Draw.Toggle('Rotate X90', EVENT_NONE, ui_x+119, ui_y+170, 110, 20, EXPORT_ROTX90.val, 'Rotate on export so Blenders UP is translated into OBJs UP')
799                         EXPORT_COPY_IMAGES = Draw.Toggle('Copy Images', EVENT_NONE, ui_x+229, ui_y+170, 110, 20, EXPORT_COPY_IMAGES.val, 'Copy image files to the export directory, never overwrite.')
800                         Draw.EndAlign()
801                         
802                         
803                         Draw.Label('Export...', ui_x+9, ui_y+139, 220, 20)
804                         Draw.BeginAlign()
805                         EXPORT_EDGES = Draw.Toggle('Edges', EVENT_NONE, ui_x+9, ui_y+120, 50, 20, EXPORT_EDGES.val, 'Edges not connected to faces.')
806                         EXPORT_TRI = Draw.Toggle('Triangulate', EVENT_NONE, ui_x+59, ui_y+120, 70, 20, EXPORT_TRI.val, 'Triangulate quads.')
807                         Draw.EndAlign()
808                         Draw.BeginAlign()
809                         EXPORT_MTL = Draw.Toggle('Materials', EVENT_NONE, ui_x+139, ui_y+120, 70, 20, EXPORT_MTL.val, 'Write a separate MTL file with the OBJ.')
810                         EXPORT_UV = Draw.Toggle('UVs', EVENT_NONE, ui_x+209, ui_y+120, 31, 20, EXPORT_UV.val, 'Export texface UV coords.')
811                         Draw.EndAlign()
812                         Draw.BeginAlign()
813                         EXPORT_NORMALS = Draw.Toggle('Normals', EVENT_NONE, ui_x+250, ui_y+120, 59, 20, EXPORT_NORMALS.val, 'Export vertex normal data (Ignored on import).')
814                         EXPORT_NORMALS_HQ = Draw.Toggle('HQ', EVENT_NONE, ui_x+309, ui_y+120, 31, 20, EXPORT_NORMALS_HQ.val, 'Calculate high quality normals for rendering.')
815                         Draw.EndAlign()
816                         EXPORT_POLYGROUPS = Draw.Toggle('Polygroups', EVENT_REDRAW, ui_x+9, ui_y+95, 120, 20, EXPORT_POLYGROUPS.val, 'Export vertex groups as OBJ groups (one group per face approximation).')
817                         
818                         EXPORT_CURVE_AS_NURBS = Draw.Toggle('Nurbs', EVENT_NONE, ui_x+139, ui_y+95, 100, 20, EXPORT_CURVE_AS_NURBS.val, 'Export 3D nurbs curves and polylines as OBJ curves, (bezier not supported).')
819                         
820                         
821                         Draw.Label('Blender Objects as OBJ:', ui_x+9, ui_y+59, 220, 20)
822                         Draw.BeginAlign()
823                         EXPORT_BLEN_OBS = Draw.Toggle('Objects', EVENT_REDRAW, ui_x+9, ui_y+39, 60, 20, EXPORT_BLEN_OBS.val, 'Export blender objects as "OBJ objects".', do_split)
824                         EXPORT_GROUP_BY_OB = Draw.Toggle('Groups', EVENT_REDRAW, ui_x+69, ui_y+39, 60, 20, EXPORT_GROUP_BY_OB.val, 'Export blender objects as "OBJ Groups".', do_split)
825                         EXPORT_GROUP_BY_MAT = Draw.Toggle('Material Groups', EVENT_REDRAW, ui_x+129, ui_y+39, 100, 20, EXPORT_GROUP_BY_MAT.val, 'Group by materials.', do_split)
826                         Draw.EndAlign()
827                         
828                         EXPORT_KEEP_VERT_ORDER = Draw.Toggle('Keep Vert Order', EVENT_REDRAW, ui_x+239, ui_y+39, 100, 20, EXPORT_KEEP_VERT_ORDER.val, 'Keep vert and face order, disables some other options. Use for morph targets.', do_vertorder)
829                         
830                         Draw.BeginAlign()
831                         Draw.PushButton('Online Help', EVENT_REDRAW, ui_x+9, ui_y+9, 110, 20, 'Load the wiki page for this script', do_help)
832                         Draw.PushButton('Cancel', EVENT_EXIT, ui_x+119, ui_y+9, 110, 20, '', obj_ui_set_event)
833                         Draw.PushButton('Export', EVENT_EXPORT, ui_x+229, ui_y+9, 110, 20, 'Export with these settings', obj_ui_set_event)
834                         Draw.EndAlign()
835
836                 
837                 # hack so the toggle buttons redraw. this is not nice at all
838                 while GLOBALS['EVENT'] not in (EVENT_EXIT, EVENT_EXPORT):
839                         Draw.UIBlock(obj_ui, 0)
840                 
841                 if GLOBALS['EVENT'] != EVENT_EXPORT:
842                         return
843                 
844         # END ALTERNATIVE UI *********************
845         
846         
847         if EXPORT_KEEP_VERT_ORDER.val:
848                 EXPORT_BLEN_OBS.val = False
849                 EXPORT_GROUP_BY_OB.val = False
850                 EXPORT_GROUP_BY_MAT.val = False
851                 EXPORT_APPLY_MODIFIERS.val = False
852         
853         Window.EditMode(0)
854         Window.WaitCursor(1)
855         
856         EXPORT_APPLY_MODIFIERS = EXPORT_APPLY_MODIFIERS.val
857         EXPORT_ROTX90 = EXPORT_ROTX90.val
858         EXPORT_TRI = EXPORT_TRI.val
859         EXPORT_EDGES = EXPORT_EDGES.val
860         EXPORT_NORMALS = EXPORT_NORMALS.val
861         EXPORT_NORMALS_HQ = EXPORT_NORMALS_HQ.val
862         EXPORT_UV = EXPORT_UV.val
863         EXPORT_MTL = EXPORT_MTL.val
864         EXPORT_SEL_ONLY = EXPORT_SEL_ONLY.val
865         EXPORT_ALL_SCENES = EXPORT_ALL_SCENES.val
866         EXPORT_ANIMATION = EXPORT_ANIMATION.val
867         EXPORT_COPY_IMAGES = EXPORT_COPY_IMAGES.val
868         EXPORT_BLEN_OBS = EXPORT_BLEN_OBS.val
869         EXPORT_GROUP_BY_OB = EXPORT_GROUP_BY_OB.val
870         EXPORT_GROUP_BY_MAT = EXPORT_GROUP_BY_MAT.val
871         EXPORT_KEEP_VERT_ORDER = EXPORT_KEEP_VERT_ORDER.val
872         EXPORT_POLYGROUPS = EXPORT_POLYGROUPS.val
873         EXPORT_CURVE_AS_NURBS = EXPORT_CURVE_AS_NURBS.val
874         
875         
876         base_name, ext = splitExt(filename)
877         context_name = [base_name, '', '', ext] # basename, scene_name, framenumber, extension
878         
879         # Use the options to export the data using write()
880         # def write(filename, objects, EXPORT_EDGES=False, EXPORT_NORMALS=False, EXPORT_MTL=True, EXPORT_COPY_IMAGES=False, EXPORT_APPLY_MODIFIERS=True):
881         orig_scene = Scene.GetCurrent()
882         if EXPORT_ALL_SCENES:
883                 export_scenes = Scene.Get()
884         else:
885                 export_scenes = [orig_scene]
886         
887         # Export all scenes.
888         for scn in export_scenes:
889                 scn.makeCurrent() # If alredy current, this is not slow.
890                 context = scn.getRenderingContext()
891                 orig_frame = Blender.Get('curframe')
892                 
893                 if EXPORT_ALL_SCENES: # Add scene name into the context_name
894                         context_name[1] = '_%s' % BPySys.cleanName(scn.name) # WARNING, its possible that this could cause a collision. we could fix if were feeling parranoied.
895                 
896                 # Export an animation?
897                 if EXPORT_ANIMATION:
898                         scene_frames = xrange(context.startFrame(), context.endFrame()+1) # up to and including the end frame.
899                 else:
900                         scene_frames = [orig_frame] # Dont export an animation.
901                 
902                 # Loop through all frames in the scene and export.
903                 for frame in scene_frames:
904                         if EXPORT_ANIMATION: # Add frame to the filename.
905                                 context_name[2] = '_%.6d' % frame
906                         
907                         Blender.Set('curframe', frame)
908                         if EXPORT_SEL_ONLY:
909                                 export_objects = scn.objects.context
910                         else:   
911                                 export_objects = scn.objects
912                         
913                         full_path= ''.join(context_name)
914                         
915                         # erm... bit of a problem here, this can overwrite files when exporting frames. not too bad.
916                         # EXPORT THE FILE.
917                         write(full_path, export_objects,\
918                         EXPORT_TRI, EXPORT_EDGES, EXPORT_NORMALS,\
919                         EXPORT_NORMALS_HQ, EXPORT_UV, EXPORT_MTL,\
920                         EXPORT_COPY_IMAGES, EXPORT_APPLY_MODIFIERS,\
921                         EXPORT_ROTX90, EXPORT_BLEN_OBS,\
922                         EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_KEEP_VERT_ORDER,\
923                         EXPORT_POLYGROUPS, EXPORT_CURVE_AS_NURBS)
924                 
925                 Blender.Set('curframe', orig_frame)
926         
927         # Restore old active scene.
928         orig_scene.makeCurrent()
929         Window.WaitCursor(0)
930
931
932 if __name__ == '__main__':
933         Window.FileSelector(write_ui, 'Export Wavefront OBJ', sys.makename(ext='.obj'))