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