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