change blender python interface for classes not to ise __idname__ rather bl_idname...
[blender-staging.git] / release / scripts / io / import_obj.py
1
2 __author__= "Campbell Barton", "Jiri Hnidek", "Paolo Ciccone"
3 __url__= ['http://wiki.blender.org/index.php/Scripts/Manual/Import/wavefront_obj', 'blender.org', 'blenderartists.org']
4 __version__= "2.11"
5
6 __bpydoc__= """\
7 This script imports a Wavefront OBJ files to Blender.
8
9 Usage:
10 Run this script from "File->Import" menu and then load the desired OBJ file.
11 Note, This loads mesh objects and materials only, nurbs and curves are not supported.
12 """
13
14 # ***** BEGIN GPL LICENSE BLOCK *****
15 #
16 # Script copyright (C) Campbell J Barton 2007
17 #
18 # This program is free software; you can redistribute it and/or
19 # modify it under the terms of the GNU General Public License
20 # as published by the Free Software Foundation; either version 2
21 # of the License, or (at your option) any later version.
22 #
23 # This program is distributed in the hope that it will be useful,
24 # but WITHOUT ANY WARRANTY; without even the implied warranty of
25 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26 # GNU General Public License for more details.
27 #
28 # You should have received a copy of the GNU General Public License
29 # along with this program; if not, write to the Free Software Foundation,
30 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
31 #
32 # ***** END GPL LICENCE BLOCK *****
33 # --------------------------------------------------------------------------
34
35 import os
36 import time
37 import bpy
38 import Mathutils
39 import Geometry
40
41 # from Blender import Mesh, Draw, Window, Texture, Material, sys
42 # # import BPyMesh
43 # import BPyImage
44 # import BPyMessages
45
46 # try:          import os
47 # except:               os= False
48
49 # Generic path functions
50 def stripFile(path):
51         '''Return directory, where the file is'''
52         lastSlash= max(path.rfind('\\'), path.rfind('/'))
53         if lastSlash != -1:
54                 path= path[:lastSlash]
55         return '%s%s' % (path, os.sep)
56 #       return '%s%s' % (path, sys.sep)
57
58 def stripPath(path):
59         '''Strips the slashes from the back of a string'''
60         return path.split('/')[-1].split('\\')[-1]
61
62 def stripExt(name): # name is a string
63         '''Strips the prefix off the name before writing'''
64         index= name.rfind('.')
65         if index != -1:
66                 return name[ : index ]
67         else:
68                 return name
69 # end path funcs
70
71 def unpack_list(list_of_tuples):
72         l = []
73         for t in list_of_tuples:
74                 l.extend(t)
75         return l
76
77 # same as above except that it adds 0 for triangle faces
78 def unpack_face_list(list_of_tuples):
79         l = []
80         for t in list_of_tuples:
81                 face = [i for i in t]
82
83                 if len(face) != 3 and len(face) != 4:
84                         raise RuntimeError("{0} vertices in face.".format(len(face)))
85                 
86                 # rotate indices if the 4th is 0
87                 if len(face) == 4 and face[3] == 0:
88                         face = [face[3], face[0], face[1], face[2]]
89
90                 if len(face) == 3:
91                         face.append(0)
92                         
93                 l.extend(face)
94
95         return l
96
97 def BPyMesh_ngon(from_data, indices, PREF_FIX_LOOPS= True):
98         '''
99         Takes a polyline of indices (fgon)
100         and returns a list of face indicie lists.
101         Designed to be used for importers that need indices for an fgon to create from existing verts.
102         
103         from_data: either a mesh, or a list/tuple of vectors.
104         indices: a list of indicies to use this list is the ordered closed polyline to fill, and can be a subset of the data given.
105         PREF_FIX_LOOPS: If this is enabled polylines that use loops to make multiple polylines are delt with correctly.
106         '''
107         
108         if not set: # Need sets for this, otherwise do a normal fill.
109                 PREF_FIX_LOOPS= False 
110         
111         Vector= Mathutils.Vector
112         if not indices:
113                 return []
114         
115         #       return []
116         def rvec(co): return round(co.x, 6), round(co.y, 6), round(co.z, 6)
117         def mlen(co): return abs(co[0])+abs(co[1])+abs(co[2]) # manhatten length of a vector, faster then length
118         
119         def vert_treplet(v, i):
120                 return v, rvec(v), i, mlen(v)
121         
122         def ed_key_mlen(v1, v2):
123                 if v1[3] > v2[3]:
124                         return v2[1], v1[1]
125                 else:
126                         return v1[1], v2[1]
127         
128         
129         if not PREF_FIX_LOOPS:
130                 '''
131                 Normal single concave loop filling
132                 '''
133                 if type(from_data) in (tuple, list):
134                         verts= [Vector(from_data[i]) for ii, i in enumerate(indices)]
135                 else:
136                         verts= [from_data.verts[i].co for ii, i in enumerate(indices)]
137                 
138                 for i in range(len(verts)-1, 0, -1): # same as reversed(xrange(1, len(verts))):
139                         if verts[i][1]==verts[i-1][0]:
140                                 verts.pop(i-1)
141                 
142                 fill= Geometry.PolyFill([verts])
143                 
144         else:
145                 '''
146                 Seperate this loop into multiple loops be finding edges that are used twice
147                 This is used by lightwave LWO files a lot
148                 '''
149                 
150                 if type(from_data) in (tuple, list):
151                         verts= [vert_treplet(Vector(from_data[i]), ii) for ii, i in enumerate(indices)]
152                 else:
153                         verts= [vert_treplet(from_data.verts[i].co, ii) for ii, i in enumerate(indices)]
154                         
155                 edges= [(i, i-1) for i in range(len(verts))]
156                 if edges:
157                         edges[0]= (0,len(verts)-1)
158                 
159                 if not verts:
160                         return []
161                 
162                 
163                 edges_used= set()
164                 edges_doubles= set()
165                 # We need to check if any edges are used twice location based.
166                 for ed in edges:
167                         edkey= ed_key_mlen(verts[ed[0]], verts[ed[1]])
168                         if edkey in edges_used:
169                                 edges_doubles.add(edkey)
170                         else:
171                                 edges_used.add(edkey)
172                 
173                 # Store a list of unconnected loop segments split by double edges.
174                 # will join later
175                 loop_segments= [] 
176                 
177                 v_prev= verts[0]
178                 context_loop= [v_prev]
179                 loop_segments= [context_loop]
180                 
181                 for v in verts:
182                         if v!=v_prev:
183                                 # Are we crossing an edge we removed?
184                                 if ed_key_mlen(v, v_prev) in edges_doubles:
185                                         context_loop= [v]
186                                         loop_segments.append(context_loop)
187                                 else:
188                                         if context_loop and context_loop[-1][1]==v[1]:
189                                                 #raise "as"
190                                                 pass
191                                         else:
192                                                 context_loop.append(v)
193                                 
194                                 v_prev= v
195                 # Now join loop segments
196                 
197                 def join_seg(s1,s2):
198                         if s2[-1][1]==s1[0][1]: # 
199                                 s1,s2= s2,s1
200                         elif s1[-1][1]==s2[0][1]:
201                                 pass
202                         else:
203                                 return False
204                         
205                         # If were stuill here s1 and s2 are 2 segments in the same polyline
206                         s1.pop() # remove the last vert from s1
207                         s1.extend(s2) # add segment 2 to segment 1
208                         
209                         if s1[0][1]==s1[-1][1]: # remove endpoints double
210                                 s1.pop()
211                         
212                         s2[:]= [] # Empty this segment s2 so we dont use it again.
213                         return True
214                 
215                 joining_segments= True
216                 while joining_segments:
217                         joining_segments= False
218                         segcount= len(loop_segments)
219                         
220                         for j in range(segcount-1, -1, -1): #reversed(range(segcount)):
221                                 seg_j= loop_segments[j]
222                                 if seg_j:
223                                         for k in range(j-1, -1, -1): # reversed(range(j)):
224                                                 if not seg_j:
225                                                         break
226                                                 seg_k= loop_segments[k]
227                                                 
228                                                 if seg_k and join_seg(seg_j, seg_k):
229                                                         joining_segments= True
230                 
231                 loop_list= loop_segments
232                 
233                 for verts in loop_list:
234                         while verts and verts[0][1]==verts[-1][1]:
235                                 verts.pop()
236                 
237                 loop_list= [verts for verts in loop_list if len(verts)>2]
238                 # DONE DEALING WITH LOOP FIXING
239                 
240                 
241                 # vert mapping
242                 vert_map= [None]*len(indices)
243                 ii=0
244                 for verts in loop_list:
245                         if len(verts)>2:
246                                 for i, vert in enumerate(verts):
247                                         vert_map[i+ii]= vert[2]
248                                 ii+=len(verts)
249                 
250                 fill= Geometry.PolyFill([ [v[0] for v in loop] for loop in loop_list ])
251                 #draw_loops(loop_list)
252                 #raise 'done loop'
253                 # map to original indicies
254                 fill= [[vert_map[i] for i in reversed(f)] for f in fill]
255         
256         
257         if not fill:
258                 print('Warning Cannot scanfill, fallback on a triangle fan.')
259                 fill= [ [0, i-1, i] for i in range(2, len(indices)) ]
260         else:
261                 # Use real scanfill.
262                 # See if its flipped the wrong way.
263                 flip= None
264                 for fi in fill:
265                         if flip != None:
266                                 break
267                         for i, vi in enumerate(fi):
268                                 if vi==0 and fi[i-1]==1:
269                                         flip= False
270                                         break
271                                 elif vi==1 and fi[i-1]==0:
272                                         flip= True
273                                         break
274                 
275                 if not flip:
276                         for i, fi in enumerate(fill):
277                                 fill[i]= tuple([ii for ii in reversed(fi)])             
278         
279         return fill
280
281 def line_value(line_split):
282         '''
283         Returns 1 string represneting the value for this line
284         None will be returned if theres only 1 word
285         '''
286         length= len(line_split)
287         if length == 1:
288                 return None
289         
290         elif length == 2:
291                 return line_split[1]
292         
293         elif length > 2:
294                 return ' '.join( line_split[1:] )
295
296 # limited replacement for BPyImage.comprehensiveImageLoad
297 def load_image(imagepath, dirname):
298
299         if os.path.exists(imagepath):
300                 return bpy.data.add_image(imagepath)
301
302         variants = [os.path.join(dirname, imagepath), os.path.join(dirname, os.path.basename(imagepath))]
303
304         for path in variants:
305                 if os.path.exists(path):
306                         return bpy.data.add_image(path)
307                 else:
308                         print(path, "doesn't exist")
309
310         # TODO comprehensiveImageLoad also searched in bpy.config.textureDir
311         return None
312
313 def obj_image_load(imagepath, DIR, IMAGE_SEARCH):
314
315         if '_' in imagepath:
316                 image= load_image(imagepath.replace('_', ' '), DIR)
317                 if image: return image
318
319         return load_image(imagepath, DIR)
320
321 # def obj_image_load(imagepath, DIR, IMAGE_SEARCH):
322 #       '''
323 #       Mainly uses comprehensiveImageLoad
324 #       but tries to replace '_' with ' ' for Max's exporter replaces spaces with underscores.
325 #       '''
326         
327 #       if '_' in imagepath:
328 #               image= BPyImage.comprehensiveImageLoad(imagepath, DIR, PLACE_HOLDER= False, RECURSIVE= IMAGE_SEARCH)
329 #               if image: return image
330 #               # Did the exporter rename the image?
331 #               image= BPyImage.comprehensiveImageLoad(imagepath.replace('_', ' '), DIR, PLACE_HOLDER= False, RECURSIVE= IMAGE_SEARCH)
332 #               if image: return image
333         
334 #       # Return an image, placeholder if it dosnt exist
335 #       image= BPyImage.comprehensiveImageLoad(imagepath, DIR, PLACE_HOLDER= True, RECURSIVE= IMAGE_SEARCH)
336 #       return image
337         
338
339 def create_materials(filepath, material_libs, unique_materials, unique_material_images, IMAGE_SEARCH):
340         '''
341         Create all the used materials in this obj,
342         assign colors and images to the materials from all referenced material libs
343         '''
344         DIR= stripFile(filepath)
345         
346         #==================================================================================#
347         # This function sets textures defined in .mtl file                                 #
348         #==================================================================================#
349         def load_material_image(blender_material, context_material_name, imagepath, type):
350
351                 texture= bpy.data.add_texture(type)
352                 texture.type= 'IMAGE'
353 #               texture= bpy.data.textures.new(type)
354 #               texture.setType('Image')
355                 
356                 # Absolute path - c:\.. etc would work here
357                 image= obj_image_load(imagepath, DIR, IMAGE_SEARCH)
358                 has_data = image.has_data if image else False
359
360                 if image:
361                         texture.image = image
362                 
363                 # Adds textures for materials (rendering)
364                 if type == 'Kd':
365                         if has_data and image.depth == 32:
366                                 # Image has alpha
367
368                                 # XXX bitmask won't work?
369                                 blender_material.add_texture(texture, "UV", ("COLOR", "ALPHA"))
370                                 texture.mipmap = True
371                                 texture.interpolation = True
372                                 texture.use_alpha = True
373                                 blender_material.z_transparency = True
374                                 blender_material.alpha = 0.0
375
376 #                               blender_material.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL | Texture.MapTo.ALPHA)
377 #                               texture.setImageFlags('MipMap', 'InterPol', 'UseAlpha')
378 #                               blender_material.mode |= Material.Modes.ZTRANSP
379 #                               blender_material.alpha = 0.0
380                         else:
381                                 blender_material.add_texture(texture, "UV", "COLOR")
382 #                               blender_material.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL)
383                                 
384                         # adds textures to faces (Textured/Alt-Z mode)
385                         # Only apply the diffuse texture to the face if the image has not been set with the inline usemat func.
386                         unique_material_images[context_material_name]= image, has_data # set the texface image
387                 
388                 elif type == 'Ka':
389                         blender_material.add_texture(texture, "UV", "AMBIENT")
390 #                       blender_material.setTexture(1, texture, Texture.TexCo.UV, Texture.MapTo.CMIR) # TODO- Add AMB to BPY API
391                         
392                 elif type == 'Ks':
393                         blender_material.add_texture(texture, "UV", "SPECULARITY")
394 #                       blender_material.setTexture(2, texture, Texture.TexCo.UV, Texture.MapTo.SPEC)
395                 
396                 elif type == 'Bump':
397                         blender_material.add_texture(texture, "UV", "NORMAL")
398 #                       blender_material.setTexture(3, texture, Texture.TexCo.UV, Texture.MapTo.NOR)            
399                 elif type == 'D':
400                         blender_material.add_texture(texture, "UV", "ALPHA")
401                         blender_material.z_transparency = True
402                         blender_material.alpha = 0.0
403 #                       blender_material.setTexture(4, texture, Texture.TexCo.UV, Texture.MapTo.ALPHA)                          
404 #                       blender_material.mode |= Material.Modes.ZTRANSP
405 #                       blender_material.alpha = 0.0
406                         # Todo, unset deffuse material alpha if it has an alpha channel
407                         
408                 elif type == 'refl':
409                         blender_material.add_texture(texture, "UV", "REFLECTION")
410 #                       blender_material.setTexture(5, texture, Texture.TexCo.UV, Texture.MapTo.REF)
411         
412         
413         # Add an MTL with the same name as the obj if no MTLs are spesified.
414         temp_mtl= stripExt(stripPath(filepath))+ '.mtl'
415
416         if os.path.exists(DIR + temp_mtl) and temp_mtl not in material_libs:
417 #       if sys.exists(DIR + temp_mtl) and temp_mtl not in material_libs:
418                 material_libs.append( temp_mtl )
419         del temp_mtl
420         
421         #Create new materials
422         for name in unique_materials: # .keys()
423                 if name != None:
424                         unique_materials[name]= bpy.data.add_material(name)
425 #                       unique_materials[name]= bpy.data.materials.new(name)
426                         unique_material_images[name]= None, False # assign None to all material images to start with, add to later.
427                 
428         unique_materials[None]= None
429         unique_material_images[None]= None, False
430         
431         for libname in material_libs:
432                 mtlpath= DIR + libname
433                 if not os.path.exists(mtlpath):
434 #               if not sys.exists(mtlpath):
435                         #print '\tError Missing MTL: "%s"' % mtlpath
436                         pass
437                 else:
438                         #print '\t\tloading mtl: "%s"' % mtlpath
439                         context_material= None
440                         mtl= open(mtlpath, 'rU')
441                         for line in mtl: #.xreadlines():
442                                 if line.startswith('newmtl'):
443                                         context_material_name= line_value(line.split())
444                                         if context_material_name in unique_materials:
445                                                 context_material = unique_materials[ context_material_name ]
446                                         else:
447                                                 context_material = None
448                                 
449                                 elif context_material:
450                                         # we need to make a material to assign properties to it.
451                                         line_split= line.split()
452                                         line_lower= line.lower().lstrip()
453                                         if line_lower.startswith('ka'):
454                                                 context_material.mirror_color = (float(line_split[1]), float(line_split[2]), float(line_split[3]))
455 #                                               context_material.setMirCol((float(line_split[1]), float(line_split[2]), float(line_split[3])))
456                                         elif line_lower.startswith('kd'):
457                                                 context_material.diffuse_color = (float(line_split[1]), float(line_split[2]), float(line_split[3]))
458 #                                               context_material.setRGBCol((float(line_split[1]), float(line_split[2]), float(line_split[3])))
459                                         elif line_lower.startswith('ks'):
460                                                 context_material.specular_color = (float(line_split[1]), float(line_split[2]), float(line_split[3]))
461 #                                               context_material.setSpecCol((float(line_split[1]), float(line_split[2]), float(line_split[3])))
462                                         elif line_lower.startswith('ns'):
463                                                 context_material.specular_hardness = int((float(line_split[1])*0.51))
464 #                                               context_material.setHardness( int((float(line_split[1])*0.51)) )
465                                         elif line_lower.startswith('ni'): # Refraction index
466                                                 context_material.ior = max(1, min(float(line_split[1]), 3))
467 #                                               context_material.setIOR( max(1, min(float(line_split[1]), 3))) # Between 1 and 3
468                                         elif line_lower.startswith('d') or line_lower.startswith('tr'):
469                                                 context_material.alpha = float(line_split[1])
470 #                                               context_material.setAlpha(float(line_split[1]))
471                                         elif line_lower.startswith('map_ka'):
472                                                 img_filepath= line_value(line.split())
473                                                 if img_filepath:
474                                                         load_material_image(context_material, context_material_name, img_filepath, 'Ka')
475                                         elif line_lower.startswith('map_ks'):
476                                                 img_filepath= line_value(line.split())
477                                                 if img_filepath:
478                                                         load_material_image(context_material, context_material_name, img_filepath, 'Ks')
479                                         elif line_lower.startswith('map_kd'):
480                                                 img_filepath= line_value(line.split())
481                                                 if img_filepath:
482                                                         load_material_image(context_material, context_material_name, img_filepath, 'Kd')
483                                         elif line_lower.startswith('map_bump'):
484                                                 img_filepath= line_value(line.split())
485                                                 if img_filepath:
486                                                         load_material_image(context_material, context_material_name, img_filepath, 'Bump')
487                                         elif line_lower.startswith('map_d') or line_lower.startswith('map_tr'): # Alpha map - Dissolve
488                                                 img_filepath= line_value(line.split())
489                                                 if img_filepath:
490                                                         load_material_image(context_material, context_material_name, img_filepath, 'D')
491                                         
492                                         elif line_lower.startswith('refl'): # Reflectionmap
493                                                 img_filepath= line_value(line.split())
494                                                 if img_filepath:
495                                                         load_material_image(context_material, context_material_name, img_filepath, 'refl')
496                         mtl.close()
497
498
499
500         
501 def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP, SPLIT_MATERIALS):
502         '''
503         Takes vert_loc and faces, and seperates into multiple sets of 
504         (verts_loc, faces, unique_materials, dataname)
505         This is done so objects do not overload the 16 material limit.
506         '''
507         
508         filename = stripExt(stripPath(filepath))
509         
510         if not SPLIT_OB_OR_GROUP and not SPLIT_MATERIALS:
511                 # use the filename for the object name since we arnt chopping up the mesh.
512                 return [(verts_loc, faces, unique_materials, filename)]
513         
514         
515         def key_to_name(key):
516                 # if the key is a tuple, join it to make a string
517                 if type(key) == tuple:
518                         return '%s_%s' % key
519                 elif not key:
520                         return filename # assume its a string. make sure this is true if the splitting code is changed
521                 else:
522                         return key
523         
524         # Return a key that makes the faces unique.
525         if SPLIT_OB_OR_GROUP and not SPLIT_MATERIALS:
526                 def face_key(face):
527                         return face[4] # object
528         
529         elif not SPLIT_OB_OR_GROUP and SPLIT_MATERIALS:
530                 def face_key(face):
531                         return face[2] # material
532         
533         else: # Both
534                 def face_key(face):
535                         return face[4], face[2] # object,material               
536         
537         
538         face_split_dict= {}
539         
540         oldkey= -1 # initialize to a value that will never match the key
541         
542         for face in faces:
543                 
544                 key= face_key(face)
545                 
546                 if oldkey != key:
547                         # Check the key has changed.
548                         try:
549                                 verts_split, faces_split, unique_materials_split, vert_remap= face_split_dict[key]
550                         except KeyError:
551                                 faces_split= []
552                                 verts_split= []
553                                 unique_materials_split= {}
554                                 vert_remap= [-1]*len(verts_loc)
555                                 
556                                 face_split_dict[key]= (verts_split, faces_split, unique_materials_split, vert_remap)
557                         
558                         oldkey= key
559                         
560                 face_vert_loc_indicies= face[0]
561                 
562                 # Remap verts to new vert list and add where needed
563                 for enum, i in enumerate(face_vert_loc_indicies):
564                         if vert_remap[i] == -1:
565                                 new_index= len(verts_split)
566                                 vert_remap[i]= new_index # set the new remapped index so we only add once and can reference next time.
567                                 face_vert_loc_indicies[enum] = new_index # remap to the local index
568                                 verts_split.append( verts_loc[i] ) # add the vert to the local verts 
569                                 
570                         else:
571                                 face_vert_loc_indicies[enum] = vert_remap[i] # remap to the local index
572                         
573                         matname= face[2]
574                         if matname and matname not in unique_materials_split:
575                                 unique_materials_split[matname] = unique_materials[matname]
576                 
577                 faces_split.append(face)
578         
579         
580         # remove one of the itemas and reorder
581         return [(value[0], value[1], value[2], key_to_name(key)) for key, value in list(face_split_dict.items())]
582
583
584 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, vertex_groups, dataname):
585         '''
586         Takes all the data gathered and generates a mesh, adding the new object to new_objects
587         deals with fgons, sharp edges and assigning materials
588         '''
589         if not has_ngons:
590                 CREATE_FGONS= False
591         
592         if unique_smooth_groups:
593                 sharp_edges= {}
594                 smooth_group_users= dict([ (context_smooth_group, {}) for context_smooth_group in list(unique_smooth_groups.keys()) ])
595                 context_smooth_group_old= -1
596         
597         # Split fgons into tri's
598         fgon_edges= {} # Used for storing fgon keys
599         if CREATE_EDGES:
600                 edges= []
601         
602         context_object= None
603         
604         # reverse loop through face indicies
605         for f_idx in range(len(faces)-1, -1, -1):
606                 
607                 face_vert_loc_indicies,\
608                 face_vert_tex_indicies,\
609                 context_material,\
610                 context_smooth_group,\
611                 context_object= faces[f_idx]
612                 
613                 len_face_vert_loc_indicies = len(face_vert_loc_indicies)
614                 
615                 if len_face_vert_loc_indicies==1:
616                         faces.pop(f_idx)# cant add single vert faces
617                 
618                 elif not face_vert_tex_indicies or len_face_vert_loc_indicies == 2: # faces that have no texture coords are lines
619                         if CREATE_EDGES:
620                                 # generators are better in python 2.4+ but can't be used in 2.3
621                                 # edges.extend( (face_vert_loc_indicies[i], face_vert_loc_indicies[i+1]) for i in xrange(len_face_vert_loc_indicies-1) )
622                                 edges.extend( [(face_vert_loc_indicies[i], face_vert_loc_indicies[i+1]) for i in range(len_face_vert_loc_indicies-1)] )
623
624                         faces.pop(f_idx)
625                 else:
626                         
627                         # Smooth Group
628                         if unique_smooth_groups and context_smooth_group:
629                                 # Is a part of of a smooth group and is a face
630                                 if context_smooth_group_old is not context_smooth_group:
631                                         edge_dict= smooth_group_users[context_smooth_group]
632                                         context_smooth_group_old= context_smooth_group
633                                 
634                                 for i in range(len_face_vert_loc_indicies):
635                                         i1= face_vert_loc_indicies[i]
636                                         i2= face_vert_loc_indicies[i-1]
637                                         if i1>i2: i1,i2= i2,i1
638                                         
639                                         try:
640                                                 edge_dict[i1,i2]+= 1
641                                         except KeyError:
642                                                 edge_dict[i1,i2]=  1
643                         
644                         # FGons into triangles
645                         if has_ngons and len_face_vert_loc_indicies > 4:
646                                 
647                                 ngon_face_indices= BPyMesh_ngon(verts_loc, face_vert_loc_indicies)
648                                 faces.extend(\
649                                 [(\
650                                 [face_vert_loc_indicies[ngon[0]], face_vert_loc_indicies[ngon[1]], face_vert_loc_indicies[ngon[2]] ],\
651                                 [face_vert_tex_indicies[ngon[0]], face_vert_tex_indicies[ngon[1]], face_vert_tex_indicies[ngon[2]] ],\
652                                 context_material,\
653                                 context_smooth_group,\
654                                 context_object)\
655                                 for ngon in ngon_face_indices]\
656                                 )
657                                 
658                                 # edges to make fgons
659                                 if CREATE_FGONS:
660                                         edge_users= {}
661                                         for ngon in ngon_face_indices:
662                                                 for i in (0,1,2):
663                                                         i1= face_vert_loc_indicies[ngon[i  ]]
664                                                         i2= face_vert_loc_indicies[ngon[i-1]]
665                                                         if i1>i2: i1,i2= i2,i1
666                                                         
667                                                         try:
668                                                                 edge_users[i1,i2]+=1
669                                                         except KeyError:
670                                                                 edge_users[i1,i2]= 1
671                                         
672                                         for key, users in edge_users.items():
673                                                 if users>1:
674                                                         fgon_edges[key]= None
675                                 
676                                 # remove all after 3, means we dont have to pop this one.
677                                 faces.pop(f_idx)
678                 
679                 
680         # Build sharp edges
681         if unique_smooth_groups:
682                 for edge_dict in list(smooth_group_users.values()):
683                         for key, users in list(edge_dict.items()):
684                                 if users==1: # This edge is on the boundry of a group
685                                         sharp_edges[key]= None
686         
687         
688         # map the material names to an index
689         material_mapping= dict([(name, i) for i, name in enumerate(unique_materials)]) # enumerate over unique_materials keys()
690         
691         materials= [None] * len(unique_materials)
692         
693         for name, index in list(material_mapping.items()):
694                 materials[index]= unique_materials[name]
695
696         me= bpy.data.add_mesh(dataname)
697 #       me= bpy.data.meshes.new(dataname)
698
699         # make sure the list isnt too big
700         for material in materials[0:16]:
701                 me.add_material(material)
702 #       me.materials= materials[0:16] # make sure the list isnt too big.
703         #me.verts.extend([(0,0,0)]) # dummy vert
704
705         me.add_geometry(len(verts_loc), 0, len(faces))
706
707         # verts_loc is a list of (x, y, z) tuples
708         me.verts.foreach_set("co", unpack_list(verts_loc))
709 #       me.verts.extend(verts_loc)
710
711         # faces is a list of (vert_indices, texco_indices, ...) tuples
712         # XXX faces should contain either 3 or 4 verts
713         # XXX no check for valid face indices
714         me.faces.foreach_set("verts_raw", unpack_face_list([f[0] for f in faces]))
715 #       face_mapping= me.faces.extend([f[0] for f in faces], indexList=True)
716         
717         if verts_tex and me.faces:
718                 me.add_uv_texture()
719 #               me.faceUV= 1
720                 # TEXMODE= Mesh.FaceModes['TEX']
721         
722         context_material_old= -1 # avoid a dict lookup
723         mat= 0 # rare case it may be un-initialized.
724         me_faces= me.faces
725 #       ALPHA= Mesh.FaceTranspModes.ALPHA
726         
727         for i, face in enumerate(faces):
728                 if len(face[0]) < 2:
729                         pass #raise "bad face"
730                 elif len(face[0])==2:
731                         if CREATE_EDGES:
732                                 edges.append(face[0])
733                 else:
734 #                       face_index_map= face_mapping[i]
735
736                         # since we use foreach_set to add faces, all of them are added
737                         if 1:
738 #                       if face_index_map!=None: # None means the face wasnt added
739
740                                 blender_face = me.faces[i]
741 #                               blender_face= me_faces[face_index_map]
742                                 
743                                 face_vert_loc_indicies,\
744                                 face_vert_tex_indicies,\
745                                 context_material,\
746                                 context_smooth_group,\
747                                 context_object= face
748                                 
749                                 
750                                 
751                                 if context_smooth_group:
752                                         blender_face.smooth= True
753                                 
754                                 if context_material:
755                                         if context_material_old is not context_material:
756                                                 mat= material_mapping[context_material]
757                                                 if mat>15:
758                                                         mat= 15
759                                                 context_material_old= context_material
760
761                                         blender_face.material_index= mat
762 #                                       blender_face.mat= mat
763                                 
764                                 
765                                 if verts_tex:
766
767                                         blender_tface= me.uv_textures[0].data[i]
768
769                                         if context_material:
770                                                 image, has_data= unique_material_images[context_material]
771                                                 if image: # Can be none if the material dosnt have an image.
772                                                         blender_tface.image= image
773 #                                                       blender_face.image= image
774                                                         if has_data:
775 #                                                       if has_data and image.depth == 32:
776                                                                 blender_tface.transp = 'ALPHA'
777 #                                                               blender_face.transp |= ALPHA
778                                         
779                                         # BUG - Evil eekadoodle problem where faces that have vert index 0 location at 3 or 4 are shuffled.
780                                         if len(face_vert_loc_indicies)==4:
781                                                 if face_vert_loc_indicies[2]==0 or face_vert_loc_indicies[3]==0:
782                                                         face_vert_tex_indicies= face_vert_tex_indicies[2], face_vert_tex_indicies[3], face_vert_tex_indicies[0], face_vert_tex_indicies[1]
783                                         else: # length of 3
784                                                 if face_vert_loc_indicies[2]==0:
785                                                         face_vert_tex_indicies= face_vert_tex_indicies[1], face_vert_tex_indicies[2], face_vert_tex_indicies[0]
786                                         # END EEEKADOODLE FIX
787                                         
788                                         # assign material, uv's and image
789                                         blender_tface.uv1= verts_tex[face_vert_tex_indicies[0]]
790                                         blender_tface.uv2= verts_tex[face_vert_tex_indicies[1]]
791                                         blender_tface.uv3= verts_tex[face_vert_tex_indicies[2]]
792
793                                         if blender_face.verts[3] != 0:
794                                                 blender_tface.uv4= verts_tex[face_vert_tex_indicies[3]]
795
796 #                                       for ii, uv in enumerate(blender_face.uv):
797 #                                               uv.x, uv.y=  verts_tex[face_vert_tex_indicies[ii]]
798         del me_faces
799 #       del ALPHA
800
801         if CREATE_EDGES:
802
803                 me.add_geometry(0, len(edges), 0)
804
805                 # edges should be a list of (a, b) tuples
806                 me.edges.foreach_set("verts", unpack_list(edges))
807 #               me_edges.extend( edges )
808         
809 #       del me_edges
810         
811         # Add edge faces.
812 #       me_edges= me.edges
813
814         def edges_match(e1, e2):
815                 return (e1[0] == e2[0] and e1[1] == e2[1]) or (e1[0] == e2[1] and e1[1] == e2[0])
816
817         # XXX slow
818 #       if CREATE_FGONS and fgon_edges:
819 #               for fgon_edge in fgon_edges.keys():
820 #                       for ed in me.edges:
821 #                               if edges_match(fgon_edge, ed.verts):
822 #                                       ed.fgon = True
823
824 #       if CREATE_FGONS and fgon_edges:
825 #               FGON= Mesh.EdgeFlags.FGON
826 #               for ed in me.findEdges( fgon_edges.keys() ):
827 #                       if ed!=None:
828 #                               me_edges[ed].flag |= FGON
829 #               del FGON
830
831         # XXX slow
832 #       if unique_smooth_groups and sharp_edges:
833 #               for sharp_edge in sharp_edges.keys():
834 #                       for ed in me.edges:
835 #                               if edges_match(sharp_edge, ed.verts):
836 #                                       ed.sharp = True
837
838 #       if unique_smooth_groups and sharp_edges:
839 #               SHARP= Mesh.EdgeFlags.SHARP
840 #               for ed in me.findEdges( sharp_edges.keys() ):
841 #                       if ed!=None:
842 #                               me_edges[ed].flag |= SHARP
843 #               del SHARP
844
845         me.update()
846 #       me.calcNormals()
847         
848         ob= bpy.data.add_object("MESH", "Mesh")
849         ob.data= me
850         scn.add_object(ob)
851 #       ob= scn.objects.new(me)
852         new_objects.append(ob)
853
854         # Create the vertex groups. No need to have the flag passed here since we test for the 
855         # content of the vertex_groups. If the user selects to NOT have vertex groups saved then
856         # the following test will never run
857         for group_name, group_indicies in vertex_groups.items():
858                 group= ob.add_vertex_group(group_name)
859 #               me.addVertGroup(group_name)
860                 for vertex_index in group_indicies:
861                         ob.add_vertex_to_group(vertex_index, group, 1.0, 'REPLACE')
862 #               me.assignVertsToGroup(group_name, group_indicies, 1.00, Mesh.AssignModes.REPLACE)
863
864
865 def create_nurbs(scn, context_nurbs, vert_loc, new_objects):
866         '''
867         Add nurbs object to blender, only support one type at the moment
868         '''
869         deg = context_nurbs.get('deg', (3,))
870         curv_range = context_nurbs.get('curv_range', None)
871         curv_idx = context_nurbs.get('curv_idx', [])
872         parm_u = context_nurbs.get('parm_u', [])
873         parm_v = context_nurbs.get('parm_v', [])
874         name = context_nurbs.get('name', 'ObjNurb')
875         cstype = context_nurbs.get('cstype', None)
876         
877         if cstype == None:
878                 print('\tWarning, cstype not found')
879                 return
880         if cstype != 'bspline':
881                 print('\tWarning, cstype is not supported (only bspline)')
882                 return
883         if not curv_idx:
884                 print('\tWarning, curv argument empty or not set')
885                 return
886         if len(deg) > 1 or parm_v:
887                 print('\tWarning, surfaces not supported')
888                 return
889         
890         cu = bpy.data.curves.new(name, 'Curve')
891         cu.flag |= 1 # 3D curve
892         
893         nu = None
894         for pt in curv_idx:
895                 
896                 pt = vert_loc[pt]
897                 pt = (pt[0], pt[1], pt[2], 1.0)
898                 
899                 if nu == None:
900                         nu = cu.appendNurb(pt)
901                 else:
902                         nu.append(pt)
903         
904         nu.orderU = deg[0]+1
905         
906         # get for endpoint flag from the weighting
907         if curv_range and len(parm_u) > deg[0]+1:
908                 do_endpoints = True
909                 for i in range(deg[0]+1):
910                         
911                         if abs(parm_u[i]-curv_range[0]) > 0.0001:
912                                 do_endpoints = False
913                                 break
914                         
915                         if abs(parm_u[-(i+1)]-curv_range[1]) > 0.0001:
916                                 do_endpoints = False
917                                 break
918                         
919         else:
920                 do_endpoints = False
921         
922         if do_endpoints:
923                 nu.flagU |= 2
924         
925         
926         # close
927         '''
928         do_closed = False
929         if len(parm_u) > deg[0]+1:
930                 for i in xrange(deg[0]+1):
931                         #print curv_idx[i], curv_idx[-(i+1)]
932                         
933                         if curv_idx[i]==curv_idx[-(i+1)]:
934                                 do_closed = True
935                                 break
936         
937         if do_closed:
938                 nu.flagU |= 1
939         '''
940         
941         ob = scn.objects.new(cu)
942         new_objects.append(ob)
943
944
945 def strip_slash(line_split):
946         if line_split[-1][-1]== '\\':
947                 if len(line_split[-1])==1:
948                         line_split.pop() # remove the \ item
949                 else:
950                         line_split[-1]= line_split[-1][:-1] # remove the \ from the end last number
951                 return True
952         return False
953
954
955
956 def get_float_func(filepath):
957         '''
958         find the float function for this obj file
959         - weather to replace commas or not
960         '''
961         file= open(filepath, 'rU')
962         for line in file: #.xreadlines():
963                 line = line.lstrip()
964                 if line.startswith('v'): # vn vt v 
965                         if ',' in line:
966                                 return lambda f: float(f.replace(',', '.'))
967                         elif '.' in line:
968                                 return float
969         
970         # incase all vert values were ints 
971         return float
972
973 def load_obj(filepath,
974                          context,
975                          CLAMP_SIZE= 0.0, 
976                          CREATE_FGONS= True, 
977                          CREATE_SMOOTH_GROUPS= True, 
978                          CREATE_EDGES= True, 
979                          SPLIT_OBJECTS= True, 
980                          SPLIT_GROUPS= True, 
981                          SPLIT_MATERIALS= True, 
982                          ROTATE_X90= True, 
983                          IMAGE_SEARCH=True,
984                          POLYGROUPS=False):
985         '''
986         Called by the user interface or another script.
987         load_obj(path) - should give acceptable results.
988         This function passes the file and sends the data off
989                 to be split into objects and then converted into mesh objects
990         '''
991         print('\nimporting obj "%s"' % filepath)
992         
993         if SPLIT_OBJECTS or SPLIT_GROUPS or SPLIT_MATERIALS:
994                 POLYGROUPS = False
995
996         time_main= time.time()
997 #       time_main= sys.time()
998         
999         verts_loc= []
1000         verts_tex= []
1001         faces= [] # tuples of the faces
1002         material_libs= [] # filanems to material libs this uses
1003         vertex_groups = {} # when POLYGROUPS is true
1004         
1005         # Get the string to float conversion func for this file- is 'float' for almost all files.
1006         float_func= get_float_func(filepath)
1007         
1008         # Context variables
1009         context_material= None
1010         context_smooth_group= None
1011         context_object= None
1012         context_vgroup = None
1013         
1014         # Nurbs
1015         context_nurbs = {}
1016         nurbs = []
1017         context_parm = '' # used by nurbs too but could be used elsewhere
1018
1019         has_ngons= False
1020         # has_smoothgroups= False - is explicit with len(unique_smooth_groups) being > 0
1021         
1022         # Until we can use sets
1023         unique_materials= {}
1024         unique_material_images= {}
1025         unique_smooth_groups= {}
1026         # unique_obects= {} - no use for this variable since the objects are stored in the face.
1027         
1028         # when there are faces that end with \
1029         # it means they are multiline- 
1030         # since we use xreadline we cant skip to the next line
1031         # so we need to know weather 
1032         context_multi_line= ''
1033         
1034         print('\tparsing obj file "%s"...' % filepath)
1035         time_sub= time.time()
1036 #       time_sub= sys.time()
1037
1038         file= open(filepath, 'rU')
1039         for line in file: #.xreadlines():
1040                 line = line.lstrip() # rare cases there is white space at the start of the line
1041                 
1042                 if line.startswith('v '):
1043                         line_split= line.split()
1044                         # rotate X90: (x,-z,y)
1045                         verts_loc.append( (float_func(line_split[1]), -float_func(line_split[3]), float_func(line_split[2])) )
1046                                 
1047                 elif line.startswith('vn '):
1048                         pass
1049                 
1050                 elif line.startswith('vt '):
1051                         line_split= line.split()
1052                         verts_tex.append( (float_func(line_split[1]), float_func(line_split[2])) ) 
1053                 
1054                 # Handel faces lines (as faces) and the second+ lines of fa multiline face here
1055                 # use 'f' not 'f ' because some objs (very rare have 'fo ' for faces)
1056                 elif line.startswith('f') or context_multi_line == 'f':
1057                         
1058                         if context_multi_line:
1059                                 # use face_vert_loc_indicies and face_vert_tex_indicies previously defined and used the obj_face
1060                                 line_split= line.split()
1061                                 
1062                         else:
1063                                 line_split= line[2:].split()
1064                                 face_vert_loc_indicies= []
1065                                 face_vert_tex_indicies= []
1066                                 
1067                                 # Instance a face
1068                                 faces.append((\
1069                                 face_vert_loc_indicies,\
1070                                 face_vert_tex_indicies,\
1071                                 context_material,\
1072                                 context_smooth_group,\
1073                                 context_object\
1074                                 ))
1075                         
1076                         if strip_slash(line_split):
1077                                 context_multi_line = 'f'
1078                         else:
1079                                 context_multi_line = ''
1080                         
1081                         for v in line_split:
1082                                 obj_vert= v.split('/')
1083                                 
1084                                 vert_loc_index= int(obj_vert[0])-1
1085                                 # Add the vertex to the current group
1086                                 # *warning*, this wont work for files that have groups defined around verts
1087                                 if      POLYGROUPS and context_vgroup:
1088                                         vertex_groups[context_vgroup].append(vert_loc_index)
1089                                 
1090                                 # Make relative negative vert indicies absolute
1091                                 if vert_loc_index < 0:
1092                                         vert_loc_index= len(verts_loc) + vert_loc_index + 1
1093                                 
1094                                 face_vert_loc_indicies.append(vert_loc_index)
1095                                 
1096                                 if len(obj_vert)>1 and obj_vert[1]:
1097                                         # formatting for faces with normals and textures us 
1098                                         # loc_index/tex_index/nor_index
1099                                         
1100                                         vert_tex_index= int(obj_vert[1])-1
1101                                         # Make relative negative vert indicies absolute
1102                                         if vert_tex_index < 0:
1103                                                 vert_tex_index= len(verts_tex) + vert_tex_index + 1
1104                                         
1105                                         face_vert_tex_indicies.append(vert_tex_index)
1106                                 else:
1107                                         # dummy
1108                                         face_vert_tex_indicies.append(0)
1109                         
1110                         if len(face_vert_loc_indicies) > 4:
1111                                 has_ngons= True
1112                 
1113                 elif CREATE_EDGES and (line.startswith('l ') or context_multi_line == 'l'):
1114                         # very similar to the face load function above with some parts removed
1115                         
1116                         if context_multi_line:
1117                                 # use face_vert_loc_indicies and face_vert_tex_indicies previously defined and used the obj_face
1118                                 line_split= line.split()
1119                                 
1120                         else:
1121                                 line_split= line[2:].split()
1122                                 face_vert_loc_indicies= []
1123                                 face_vert_tex_indicies= []
1124                                 
1125                                 # Instance a face
1126                                 faces.append((\
1127                                 face_vert_loc_indicies,\
1128                                 face_vert_tex_indicies,\
1129                                 context_material,\
1130                                 context_smooth_group,\
1131                                 context_object\
1132                                 ))
1133                         
1134                         if strip_slash(line_split):
1135                                 context_multi_line = 'l'
1136                         else:
1137                                 context_multi_line = ''
1138                         
1139                         isline= line.startswith('l')
1140                         
1141                         for v in line_split:
1142                                 vert_loc_index= int(v)-1
1143                                 
1144                                 # Make relative negative vert indicies absolute
1145                                 if vert_loc_index < 0:
1146                                         vert_loc_index= len(verts_loc) + vert_loc_index + 1
1147                                 
1148                                 face_vert_loc_indicies.append(vert_loc_index)
1149                 
1150                 elif line.startswith('s'):
1151                         if CREATE_SMOOTH_GROUPS:
1152                                 context_smooth_group= line_value(line.split())
1153                                 if context_smooth_group=='off':
1154                                         context_smooth_group= None
1155                                 elif context_smooth_group: # is not None
1156                                         unique_smooth_groups[context_smooth_group]= None
1157                 
1158                 elif line.startswith('o'):
1159                         if SPLIT_OBJECTS:
1160                                 context_object= line_value(line.split())
1161                                 # unique_obects[context_object]= None
1162                         
1163                 elif line.startswith('g'):
1164                         if SPLIT_GROUPS:
1165                                 context_object= line_value(line.split())
1166                                 # print 'context_object', context_object
1167                                 # unique_obects[context_object]= None
1168                         elif POLYGROUPS:
1169                                 context_vgroup = line_value(line.split())
1170                                 if context_vgroup and context_vgroup != '(null)':
1171                                         vertex_groups.setdefault(context_vgroup, [])
1172                                 else:
1173                                         context_vgroup = None # dont assign a vgroup
1174                 
1175                 elif line.startswith('usemtl'):
1176                         context_material= line_value(line.split())
1177                         unique_materials[context_material]= None
1178                 elif line.startswith('mtllib'): # usemap or usemat
1179                         material_libs.extend( line.split()[1:] ) # can have multiple mtllib filenames per line
1180                         
1181                         
1182                         # Nurbs support
1183                 elif line.startswith('cstype '):
1184                         context_nurbs['cstype']= line_value(line.split()) # 'rat bspline' / 'bspline'
1185                 elif line.startswith('curv ') or context_multi_line == 'curv':
1186                         line_split= line.split()
1187                         
1188                         curv_idx = context_nurbs['curv_idx'] = context_nurbs.get('curv_idx', []) # incase were multiline
1189                         
1190                         if not context_multi_line:
1191                                 context_nurbs['curv_range'] = float_func(line_split[1]), float_func(line_split[2])
1192                                 line_split[0:3] = [] # remove first 3 items
1193                         
1194                         if strip_slash(line_split):
1195                                 context_multi_line = 'curv'
1196                         else:
1197                                 context_multi_line = ''
1198                                 
1199                         
1200                         for i in line_split:
1201                                 vert_loc_index = int(i)-1
1202                                 
1203                                 if vert_loc_index < 0:
1204                                         vert_loc_index= len(verts_loc) + vert_loc_index + 1
1205                                 
1206                                 curv_idx.append(vert_loc_index)
1207                         
1208                 elif line.startswith('parm') or context_multi_line == 'parm':
1209                         line_split= line.split()
1210                         
1211                         if context_multi_line:
1212                                 context_multi_line = ''
1213                         else:
1214                                 context_parm = line_split[1]
1215                                 line_split[0:2] = [] # remove first 2
1216                         
1217                         if strip_slash(line_split):
1218                                 context_multi_line = 'parm'
1219                         else:
1220                                 context_multi_line = ''
1221                         
1222                         if context_parm.lower() == 'u':
1223                                 context_nurbs.setdefault('parm_u', []).extend( [float_func(f) for f in line_split] )
1224                         elif context_parm.lower() == 'v': # surfaces not suported yet
1225                                 context_nurbs.setdefault('parm_v', []).extend( [float_func(f) for f in line_split] )
1226                         # else: # may want to support other parm's ?
1227                 
1228                 elif line.startswith('deg '):
1229                         context_nurbs['deg']= [int(i) for i in line.split()[1:]]
1230                 elif line.startswith('end'):
1231                         # Add the nurbs curve
1232                         if context_object:
1233                                 context_nurbs['name'] = context_object
1234                         nurbs.append(context_nurbs)
1235                         context_nurbs = {}
1236                         context_parm = ''
1237                 
1238                 ''' # How to use usemap? depricated?
1239                 elif line.startswith('usema'): # usemap or usemat
1240                         context_image= line_value(line.split())
1241                 '''
1242         
1243         file.close()
1244         time_new= time.time()
1245 #       time_new= sys.time()
1246         print('%.4f sec' % (time_new-time_sub))
1247         time_sub= time_new
1248         
1249         
1250         print('\tloading materials and images...')
1251         create_materials(filepath, material_libs, unique_materials, unique_material_images, IMAGE_SEARCH)
1252
1253         time_new= time.time()
1254 #       time_new= sys.time()
1255         print('%.4f sec' % (time_new-time_sub))
1256         time_sub= time_new
1257         
1258         if not ROTATE_X90:
1259                 verts_loc[:] = [(v[0], v[2], -v[1]) for v in verts_loc]
1260         
1261         # deselect all
1262 #       if context.selected_objects:
1263 #               bpy.ops.OBJECT_OT_select_all_toggle()
1264
1265         scene = context.scene
1266 #       scn = bpy.data.scenes.active
1267 #       scn.objects.selected = []
1268         new_objects= [] # put new objects here
1269         
1270         print('\tbuilding geometry...\n\tverts:%i faces:%i materials: %i smoothgroups:%i ...' % ( len(verts_loc), len(faces), len(unique_materials), len(unique_smooth_groups) ))
1271         # Split the mesh by objects/materials, may 
1272         if SPLIT_OBJECTS or SPLIT_GROUPS:       SPLIT_OB_OR_GROUP = True
1273         else:                                                           SPLIT_OB_OR_GROUP = False
1274         
1275         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):
1276                 # Create meshes from the data, warning 'vertex_groups' wont support splitting
1277                 create_mesh(scene, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_loc_split, verts_tex, faces_split, unique_materials_split, unique_material_images, unique_smooth_groups, vertex_groups, dataname)
1278         
1279         # nurbs support
1280 #       for context_nurbs in nurbs:
1281 #               create_nurbs(scn, context_nurbs, verts_loc, new_objects)
1282         
1283         
1284         axis_min= [ 1000000000]*3
1285         axis_max= [-1000000000]*3
1286         
1287 #       if CLAMP_SIZE:
1288 #               # Get all object bounds
1289 #               for ob in new_objects:
1290 #                       for v in ob.getBoundBox():
1291 #                               for axis, value in enumerate(v):
1292 #                                       if axis_min[axis] > value:      axis_min[axis]= value
1293 #                                       if axis_max[axis] < value:      axis_max[axis]= value
1294                 
1295 #               # Scale objects
1296 #               max_axis= max(axis_max[0]-axis_min[0], axis_max[1]-axis_min[1], axis_max[2]-axis_min[2])
1297 #               scale= 1.0
1298                 
1299 #               while CLAMP_SIZE < max_axis * scale:
1300 #                       scale= scale/10.0
1301                 
1302 #               for ob in new_objects:
1303 #                       ob.setSize(scale, scale, scale)
1304         
1305         # Better rotate the vert locations
1306         #if not ROTATE_X90:
1307         #       for ob in new_objects:
1308         #               ob.RotX = -1.570796326794896558
1309
1310         time_new= time.time()
1311 #       time_new= sys.time()
1312         
1313         print('%.4f sec' % (time_new-time_sub))
1314         print('finished importing: "%s" in %.4f sec.' % (filepath, (time_new-time_main)))
1315
1316
1317 DEBUG= True
1318
1319
1320 def load_obj_ui(filepath, BATCH_LOAD= False):
1321         if BPyMessages.Error_NoFile(filepath):
1322                 return
1323         
1324         global CREATE_SMOOTH_GROUPS, CREATE_FGONS, CREATE_EDGES, SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, CLAMP_SIZE, IMAGE_SEARCH, POLYGROUPS, KEEP_VERT_ORDER, ROTATE_X90
1325         
1326         CREATE_SMOOTH_GROUPS= Draw.Create(0)
1327         CREATE_FGONS= Draw.Create(1)
1328         CREATE_EDGES= Draw.Create(1)
1329         SPLIT_OBJECTS= Draw.Create(0)
1330         SPLIT_GROUPS= Draw.Create(0)
1331         SPLIT_MATERIALS= Draw.Create(0)
1332         CLAMP_SIZE= Draw.Create(10.0)
1333         IMAGE_SEARCH= Draw.Create(1)
1334         POLYGROUPS= Draw.Create(0)
1335         KEEP_VERT_ORDER= Draw.Create(1)
1336         ROTATE_X90= Draw.Create(1)
1337         
1338         
1339         # Get USER Options
1340         # Note, Works but not pretty, instead use a more complicated GUI
1341         '''
1342         pup_block= [\
1343         'Import...',\
1344         ('Smooth Groups', CREATE_SMOOTH_GROUPS, 'Surround smooth groups by sharp edges'),\
1345         ('Create FGons', CREATE_FGONS, 'Import faces with more then 4 verts as fgons.'),\
1346         ('Lines', CREATE_EDGES, 'Import lines and faces with 2 verts as edges'),\
1347         'Separate objects from obj...',\
1348         ('Object', SPLIT_OBJECTS, 'Import OBJ Objects into Blender Objects'),\
1349         ('Group', SPLIT_GROUPS, 'Import OBJ Groups into Blender Objects'),\
1350         ('Material', SPLIT_MATERIALS, 'Import each material into a seperate mesh (Avoids > 16 per mesh error)'),\
1351         'Options...',\
1352         ('Keep Vert Order', KEEP_VERT_ORDER, 'Keep vert and face order, disables some other options.'),\
1353         ('Clamp Scale:', CLAMP_SIZE, 0.0, 1000.0, 'Clamp the size to this maximum (Zero to Disable)'),\
1354         ('Image Search', IMAGE_SEARCH, 'Search subdirs for any assosiated images (Warning, may be slow)'),\
1355         ]
1356         
1357         if not Draw.PupBlock('Import OBJ...', pup_block):
1358                 return
1359         
1360         if KEEP_VERT_ORDER.val:
1361                 SPLIT_OBJECTS.val = False
1362                 SPLIT_GROUPS.val = False
1363                 SPLIT_MATERIALS.val = False
1364         '''
1365         
1366         
1367         
1368         # BEGIN ALTERNATIVE UI *******************
1369         if True: 
1370                 
1371                 EVENT_NONE = 0
1372                 EVENT_EXIT = 1
1373                 EVENT_REDRAW = 2
1374                 EVENT_IMPORT = 3
1375                 
1376                 GLOBALS = {}
1377                 GLOBALS['EVENT'] = EVENT_REDRAW
1378                 #GLOBALS['MOUSE'] = Window.GetMouseCoords()
1379                 GLOBALS['MOUSE'] = [i/2 for i in Window.GetScreenSize()]
1380                 
1381                 def obj_ui_set_event(e,v):
1382                         GLOBALS['EVENT'] = e
1383                 
1384                 def do_split(e,v):
1385                         global SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, KEEP_VERT_ORDER, POLYGROUPS
1386                         if SPLIT_OBJECTS.val or SPLIT_GROUPS.val or SPLIT_MATERIALS.val:
1387                                 KEEP_VERT_ORDER.val = 0
1388                                 POLYGROUPS.val = 0
1389                         else:
1390                                 KEEP_VERT_ORDER.val = 1
1391                         
1392                 def do_vertorder(e,v):
1393                         global SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, KEEP_VERT_ORDER
1394                         if KEEP_VERT_ORDER.val:
1395                                 SPLIT_OBJECTS.val = SPLIT_GROUPS.val = SPLIT_MATERIALS.val = 0
1396                         else:
1397                                 if not (SPLIT_OBJECTS.val or SPLIT_GROUPS.val or SPLIT_MATERIALS.val):
1398                                         KEEP_VERT_ORDER.val = 1
1399                         
1400                 def do_polygroups(e,v):
1401                         global SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, KEEP_VERT_ORDER, POLYGROUPS
1402                         if POLYGROUPS.val:
1403                                 SPLIT_OBJECTS.val = SPLIT_GROUPS.val = SPLIT_MATERIALS.val = 0
1404                         
1405                 def do_help(e,v):
1406                         url = __url__[0]
1407                         print('Trying to open web browser with documentation at this address...')
1408                         print('\t' + url)
1409                         
1410                         try:
1411                                 import webbrowser
1412                                 webbrowser.open(url)
1413                         except:
1414                                 print('...could not open a browser window.')
1415                 
1416                 def obj_ui():
1417                         ui_x, ui_y = GLOBALS['MOUSE']
1418                         
1419                         # Center based on overall pup size
1420                         ui_x -= 165
1421                         ui_y -= 90
1422                         
1423                         global CREATE_SMOOTH_GROUPS, CREATE_FGONS, CREATE_EDGES, SPLIT_OBJECTS, SPLIT_GROUPS, SPLIT_MATERIALS, CLAMP_SIZE, IMAGE_SEARCH, POLYGROUPS, KEEP_VERT_ORDER, ROTATE_X90
1424                         
1425                         Draw.Label('Import...', ui_x+9, ui_y+159, 220, 21)
1426                         Draw.BeginAlign()
1427                         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')
1428                         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')
1429                         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')
1430                         Draw.EndAlign()
1431                         
1432                         Draw.Label('Separate objects by OBJ...', ui_x+9, ui_y+110, 220, 20)
1433                         Draw.BeginAlign()
1434                         SPLIT_OBJECTS = Draw.Toggle('Object', EVENT_REDRAW, ui_x+9, ui_y+89, 55, 21, SPLIT_OBJECTS.val, 'Import OBJ Objects into Blender Objects', do_split)
1435                         SPLIT_GROUPS = Draw.Toggle('Group', EVENT_REDRAW, ui_x+64, ui_y+89, 55, 21, SPLIT_GROUPS.val, 'Import OBJ Groups into Blender Objects', do_split)
1436                         SPLIT_MATERIALS = Draw.Toggle('Material', EVENT_REDRAW, ui_x+119, ui_y+89, 60, 21, SPLIT_MATERIALS.val, 'Import each material into a seperate mesh (Avoids > 16 per mesh error)', do_split)
1437                         Draw.EndAlign()
1438                         
1439                         # Only used for user feedback
1440                         KEEP_VERT_ORDER = Draw.Toggle('Keep Vert Order', EVENT_REDRAW, ui_x+184, ui_y+89, 113, 21, KEEP_VERT_ORDER.val, 'Keep vert and face order, disables split options, enable for morph targets', do_vertorder)
1441                         
1442                         ROTATE_X90 = Draw.Toggle('-X90', EVENT_REDRAW, ui_x+302, ui_y+89, 38, 21, ROTATE_X90.val, 'Rotate X 90.')
1443                         
1444                         Draw.Label('Options...', ui_x+9, ui_y+60, 211, 20)
1445                         CLAMP_SIZE = Draw.Number('Clamp Scale: ', EVENT_NONE, ui_x+9, ui_y+39, 130, 21, CLAMP_SIZE.val, 0.0, 1000.0, 'Clamp the size to this maximum (Zero to Disable)')
1446                         POLYGROUPS = Draw.Toggle('Poly Groups', EVENT_REDRAW, ui_x+144, ui_y+39, 90, 21, POLYGROUPS.val, 'Import OBJ groups as vertex groups.', do_polygroups)
1447                         IMAGE_SEARCH = Draw.Toggle('Image Search', EVENT_NONE, ui_x+239, ui_y+39, 100, 21, IMAGE_SEARCH.val, 'Search subdirs for any assosiated images (Warning, may be slow)')
1448                         Draw.BeginAlign()
1449                         Draw.PushButton('Online Help', EVENT_REDRAW, ui_x+9, ui_y+9, 110, 21, 'Load the wiki page for this script', do_help)
1450                         Draw.PushButton('Cancel', EVENT_EXIT, ui_x+119, ui_y+9, 110, 21, '', obj_ui_set_event)
1451                         Draw.PushButton('Import', EVENT_IMPORT, ui_x+229, ui_y+9, 110, 21, 'Import with these settings', obj_ui_set_event)
1452                         Draw.EndAlign()
1453                         
1454                 
1455                 # hack so the toggle buttons redraw. this is not nice at all
1456                 while GLOBALS['EVENT'] not in (EVENT_EXIT, EVENT_IMPORT):
1457                         Draw.UIBlock(obj_ui, 0)
1458                 
1459                 if GLOBALS['EVENT'] != EVENT_IMPORT:
1460                         return
1461                 
1462         # END ALTERNATIVE UI *********************
1463         
1464         
1465         
1466         
1467         
1468         
1469         
1470         Window.WaitCursor(1)
1471         
1472         if BATCH_LOAD: # load the dir
1473                 try:
1474                         files= [ f for f in os.listdir(filepath) if f.lower().endswith('.obj') ]
1475                 except:
1476                         Window.WaitCursor(0)
1477                         Draw.PupMenu('Error%t|Could not open path ' + filepath)
1478                         return
1479                 
1480                 if not files:
1481                         Window.WaitCursor(0)
1482                         Draw.PupMenu('Error%t|No files at path ' + filepath)
1483                         return
1484                 
1485                 for f in files:
1486                         scn= bpy.data.scenes.new( stripExt(f) )
1487                         scn.makeCurrent()
1488                         
1489                         load_obj(sys.join(filepath, f),\
1490                           CLAMP_SIZE.val,\
1491                           CREATE_FGONS.val,\
1492                           CREATE_SMOOTH_GROUPS.val,\
1493                           CREATE_EDGES.val,\
1494                           SPLIT_OBJECTS.val,\
1495                           SPLIT_GROUPS.val,\
1496                           SPLIT_MATERIALS.val,\
1497                           ROTATE_X90.val,\
1498                           IMAGE_SEARCH.val,\
1499                           POLYGROUPS.val
1500                         )
1501         
1502         else: # Normal load
1503                 load_obj(filepath,\
1504                   CLAMP_SIZE.val,\
1505                   CREATE_FGONS.val,\
1506                   CREATE_SMOOTH_GROUPS.val,\
1507                   CREATE_EDGES.val,\
1508                   SPLIT_OBJECTS.val,\
1509                   SPLIT_GROUPS.val,\
1510                   SPLIT_MATERIALS.val,\
1511                   ROTATE_X90.val,\
1512                   IMAGE_SEARCH.val,\
1513                   POLYGROUPS.val
1514                 )
1515         
1516         Window.WaitCursor(0)
1517
1518
1519 def load_obj_ui_batch(file):
1520         load_obj_ui(file, True)
1521
1522 DEBUG= False
1523
1524 # if __name__=='__main__' and not DEBUG:
1525 #       if os and Window.GetKeyQualifiers() & Window.Qual.SHIFT:
1526 #               Window.FileSelector(load_obj_ui_batch, 'Import OBJ Dir', '')
1527 #       else:
1528 #               Window.FileSelector(load_obj_ui, 'Import a Wavefront OBJ', '*.obj')
1529
1530         # For testing compatibility
1531 '''
1532 else:
1533         # DEBUG ONLY
1534         TIME= sys.time()
1535         DIR = '/fe/obj'
1536         import os
1537         print 'Searching for files'
1538         def fileList(path):
1539                 for dirpath, dirnames, filenames in os.walk(path):
1540                         for filename in filenames:
1541                                 yield os.path.join(dirpath, filename)
1542         
1543         files = [f for f in fileList(DIR) if f.lower().endswith('.obj')]
1544         files.sort()
1545         
1546         for i, obj_file in enumerate(files):
1547                 if 0 < i < 20:
1548                         print 'Importing', obj_file, '\nNUMBER', i, 'of', len(files)
1549                         newScn= bpy.data.scenes.new(os.path.basename(obj_file))
1550                         newScn.makeCurrent()
1551                         load_obj(obj_file, False, IMAGE_SEARCH=0)
1552
1553         print 'TOTAL TIME: %.6f' % (sys.time() - TIME)
1554 '''
1555
1556 class IMPORT_OT_obj(bpy.types.Operator):
1557         '''Load a Wavefront OBJ File.'''
1558         bl_idname = "import.obj"
1559         bl_label = "Import OBJ"
1560         
1561         # List of operator properties, the attributes will be assigned
1562         # to the class instance from the operator settings before calling.
1563         
1564         bl_props = [
1565                 bpy.props.StringProperty(attr="path", name="File Path", description="File path used for importing the OBJ file", maxlen= 1024, default= ""),
1566
1567                 bpy.props.BoolProperty(attr="CREATE_SMOOTH_GROUPS", name="Smooth Groups", description="Surround smooth groups by sharp edges", default= True),
1568                 bpy.props.BoolProperty(attr="CREATE_FGONS", name="NGons as FGons", description="Import faces with more then 4 verts as fgons", default= True),
1569                 bpy.props.BoolProperty(attr="CREATE_EDGES", name="Lines as Edges", description="Import lines and faces with 2 verts as edge", default= True),
1570                 bpy.props.BoolProperty(attr="SPLIT_OBJECTS", name="Object", description="Import OBJ Objects into Blender Objects", default= True),
1571                 bpy.props.BoolProperty(attr="SPLIT_GROUPS", name="Group", description="Import OBJ Groups into Blender Objects", default= True),
1572                 bpy.props.BoolProperty(attr="SPLIT_MATERIALS", name="Material", description="Import each material into a seperate mesh (Avoids > 16 per mesh error)", default= True),
1573                 # old comment: only used for user feedback
1574                 # disabled this option because in old code a handler for it disabled SPLIT* params, it's not passed to load_obj
1575                 # bpy.props.BoolProperty(attr="KEEP_VERT_ORDER", name="Keep Vert Order", description="Keep vert and face order, disables split options, enable for morph targets", default= True),
1576                 bpy.props.BoolProperty(attr="ROTATE_X90", name="-X90", description="Rotate X 90.", default= True),
1577                 bpy.props.FloatProperty(attr="CLAMP_SIZE", name="Clamp Scale", description="Clamp the size to this maximum (Zero to Disable)", min=0.01, max=1000.0, soft_min=0.0, soft_max=1000.0, default=0.0),
1578                 bpy.props.BoolProperty(attr="POLYGROUPS", name="Poly Groups", description="Import OBJ groups as vertex groups.", default= True),
1579                 bpy.props.BoolProperty(attr="IMAGE_SEARCH", name="Image Search", description="Search subdirs for any assosiated images (Warning, may be slow)", default= True),
1580         ]
1581         
1582         def execute(self, context):
1583                 # print("Selected: " + context.active_object.name)
1584
1585                 load_obj(self.path,
1586                                  context,
1587                                  self.CLAMP_SIZE,
1588                                  self.CREATE_FGONS,
1589                                  self.CREATE_SMOOTH_GROUPS,
1590                                  self.CREATE_EDGES,
1591                                  self.SPLIT_OBJECTS,
1592                                  self.SPLIT_GROUPS,
1593                                  self.SPLIT_MATERIALS,
1594                                  self.ROTATE_X90,
1595                                  self.IMAGE_SEARCH,
1596                                  self.POLYGROUPS)
1597
1598                 return ('FINISHED',)
1599         
1600         def invoke(self, context, event):       
1601                 wm = context.manager
1602                 wm.add_fileselect(self.__operator__)
1603                 return ('RUNNING_MODAL',)
1604
1605
1606 bpy.ops.add(IMPORT_OT_obj)
1607
1608
1609 import dynamic_menu
1610 menu_func = lambda self, context: self.layout.itemO("import.obj", text="Wavefront (.obj)...")
1611 menu_item = dynamic_menu.add(bpy.types.INFO_MT_file_import, menu_func)
1612
1613
1614 # NOTES (all line numbers refer to 2.4x import_obj.py, not this file)
1615 # check later: line 489
1616 # can convert now: edge flags, edges: lines 508-528
1617 # ngon (uses python module BPyMesh): 384-414
1618 # nurbs: 947-
1619 # NEXT clamp size: get bound box with RNA
1620 # get back to l 140 (here)
1621 # search image in bpy.config.textureDir - load_image
1622 # replaced BPyImage.comprehensiveImageLoad with a simplified version that only checks additional directory specified, but doesn't search dirs recursively (obj_image_load)
1623 # bitmask won't work? - 132
1624 # uses operator bpy.ops.OBJECT_OT_select_all_toggle() to deselect all (not necessary?)
1625 # uses bpy.sys.time()