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