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