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