b7bdd54fe6de1dfa3d78e8f1c6e90514b5cfba12
[blender.git] / release / scripts / import_obj.py
1 #!BPY
2  
3 """
4 Name: 'Wavefront (.obj)...'
5 Blender: 248
6 Group: 'Import'
7 Tooltip: 'Load a Wavefront OBJ File, Shift: batch import all dir.'
8 """
9
10 __author__= "Campbell Barton", "Jiri Hnidek"
11 __url__= ['http://wiki.blender.org/index.php/Scripts/Manual/Import/wavefront_obj', 'blender.org', 'blenderartists.org']
12 __version__= "2.1"
13
14 __bpydoc__= """\
15 This script imports a Wavefront OBJ files to Blender.
16
17 Usage:
18 Run this script from "File->Import" menu and then load the desired OBJ file.
19 Note, This loads mesh objects and materials only, nurbs and curves are not supported.
20 """
21
22 # ***** BEGIN GPL LICENSE BLOCK *****
23 #
24 # Script copyright (C) Campbell J Barton 2007
25 #
26 # This program is free software; you can redistribute it and/or
27 # modify it under the terms of the GNU General Public License
28 # as published by the Free Software Foundation; either version 2
29 # of the License, or (at your option) any later version.
30 #
31 # This program is distributed in the hope that it will be useful,
32 # but WITHOUT ANY WARRANTY; without even the implied warranty of
33 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
34 # GNU General Public License for more details.
35 #
36 # You should have received a copy of the GNU General Public License
37 # along with this program; if not, write to the Free Software Foundation,
38 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
39 #
40 # ***** END GPL LICENCE BLOCK *****
41 # --------------------------------------------------------------------------
42
43 from Blender import *
44 import bpy
45 import BPyMesh
46 import BPyImage
47 import BPyMessages
48
49 try:            import os
50 except:         os= False
51
52
53 # Generic path functions
54 def stripFile(path):
55         '''Return directory, where the file is'''
56         lastSlash= max(path.rfind('\\'), path.rfind('/'))
57         if lastSlash != -1:
58                 path= path[:lastSlash]
59         return '%s%s' % (path, sys.sep)
60
61 def stripPath(path):
62         '''Strips the slashes from the back of a string'''
63         return path.split('/')[-1].split('\\')[-1]
64
65 def stripExt(name): # name is a string
66         '''Strips the prefix off the name before writing'''
67         index= name.rfind('.')
68         if index != -1:
69                 return name[ : index ]
70         else:
71                 return name
72 # end path funcs
73
74
75
76 def line_value(line_split):
77         '''
78         Returns 1 string represneting the value for this line
79         None will be returned if theres only 1 word
80         '''
81         length= len(line_split)
82         if length == 1:
83                 return None
84         
85         elif length == 2:
86                 return line_split[1]
87         
88         elif length > 2:
89                 return ' '.join( line_split[1:] )
90
91 def obj_image_load(imagepath, DIR, IMAGE_SEARCH):
92         '''
93         Mainly uses comprehensiveImageLoad
94         but tries to replace '_' with ' ' for Max's exporter replaces spaces with underscores.
95         '''
96         
97         if '_' in imagepath:
98                 image= BPyImage.comprehensiveImageLoad(imagepath, DIR, PLACE_HOLDER= False, RECURSIVE= IMAGE_SEARCH)
99                 if image: return image
100                 # Did the exporter rename the image?
101                 image= BPyImage.comprehensiveImageLoad(imagepath.replace('_', ' '), DIR, PLACE_HOLDER= False, RECURSIVE= IMAGE_SEARCH)
102                 if image: return image
103         
104         # Return an image, placeholder if it dosnt exist
105         image= BPyImage.comprehensiveImageLoad(imagepath, DIR, PLACE_HOLDER= True, RECURSIVE= IMAGE_SEARCH)
106         return image
107         
108
109 def create_materials(filepath, material_libs, unique_materials, unique_material_images, IMAGE_SEARCH):
110         '''
111         Create all the used materials in this obj,
112         assign colors and images to the materials from all referenced material libs
113         '''
114         DIR= stripFile(filepath)
115         
116         #==================================================================================#
117         # This function sets textures defined in .mtl file                                 #
118         #==================================================================================#
119         def load_material_image(blender_material, context_material_name, imagepath, type):
120                 
121                 texture= bpy.data.textures.new(type)
122                 texture.setType('Image')
123                 
124                 # Absolute path - c:\.. etc would work here
125                 image= obj_image_load(imagepath, DIR, IMAGE_SEARCH)
126                 has_data = image.has_data
127                 texture.image = image
128                 
129                 # Adds textures for materials (rendering)
130                 if type == 'Kd':
131                         if has_data and image.depth == 32:
132                                 # Image has alpha
133                                 blender_material.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL | Texture.MapTo.ALPHA)
134                                 texture.setImageFlags('MipMap', 'InterPol', 'UseAlpha')
135                                 blender_material.mode |= Material.Modes.ZTRANSP
136                                 blender_material.alpha = 0.0
137                         else:
138                                 blender_material.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL)
139                                 
140                         # adds textures to faces (Textured/Alt-Z mode)
141                         # Only apply the diffuse texture to the face if the image has not been set with the inline usemat func.
142                         unique_material_images[context_material_name]= image, has_data # set the texface image
143                 
144                 elif type == 'Ka':
145                         blender_material.setTexture(1, texture, Texture.TexCo.UV, Texture.MapTo.CMIR) # TODO- Add AMB to BPY API
146                         
147                 elif type == 'Ks':
148                         blender_material.setTexture(2, texture, Texture.TexCo.UV, Texture.MapTo.SPEC)
149                 
150                 elif type == 'Bump':
151                         blender_material.setTexture(3, texture, Texture.TexCo.UV, Texture.MapTo.NOR)            
152                 elif type == 'D':
153                         blender_material.setTexture(4, texture, Texture.TexCo.UV, Texture.MapTo.ALPHA)                          
154                         blender_material.mode |= Material.Modes.ZTRANSP
155                         blender_material.alpha = 0.0
156                         # Todo, unset deffuse material alpha if it has an alpha channel
157                         
158                 elif type == 'refl':
159                         blender_material.setTexture(5, texture, Texture.TexCo.UV, Texture.MapTo.REF)            
160         
161         
162         # Add an MTL with the same name as the obj if no MTLs are spesified.
163         temp_mtl= stripExt(stripPath(filepath))+ '.mtl'
164         
165         if sys.exists(DIR + temp_mtl) and temp_mtl not in material_libs:
166                         material_libs.append( temp_mtl )
167         del temp_mtl
168         
169         #Create new materials
170         for name in unique_materials.iterkeys():
171                 unique_materials[name]= bpy.data.materials.new(name)
172                 
173                 unique_material_images[name]= None, False # assign None to all material images to start with, add to later.
174                 
175         unique_materials[None]= None
176         
177         for libname in material_libs:
178                 mtlpath= DIR + libname
179                 if not sys.exists(mtlpath):
180                         #print '\tError Missing MTL: "%s"' % mtlpath
181                         pass
182                 else:
183                         #print '\t\tloading mtl: "%s"' % mtlpath
184                         context_material= None
185                         mtl= open(mtlpath, 'rU')
186                         for line in mtl: #.xreadlines():
187                                 if line.startswith('newmtl'):
188                                         context_material_name= line_value(line.split())
189                                         if unique_materials.has_key(context_material_name):
190                                                 context_material = unique_materials[ context_material_name ]
191                                         else:
192                                                 context_material = None
193                                 
194                                 elif context_material:
195                                         # we need to make a material to assign properties to it.
196                                         line_split= line.split()
197                                         line_lower= line.lower().lstrip()
198                                         if line_lower.startswith('ka'):
199                                                 context_material.setMirCol((float(line_split[1]), float(line_split[2]), float(line_split[3])))
200                                         elif line_lower.startswith('kd'):
201                                                 context_material.setRGBCol((float(line_split[1]), float(line_split[2]), float(line_split[3])))
202                                         elif line_lower.startswith('ks'):
203                                                 context_material.setSpecCol((float(line_split[1]), float(line_split[2]), float(line_split[3])))
204                                         elif line_lower.startswith('ns'):
205                                                 context_material.setHardness( int((float(line_split[1])*0.51)) )
206                                         elif line_lower.startswith('ni'): # Refraction index
207                                                 context_material.setIOR( max(1, min(float(line_split[1]), 3))) # Between 1 and 3
208                                         elif line_lower.startswith('d') or line_lower.startswith('tr'):
209                                                 context_material.setAlpha(float(line_split[1]))
210                                         elif line_lower.startswith('map_ka'):
211                                                 img_filepath= line_value(line.split())
212                                                 if img_filepath:
213                                                         load_material_image(context_material, context_material_name, img_filepath, 'Ka')
214                                         elif line_lower.startswith('map_ks'):
215                                                 img_filepath= line_value(line.split())
216                                                 if img_filepath:
217                                                         load_material_image(context_material, context_material_name, img_filepath, 'Ks')
218                                         elif line_lower.startswith('map_kd'):
219                                                 img_filepath= line_value(line.split())
220                                                 if img_filepath:
221                                                         load_material_image(context_material, context_material_name, img_filepath, 'Kd')
222                                         elif line_lower.startswith('map_bump'):
223                                                 img_filepath= line_value(line.split())
224                                                 if img_filepath:
225                                                         load_material_image(context_material, context_material_name, img_filepath, 'Bump')
226                                         elif line_lower.startswith('map_d') or line_lower.startswith('map_tr'): # Alpha map - Dissolve
227                                                 img_filepath= line_value(line.split())
228                                                 if img_filepath:
229                                                         load_material_image(context_material, context_material_name, img_filepath, 'D')
230                                         
231                                         elif line_lower.startswith('refl'): # Reflectionmap
232                                                 img_filepath= line_value(line.split())
233                                                 if img_filepath:
234                                                         load_material_image(context_material, context_material_name, img_filepath, 'refl')
235                         mtl.close()
236
237
238
239         
240 def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP, SPLIT_MATERIALS):
241         '''
242         Takes vert_loc and faces, and seperates into multiple sets of 
243         (verts_loc, faces, unique_materials, dataname)
244         This is done so objects do not overload the 16 material limit.
245         '''
246         
247         filename = stripExt(stripPath(filepath))
248         
249         if not SPLIT_OB_OR_GROUP and not SPLIT_MATERIALS:
250                 # use the filename for the object name since we arnt chopping up the mesh.
251                 return [(verts_loc, faces, unique_materials, filename)]
252         
253         
254         def key_to_name(key):
255                 # if the key is a tuple, join it to make a string
256                 if type(key) == tuple:
257                         return '%s_%s' % key
258                 elif not key:
259                         return filename # assume its a string. make sure this is true if the splitting code is changed
260                 else:
261                         return key
262         
263         # Return a key that makes the faces unique.
264         if SPLIT_OB_OR_GROUP and not SPLIT_MATERIALS:
265                 def face_key(face):
266                         return face[4] # object
267         
268         elif not SPLIT_OB_OR_GROUP and SPLIT_MATERIALS:
269                 def face_key(face):
270                         return face[2] # material
271         
272         else: # Both
273                 def face_key(face):
274                         return face[4], face[2] # object,material               
275         
276         
277         face_split_dict= {}
278         
279         oldkey= -1 # initialize to a value that will never match the key
280         
281         for face in faces:
282                 
283                 key= face_key(face)
284                 
285                 if oldkey != key:
286                         # Check the key has changed.
287                         try:
288                                 verts_split, faces_split, unique_materials_split, vert_remap= face_split_dict[key]
289                         except KeyError:
290                                 faces_split= []
291                                 verts_split= []
292                                 unique_materials_split= {}
293                                 vert_remap= [-1]*len(verts_loc)
294                                 
295                                 face_split_dict[key]= (verts_split, faces_split, unique_materials_split, vert_remap)
296                         
297                         oldkey= key
298                         
299                 face_vert_loc_indicies= face[0]
300                 
301                 # Remap verts to new vert list and add where needed
302                 for enum, i in enumerate(face_vert_loc_indicies):
303                         if vert_remap[i] == -1:
304                                 new_index= len(verts_split)
305                                 vert_remap[i]= new_index # set the new remapped index so we only add once and can reference next time.
306                                 face_vert_loc_indicies[enum] = new_index # remap to the local index
307                                 verts_split.append( verts_loc[i] ) # add the vert to the local verts 
308                                 
309                         else:
310                                 face_vert_loc_indicies[enum] = vert_remap[i] # remap to the local index
311                         
312                         matname= face[2]
313                         if matname and not unique_materials_split.has_key(matname):
314                                 unique_materials_split[matname] = unique_materials[matname]
315                 
316                 faces_split.append(face)
317         
318         
319         # remove one of the itemas and reorder
320         return [(value[0], value[1], value[2], key_to_name(key)) for key, value in face_split_dict.iteritems()]
321
322
323 def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_loc, verts_tex, faces, unique_materials, unique_material_images, unique_smooth_groups, dataname):
324         '''
325         Takes all the data gathered and generates a mesh, adding the new object to new_objects
326         deals with fgons, sharp edges and assigning materials
327         '''
328         if not has_ngons:
329                 CREATE_FGONS= False
330         
331         if unique_smooth_groups:
332                 sharp_edges= {}
333                 smooth_group_users= dict([ (context_smooth_group, {}) for context_smooth_group in unique_smooth_groups.iterkeys() ])
334                 context_smooth_group_old= -1
335         
336         # Split fgons into tri's
337         fgon_edges= {} # Used for storing fgon keys
338         if CREATE_EDGES:
339                 edges= []
340         
341         context_object= None
342         
343         # reverse loop through face indicies
344         for f_idx in xrange(len(faces)-1, -1, -1):
345                 
346                 face_vert_loc_indicies,\
347                 face_vert_tex_indicies,\
348                 context_material,\
349                 context_smooth_group,\
350                 context_object= faces[f_idx]
351                 
352                 len_face_vert_loc_indicies = len(face_vert_loc_indicies)
353                 
354                 if len_face_vert_loc_indicies==1:
355                         faces.pop(f_idx)# cant add single vert faces
356                 
357                 elif not face_vert_tex_indicies or len_face_vert_loc_indicies == 2: # faces that have no texture coords are lines
358                         if CREATE_EDGES:
359                                 # generators are better in python 2.4+ but can't be used in 2.3
360                                 # edges.extend( (face_vert_loc_indicies[i], face_vert_loc_indicies[i+1]) for i in xrange(len_face_vert_loc_indicies-1) )
361                                 edges.extend( [(face_vert_loc_indicies[i], face_vert_loc_indicies[i+1]) for i in xrange(len_face_vert_loc_indicies-1)] )
362
363                         faces.pop(f_idx)
364                 else:
365                         
366                         # Smooth Group
367                         if unique_smooth_groups and context_smooth_group:
368                                 # Is a part of of a smooth group and is a face
369                                 if context_smooth_group_old is not context_smooth_group:
370                                         edge_dict= smooth_group_users[context_smooth_group]
371                                         context_smooth_group_old= context_smooth_group
372                                 
373                                 for i in xrange(len_face_vert_loc_indicies):
374                                         i1= face_vert_loc_indicies[i]
375                                         i2= face_vert_loc_indicies[i-1]
376                                         if i1>i2: i1,i2= i2,i1
377                                         
378                                         try:
379                                                 edge_dict[i1,i2]+= 1
380                                         except KeyError:
381                                                 edge_dict[i1,i2]=  1
382                         
383                         # FGons into triangles
384                         if has_ngons and len_face_vert_loc_indicies > 4:
385                                 
386                                 ngon_face_indices= BPyMesh.ngon(verts_loc, face_vert_loc_indicies)
387                                 faces.extend(\
388                                 [(\
389                                 [face_vert_loc_indicies[ngon[0]], face_vert_loc_indicies[ngon[1]], face_vert_loc_indicies[ngon[2]] ],\
390                                 [face_vert_tex_indicies[ngon[0]], face_vert_tex_indicies[ngon[1]], face_vert_tex_indicies[ngon[2]] ],\
391                                 context_material,\
392                                 context_smooth_group,\
393                                 context_object)\
394                                 for ngon in ngon_face_indices]\
395                                 )
396                                 
397                                 # edges to make fgons
398                                 if CREATE_FGONS:
399                                         edge_users= {}
400                                         for ngon in ngon_face_indices:
401                                                 for i in (0,1,2):
402                                                         i1= face_vert_loc_indicies[ngon[i  ]]
403                                                         i2= face_vert_loc_indicies[ngon[i-1]]
404                                                         if i1>i2: i1,i2= i2,i1
405                                                         
406                                                         try:
407                                                                 edge_users[i1,i2]+=1
408                                                         except KeyError:
409                                                                 edge_users[i1,i2]= 1
410                                         
411                                         for key, users in edge_users.iteritems():
412                                                 if users>1:
413                                                         fgon_edges[key]= None
414                                 
415                                 # remove all after 3, means we dont have to pop this one.
416                                 faces.pop(f_idx)
417                 
418                 
419         # Build sharp edges
420         if unique_smooth_groups:
421                 for edge_dict in smooth_group_users.itervalues():
422                         for key, users in edge_dict.iteritems():
423                                 if users==1: # This edge is on the boundry of a group
424                                         sharp_edges[key]= None
425         
426         
427         # mat the material names to an index
428         material_mapping= dict([(name, i) for i, name in enumerate(unique_materials.keys())])
429         
430         materials= [None] * len(unique_materials)
431         
432         for name, index in material_mapping.iteritems():
433                 materials[index]= unique_materials[name]
434         
435         me= bpy.data.meshes.new(dataname)
436         
437         me.materials= materials[0:16] # make sure the list isnt too big.
438         #me.verts.extend([(0,0,0)]) # dummy vert
439         me.verts.extend(verts_loc)
440         
441         face_mapping= me.faces.extend([f[0] for f in faces], indexList=True)
442         
443         if verts_tex and me.faces:
444                 me.faceUV= 1
445                 # TEXMODE= Mesh.FaceModes['TEX']
446         
447         context_material_old= -1 # avoid a dict lookup
448         mat= 0 # rare case it may be un-initialized.
449         me_faces= me.faces
450         ALPHA= Mesh.FaceTranspModes.ALPHA
451         
452         for i, face in enumerate(faces):
453                 if len(face[0]) < 2:
454                         pass #raise "bad face"
455                 elif len(face[0])==2:
456                         if CREATE_EDGES:
457                                 edges.append(face[0])
458                 else:
459                         face_index_map= face_mapping[i]
460                         if face_index_map!=None: # None means the face wasnt added
461                                 blender_face= me_faces[face_index_map]
462                                 
463                                 face_vert_loc_indicies,\
464                                 face_vert_tex_indicies,\
465                                 context_material,\
466                                 context_smooth_group,\
467                                 context_object= face
468                                 
469                                 
470                                 
471                                 if context_smooth_group:
472                                         blender_face.smooth= True
473                                 
474                                 if context_material:
475                                         if context_material_old is not context_material:
476                                                 mat= material_mapping[context_material]
477                                                 if mat>15:
478                                                         mat= 15
479                                                 context_material_old= context_material
480                                         
481                                         blender_face.mat= mat
482                                 
483                                 
484                                 if verts_tex:   
485                                         if context_material:
486                                                 image, has_data= unique_material_images[context_material]
487                                                 if image: # Can be none if the material dosnt have an image.
488                                                         blender_face.image= image
489                                                         if has_data and image.depth == 32:
490                                                                 blender_face.transp |= ALPHA
491                                         
492                                         # BUG - Evil eekadoodle problem where faces that have vert index 0 location at 3 or 4 are shuffled.
493                                         if len(face_vert_loc_indicies)==4:
494                                                 if face_vert_loc_indicies[2]==0 or face_vert_loc_indicies[3]==0:
495                                                         face_vert_tex_indicies= face_vert_tex_indicies[2], face_vert_tex_indicies[3], face_vert_tex_indicies[0], face_vert_tex_indicies[1]
496                                         else: # length of 3
497                                                 if face_vert_loc_indicies[2]==0:
498                                                         face_vert_tex_indicies= face_vert_tex_indicies[1], face_vert_tex_indicies[2], face_vert_tex_indicies[0]
499                                         # END EEEKADOODLE FIX
500                                         
501                                         # assign material, uv's and image
502                                         for ii, uv in enumerate(blender_face.uv):
503                                                 uv.x, uv.y=  verts_tex[face_vert_tex_indicies[ii]]
504         del me_faces
505         del ALPHA
506         
507         # Add edge faces.
508         me_edges= me.edges
509         if CREATE_FGONS and fgon_edges:
510                 FGON= Mesh.EdgeFlags.FGON
511                 for ed in me.findEdges( fgon_edges.keys() ):
512                         if ed!=None:
513                                 me_edges[ed].flag |= FGON
514                 del FGON
515         
516         if unique_smooth_groups and sharp_edges:
517                 SHARP= Mesh.EdgeFlags.SHARP
518                 for ed in me.findEdges( sharp_edges.keys() ):
519                         if ed!=None:
520                                 me_edges[ed].flag |= SHARP
521                 del SHARP
522         
523         if CREATE_EDGES:
524                 me_edges.extend( edges )
525         
526         del me_edges
527         
528         me.calcNormals()
529         
530         ob= scn.objects.new(me)
531         new_objects.append(ob)
532
533 def get_float_func(filepath):
534         '''
535         find the float function for this obj file
536         - weather to replace commas or not
537         '''
538         file= open(filepath, 'rU')
539         for line in file: #.xreadlines():
540                 if line.startswith('v'): # vn vt v 
541                         if ',' in line:
542                                 return lambda f: float(f.replace(',', '.'))
543                         elif '.' in line:
544                                 return float
545         
546         # incase all vert values were ints 
547         return float
548
549 def load_obj(filepath, CLAMP_SIZE= 0.0, CREATE_FGONS= True, CREATE_SMOOTH_GROUPS= True, CREATE_EDGES= True, SPLIT_OBJECTS= True, SPLIT_GROUPS= True, SPLIT_MATERIALS= True, IMAGE_SEARCH=True):
550         '''
551         Called by the user interface or another script.
552         load_obj(path) - should give acceptable results.
553         This function passes the file and sends the data off
554                 to be split into objects and then converted into mesh objects
555         '''
556         print '\nimporting obj "%s"' % filepath
557         
558         time_main= sys.time()
559         
560         verts_loc= []
561         verts_tex= []
562         faces= [] # tuples of the faces
563         material_libs= [] # filanems to material libs this uses
564         
565         
566         # Get the string to float conversion func for this file- is 'float' for almost all files.
567         float_func= get_float_func(filepath)
568         
569         # Context variables
570         context_material= None
571         context_smooth_group= None
572         context_object= None
573         
574         has_ngons= False
575         # has_smoothgroups= False - is explicit with len(unique_smooth_groups) being > 0
576         
577         # Until we can use sets
578         unique_materials= {}
579         unique_material_images= {}
580         unique_smooth_groups= {}
581         # unique_obects= {} - no use for this variable since the objects are stored in the face.
582         
583         # when there are faces that end with \
584         # it means they are multiline- 
585         # since we use xreadline we cant skip to the next line
586         # so we need to know weather 
587         multi_line_face= False
588         
589         print '\tpassing obj file "%s"...' % filepath,
590         time_sub= sys.time()
591         file= open(filepath, 'rU')
592         for line in file: #.xreadlines():
593                 
594                 if line.startswith('v '):
595                         line_split= line.split()
596                         # rotate X90: (x,-z,y)
597                         verts_loc.append( (float_func(line_split[1]), -float_func(line_split[3]), float_func(line_split[2])) )
598                                 
599                 elif line.startswith('vn '):
600                         pass
601                 
602                 elif line.startswith('vt '):
603                         line_split= line.split()
604                         verts_tex.append( (float_func(line_split[1]), float_func(line_split[2])) ) 
605                 
606                 # Handel faces lines (as faces) and the second+ lines of fa multiline face here
607                 # use 'f' not 'f ' because some objs (very rare have 'fo ' for faces)
608                 elif line.startswith('f') or (line.startswith('l ') and CREATE_EDGES) or multi_line_face:
609                         
610                         if multi_line_face:
611                                 # use face_vert_loc_indicies and face_vert_tex_indicies previously defined and used the obj_face
612                                 line_split= line.split()
613                                 multi_line_face= False
614                                 
615                         else:
616                                 line_split= line[2:].split()
617                                 face_vert_loc_indicies= []
618                                 face_vert_tex_indicies= []
619                                 
620                                 # Instance a face
621                                 faces.append((\
622                                 face_vert_loc_indicies,\
623                                 face_vert_tex_indicies,\
624                                 context_material,\
625                                 context_smooth_group,\
626                                 context_object\
627                                 ))
628                         
629                         if line_split[-1][-1]== '\\':
630                                 multi_line_face= True
631                                 if len(line_split[-1])==1:
632                                         line_split.pop() # remove the \ item
633                                 else:
634                                         line_split[-1]= line_split[-1][:-1] # remove the \ from the end last number
635                         
636                         isline= line.startswith('l')
637                         
638                         for v in line_split:
639                                 obj_vert= v.split('/')
640                                 
641                                 vert_loc_index= int(obj_vert[0])-1
642                                 
643                                 # Make relative negative vert indicies absolute
644                                 if vert_loc_index < 0:
645                                         vert_loc_index= len(verts_loc) + vert_loc_index + 1
646                                 
647                                 face_vert_loc_indicies.append(vert_loc_index)
648                                 
649                                 if not isline:
650                                         if len(obj_vert)>1 and obj_vert[1]:
651                                                 # formatting for faces with normals and textures us 
652                                                 # loc_index/tex_index/nor_index
653                                                 
654                                                 vert_tex_index= int(obj_vert[1])-1
655                                                 # Make relative negative vert indicies absolute
656                                                 if vert_tex_index < 0:
657                                                         vert_tex_index= len(verts_tex) + vert_tex_index + 1
658                                                 
659                                                 face_vert_tex_indicies.append(vert_tex_index)
660                                         else:
661                                                 # dummy
662                                                 face_vert_tex_indicies.append(0)
663                         
664                         if len(face_vert_loc_indicies) > 4:
665                                 has_ngons= True
666                         
667                 elif line.startswith('s'):
668                         if CREATE_SMOOTH_GROUPS:
669                                 context_smooth_group= line_value(line.split())
670                                 if context_smooth_group=='off':
671                                         context_smooth_group= None
672                                 elif context_smooth_group: # is not None
673                                         unique_smooth_groups[context_smooth_group]= None
674                 
675                 elif line.startswith('o'):
676                         if SPLIT_OBJECTS:
677                                 context_object= line_value(line.split())
678                                 # unique_obects[context_object]= None
679                         
680                 elif line.startswith('g'):
681                         if SPLIT_GROUPS:
682                                 context_object= line_value(line.split())
683                                 # print 'context_object', context_object
684                                 # unique_obects[context_object]= None
685                 
686                 elif line.startswith('usemtl'):
687                         context_material= line_value(line.split())
688                         unique_materials[context_material]= None
689                 elif line.startswith('mtllib'): # usemap or usemat
690                         material_libs.extend( line.split()[1:] ) # can have multiple mtllib filenames per line
691                 
692                 ''' # How to use usemap? depricated?
693                 elif line.startswith('usema'): # usemap or usemat
694                         context_image= line_value(line.split())
695                 '''
696         
697         file.close()
698         time_new= sys.time()
699         print '%.4f sec' % (time_new-time_sub)
700         time_sub= time_new
701         
702         
703         print '\tloading materials and images...',
704         create_materials(filepath, material_libs, unique_materials, unique_material_images, IMAGE_SEARCH)
705         
706         time_new= sys.time()
707         print '%.4f sec' % (time_new-time_sub)
708         time_sub= time_new
709         
710         
711         # deselect all
712         scn = bpy.data.scenes.active
713         scn.objects.selected = []
714         new_objects= [] # put new objects here
715         
716         print '\tbuilding geometry...\n\tverts:%i faces:%i materials: %i smoothgroups:%i ...' % ( len(verts_loc), len(faces), len(unique_materials), len(unique_smooth_groups) ),
717         # Split the mesh by objects/materials, may 
718         if SPLIT_OBJECTS or SPLIT_GROUPS:       SPLIT_OB_OR_GROUP = True
719         else:                                                           SPLIT_OB_OR_GROUP = False
720         
721         for verts_loc_split, faces_split, unique_materials_split, dataname in split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP, SPLIT_MATERIALS):
722                 # Create meshes from the data
723                 create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_loc_split, verts_tex, faces_split, unique_materials_split, unique_material_images, unique_smooth_groups, dataname)
724         
725         axis_min= [ 1000000000]*3
726         axis_max= [-1000000000]*3
727         
728         if CLAMP_SIZE:
729                 # Get all object bounds
730                 for ob in new_objects:
731                         for v in ob.getBoundBox():
732                                 for axis, value in enumerate(v):
733                                         if axis_min[axis] > value:      axis_min[axis]= value
734                                         if axis_max[axis] < value:      axis_max[axis]= value
735                 
736                 # Scale objects
737                 max_axis= max(axis_max[0]-axis_min[0], axis_max[1]-axis_min[1], axis_max[2]-axis_min[2])
738                 scale= 1.0
739                 
740                 while CLAMP_SIZE < max_axis * scale:
741                         scale= scale/10.0
742                 
743                 for ob in new_objects:
744                         ob.setSize(scale, scale, scale)
745         
746         time_new= sys.time()
747         
748         print '%.4f sec' % (time_new-time_sub)
749         print 'finished importing: "%s" in %.4f sec.' % (filepath, (time_new-time_main))
750
751
752 DEBUG= True
753
754
755 def load_obj_ui(filepath, BATCH_LOAD= False):
756         if BPyMessages.Error_NoFile(filepath):
757                 return
758         
759         global CREATE_SMOOTH_GROUPS, CREATE_FGONS, CREATE_EDGES, SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, CLAMP_SIZE, IMAGE_SEARCH, KEEP_VERT_ORDER
760         
761         CREATE_SMOOTH_GROUPS= Draw.Create(0)
762         CREATE_FGONS= Draw.Create(1)
763         CREATE_EDGES= Draw.Create(1)
764         SPLIT_OBJECTS= Draw.Create(0)
765         SPLIT_GROUPS= Draw.Create(0)
766         SPLIT_MATERIALS= Draw.Create(0)
767         CLAMP_SIZE= Draw.Create(10.0)
768         IMAGE_SEARCH= Draw.Create(1)
769         KEEP_VERT_ORDER= Draw.Create(1)
770         
771         
772         # Get USER Options
773         # Note, Works but not pretty, instead use a more complicated GUI
774         '''
775         pup_block= [\
776         'Import...',\
777         ('Smooth Groups', CREATE_SMOOTH_GROUPS, 'Surround smooth groups by sharp edges'),\
778         ('Create FGons', CREATE_FGONS, 'Import faces with more then 4 verts as fgons.'),\
779         ('Lines', CREATE_EDGES, 'Import lines and faces with 2 verts as edges'),\
780         'Separate objects from obj...',\
781         ('Object', SPLIT_OBJECTS, 'Import OBJ Objects into Blender Objects'),\
782         ('Group', SPLIT_GROUPS, 'Import OBJ Groups into Blender Objects'),\
783         ('Material', SPLIT_MATERIALS, 'Import each material into a seperate mesh (Avoids > 16 per mesh error)'),\
784         'Options...',\
785         ('Keep Vert Order', KEEP_VERT_ORDER, 'Keep vert and face order, disables some other options.'),\
786         ('Clamp Scale:', CLAMP_SIZE, 0.0, 1000.0, 'Clamp the size to this maximum (Zero to Disable)'),\
787         ('Image Search', IMAGE_SEARCH, 'Search subdirs for any assosiated images (Warning, may be slow)'),\
788         ]
789         
790         if not Draw.PupBlock('Import OBJ...', pup_block):
791                 return
792         
793         if KEEP_VERT_ORDER.val:
794                 SPLIT_OBJECTS.val = False
795                 SPLIT_GROUPS.val = False
796                 SPLIT_MATERIALS.val = False
797         '''
798         
799         
800         
801         # BEGIN ALTERNATIVE UI *******************
802         if True: 
803                 
804                 EVENT_NONE = 0
805                 EVENT_EXIT = 1
806                 EVENT_REDRAW = 2
807                 EVENT_IMPORT = 3
808                 
809                 GLOBALS = {}
810                 GLOBALS['EVENT'] = EVENT_REDRAW
811                 #GLOBALS['MOUSE'] = Window.GetMouseCoords()
812                 GLOBALS['MOUSE'] = [i/2 for i in Window.GetScreenSize()]
813                 
814                 def obj_ui_set_event(e,v):
815                         GLOBALS['EVENT'] = e
816                 
817                 def do_split(e,v):
818                         global SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, KEEP_VERT_ORDER
819                         if SPLIT_OBJECTS.val or SPLIT_GROUPS.val or SPLIT_MATERIALS.val:
820                                 KEEP_VERT_ORDER.val = 0
821                         else:
822                                 KEEP_VERT_ORDER.val = 1
823                         
824                 def do_vertorder(e,v):
825                         global SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, KEEP_VERT_ORDER
826                         if KEEP_VERT_ORDER.val:
827                                 SPLIT_OBJECTS.val = SPLIT_GROUPS.val = SPLIT_MATERIALS.val = 0
828                         else:
829                                 if not (SPLIT_OBJECTS.val or SPLIT_GROUPS.val or SPLIT_MATERIALS.val):
830                                         KEEP_VERT_ORDER.val = 1
831                         
832                 def do_help(e,v):
833                         url = __url__[0]
834                         print 'Trying to open web browser with documentation at this address...'
835                         print '\t' + url
836                         
837                         try:
838                                 import webbrowser
839                                 webbrowser.open(url)
840                         except:
841                                 print '...could not open a browser window.'
842                 
843                 def obj_ui():
844                         ui_x, ui_y = GLOBALS['MOUSE']
845                         
846                         # Center based on overall pup size
847                         ui_x -= 165
848                         ui_y -= 90
849                         
850                         global CREATE_SMOOTH_GROUPS, CREATE_FGONS, CREATE_EDGES, SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, CLAMP_SIZE, IMAGE_SEARCH, KEEP_VERT_ORDER
851                         
852                         Draw.Label('Import...', ui_x+9, ui_y+159, 220, 21)
853                         Draw.BeginAlign()
854                         CREATE_SMOOTH_GROUPS = Draw.Toggle('Smooth Groups', EVENT_NONE, ui_x+9, ui_y+139, 110, 20, CREATE_SMOOTH_GROUPS.val, 'Surround smooth groups by sharp edges')
855                         CREATE_FGONS = Draw.Toggle('NGons as FGons', EVENT_NONE, ui_x+119, ui_y+139, 110, 20, CREATE_FGONS.val, 'Import faces with more then 4 verts as fgons')
856                         CREATE_EDGES = Draw.Toggle('Lines as Edges', EVENT_NONE, ui_x+229, ui_y+139, 110, 20, CREATE_EDGES.val, 'Import lines and faces with 2 verts as edges')
857                         Draw.EndAlign()
858                         
859                         Draw.Label('Separate objects by OBJ...', ui_x+9, ui_y+110, 220, 20)
860                         Draw.BeginAlign()
861                         SPLIT_OBJECTS = Draw.Toggle('Object', EVENT_REDRAW, ui_x+9, ui_y+89, 70, 21, SPLIT_OBJECTS.val, 'Import OBJ Objects into Blender Objects', do_split)
862                         SPLIT_GROUPS = Draw.Toggle('Group', EVENT_REDRAW, ui_x+79, ui_y+89, 70, 21, SPLIT_GROUPS.val, 'Import OBJ Groups into Blender Objects', do_split)
863                         SPLIT_MATERIALS = Draw.Toggle('Material', EVENT_REDRAW, ui_x+149, ui_y+89, 70, 21, SPLIT_MATERIALS.val, 'Import each material into a seperate mesh (Avoids > 16 per mesh error)', do_split)
864                         Draw.EndAlign()
865                         
866                         # Only used for user feedback
867                         KEEP_VERT_ORDER = Draw.Toggle('Keep Vert Order', EVENT_REDRAW, ui_x+229, ui_y+89, 110, 21, KEEP_VERT_ORDER.val, 'Keep vert and face order, disables split options, enable for morph targets', do_vertorder)
868                         
869                         Draw.Label('Options...', ui_x+9, ui_y+60, 211, 20)
870                         CLAMP_SIZE = Draw.Number('Clamp Scale: ', EVENT_NONE, ui_x+9, ui_y+39, 211, 21, CLAMP_SIZE.val, 0.0, 1000.0, 'Clamp the size to this maximum (Zero to Disable)')
871                         IMAGE_SEARCH = Draw.Toggle('Image Search', EVENT_NONE, ui_x+229, ui_y+39, 110, 21, IMAGE_SEARCH.val, 'Search subdirs for any assosiated images (Warning, may be slow)')
872                         Draw.BeginAlign()
873                         Draw.PushButton('Online Help', EVENT_REDRAW, ui_x+9, ui_y+9, 110, 21, 'Load the wiki page for this script', do_help)
874                         Draw.PushButton('Cancel', EVENT_EXIT, ui_x+119, ui_y+9, 110, 21, '', obj_ui_set_event)
875                         Draw.PushButton('Import', EVENT_IMPORT, ui_x+229, ui_y+9, 110, 21, 'Import with these settings', obj_ui_set_event)
876                         Draw.EndAlign()
877                         
878                 
879                 # hack so the toggle buttons redraw. this is not nice at all
880                 while GLOBALS['EVENT'] not in (EVENT_EXIT, EVENT_IMPORT):
881                         Draw.UIBlock(obj_ui)
882                 
883                 if GLOBALS['EVENT'] != EVENT_IMPORT:
884                         return
885                 
886         # END ALTERNATIVE UI *********************
887         
888         
889         
890         
891         
892         
893         
894         Window.WaitCursor(1)
895         
896         if BATCH_LOAD: # load the dir
897                 try:
898                         files= [ f for f in os.listdir(filepath) if f.lower().endswith('.obj') ]
899                 except:
900                         Window.WaitCursor(0)
901                         Draw.PupMenu('Error%t|Could not open path ' + filepath)
902                         return
903                 
904                 if not files:
905                         Window.WaitCursor(0)
906                         Draw.PupMenu('Error%t|No files at path ' + filepath)
907                         return
908                 
909                 for f in files:
910                         scn= bpy.data.scenes.new( stripExt(f) )
911                         scn.makeCurrent()
912                         
913                         load_obj(sys.join(filepath, f),\
914                           CLAMP_SIZE.val,\
915                           CREATE_FGONS.val,\
916                           CREATE_SMOOTH_GROUPS.val,\
917                           CREATE_EDGES.val,\
918                           SPLIT_OBJECTS.val,\
919                           SPLIT_GROUPS.val,\
920                           SPLIT_MATERIALS.val,\
921                           IMAGE_SEARCH.val,\
922                         )
923         
924         else: # Normal load
925                 load_obj(filepath,\
926                   CLAMP_SIZE.val,\
927                   CREATE_FGONS.val,\
928                   CREATE_SMOOTH_GROUPS.val,\
929                   CREATE_EDGES.val,\
930                   SPLIT_OBJECTS.val,\
931                   SPLIT_GROUPS.val,\
932                   SPLIT_MATERIALS.val,\
933                   IMAGE_SEARCH.val,\
934                 )
935         
936         Window.WaitCursor(0)
937
938
939 def load_obj_ui_batch(file):
940         load_obj_ui(file, True)
941
942 DEBUG= False
943
944 if __name__=='__main__' and not DEBUG:
945         if os and Window.GetKeyQualifiers() & Window.Qual.SHIFT:
946                 Window.FileSelector(load_obj_ui_batch, 'Import OBJ Dir', '')
947         else:
948                 Window.FileSelector(load_obj_ui, 'Import a Wavefront OBJ', '*.obj')
949
950
951 '''
952 # For testing compatibility
953 else:
954         # DEBUG ONLY
955         TIME= sys.time()
956         import os
957         print 'Searching for files'
958         os.system('find /fe/obj -iname "*.obj" > /tmp/temp3ds_list')
959         
960         print '...Done'
961         file= open('/tmp/temp3ds_list', 'rU')
962         lines= file.readlines()
963         file.close()
964
965         def between(v,a,b):
966                 if v <= max(a,b) and v >= min(a,b):
967                         return True             
968                 return False
969                 
970         for i, _obj in enumerate(lines):
971                 if between(i, 0,20):
972                         _obj= _obj[:-1]
973                         print 'Importing', _obj, '\nNUMBER', i, 'of', len(lines)
974                         _obj_file= _obj.split('/')[-1].split('\\')[-1]
975                         newScn= bpy.data.scenes.new(_obj_file)
976                         newScn.makeCurrent()
977                         load_obj(_obj, False)
978
979         print 'TOTAL TIME: %.6f' % (sys.time() - TIME)
980 '''
981 #load_obj('/test.obj')
982 #load_obj('/fe/obj/mba1.obj')