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