use set as a suffix (matches operators)
[blender.git] / release / scripts / op / io_scene_obj / import_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 # Script copyright (C) Campbell Barton
22 # Contributors: Campbell Barton, Jiri Hnidek, Paolo Ciccone
23
24 """
25 This script imports a Wavefront OBJ files to Blender.
26
27 Usage:
28 Run this script from "File->Import" menu and then load the desired OBJ file.
29 Note, This loads mesh objects and materials only, nurbs and curves are not supported.
30
31 http://wiki.blender.org/index.php/Scripts/Manual/Import/wavefront_obj
32 """
33
34 import os
35 import time
36 import bpy
37 import mathutils
38 from geometry import PolyFill
39 from io_utils import load_image, unpack_list, unpack_face_list
40
41
42 def BPyMesh_ngon(from_data, indices, PREF_FIX_LOOPS= True):
43     '''
44     Takes a polyline of indices (fgon)
45     and returns a list of face indicie lists.
46     Designed to be used for importers that need indices for an fgon to create from existing verts.
47
48     from_data: either a mesh, or a list/tuple of vectors.
49     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.
50     PREF_FIX_LOOPS: If this is enabled polylines that use loops to make multiple polylines are delt with correctly.
51     '''
52
53     if not set: # Need sets for this, otherwise do a normal fill.
54         PREF_FIX_LOOPS= False
55
56     Vector= mathutils.Vector
57     if not indices:
58         return []
59
60     #    return []
61     def rvec(co): return round(co.x, 6), round(co.y, 6), round(co.z, 6)
62     def mlen(co): return abs(co[0])+abs(co[1])+abs(co[2]) # manhatten length of a vector, faster then length
63
64     def vert_treplet(v, i):
65         return v, rvec(v), i, mlen(v)
66
67     def ed_key_mlen(v1, v2):
68         if v1[3] > v2[3]:
69             return v2[1], v1[1]
70         else:
71             return v1[1], v2[1]
72
73
74     if not PREF_FIX_LOOPS:
75         '''
76         Normal single concave loop filling
77         '''
78         if type(from_data) in (tuple, list):
79             verts= [Vector(from_data[i]) for ii, i in enumerate(indices)]
80         else:
81             verts= [from_data.vertices[i].co for ii, i in enumerate(indices)]
82
83         for i in range(len(verts)-1, 0, -1): # same as reversed(xrange(1, len(verts))):
84             if verts[i][1]==verts[i-1][0]:
85                 verts.pop(i-1)
86
87         fill= PolyFill([verts])
88
89     else:
90         '''
91         Seperate this loop into multiple loops be finding edges that are used twice
92         This is used by lightwave LWO files a lot
93         '''
94
95         if type(from_data) in (tuple, list):
96             verts= [vert_treplet(Vector(from_data[i]), ii) for ii, i in enumerate(indices)]
97         else:
98             verts= [vert_treplet(from_data.vertices[i].co, ii) for ii, i in enumerate(indices)]
99
100         edges= [(i, i-1) for i in range(len(verts))]
101         if edges:
102             edges[0]= (0,len(verts)-1)
103
104         if not verts:
105             return []
106
107
108         edges_used= set()
109         edges_doubles= set()
110         # We need to check if any edges are used twice location based.
111         for ed in edges:
112             edkey= ed_key_mlen(verts[ed[0]], verts[ed[1]])
113             if edkey in edges_used:
114                 edges_doubles.add(edkey)
115             else:
116                 edges_used.add(edkey)
117
118         # Store a list of unconnected loop segments split by double edges.
119         # will join later
120         loop_segments= []
121
122         v_prev= verts[0]
123         context_loop= [v_prev]
124         loop_segments= [context_loop]
125
126         for v in verts:
127             if v!=v_prev:
128                 # Are we crossing an edge we removed?
129                 if ed_key_mlen(v, v_prev) in edges_doubles:
130                     context_loop= [v]
131                     loop_segments.append(context_loop)
132                 else:
133                     if context_loop and context_loop[-1][1]==v[1]:
134                         #raise "as"
135                         pass
136                     else:
137                         context_loop.append(v)
138
139                 v_prev= v
140         # Now join loop segments
141
142         def join_seg(s1,s2):
143             if s2[-1][1]==s1[0][1]: #
144                 s1,s2= s2,s1
145             elif s1[-1][1]==s2[0][1]:
146                 pass
147             else:
148                 return False
149
150             # If were stuill here s1 and s2 are 2 segments in the same polyline
151             s1.pop() # remove the last vert from s1
152             s1.extend(s2) # add segment 2 to segment 1
153
154             if s1[0][1]==s1[-1][1]: # remove endpoints double
155                 s1.pop()
156
157             s2[:]= [] # Empty this segment s2 so we dont use it again.
158             return True
159
160         joining_segments= True
161         while joining_segments:
162             joining_segments= False
163             segcount= len(loop_segments)
164
165             for j in range(segcount-1, -1, -1): #reversed(range(segcount)):
166                 seg_j= loop_segments[j]
167                 if seg_j:
168                     for k in range(j-1, -1, -1): # reversed(range(j)):
169                         if not seg_j:
170                             break
171                         seg_k= loop_segments[k]
172
173                         if seg_k and join_seg(seg_j, seg_k):
174                             joining_segments= True
175
176         loop_list= loop_segments
177
178         for verts in loop_list:
179             while verts and verts[0][1]==verts[-1][1]:
180                 verts.pop()
181
182         loop_list= [verts for verts in loop_list if len(verts)>2]
183         # DONE DEALING WITH LOOP FIXING
184
185
186         # vert mapping
187         vert_map= [None]*len(indices)
188         ii=0
189         for verts in loop_list:
190             if len(verts)>2:
191                 for i, vert in enumerate(verts):
192                     vert_map[i+ii]= vert[2]
193                 ii+=len(verts)
194
195         fill= PolyFill([ [v[0] for v in loop] for loop in loop_list ])
196         #draw_loops(loop_list)
197         #raise 'done loop'
198         # map to original indicies
199         fill= [[vert_map[i] for i in reversed(f)] for f in fill]
200
201
202     if not fill:
203         print('Warning Cannot scanfill, fallback on a triangle fan.')
204         fill= [ [0, i-1, i] for i in range(2, len(indices)) ]
205     else:
206         # Use real scanfill.
207         # See if its flipped the wrong way.
208         flip= None
209         for fi in fill:
210             if flip != None:
211                 break
212             for i, vi in enumerate(fi):
213                 if vi==0 and fi[i-1]==1:
214                     flip= False
215                     break
216                 elif vi==1 and fi[i-1]==0:
217                     flip= True
218                     break
219
220         if not flip:
221             for i, fi in enumerate(fill):
222                 fill[i]= tuple([ii for ii in reversed(fi)])
223
224     return fill
225
226 def line_value(line_split):
227     '''
228     Returns 1 string represneting the value for this line
229     None will be returned if theres only 1 word
230     '''
231     length= len(line_split)
232     if length == 1:
233         return None
234
235     elif length == 2:
236         return line_split[1]
237
238     elif length > 2:
239         return ' '.join( line_split[1:] )
240
241
242 def obj_image_load(imagepath, DIR, IMAGE_SEARCH):
243     if '_' in imagepath:
244         image= load_image(imagepath.replace('_', ' '), DIR)
245         if image:
246             return image
247
248     image = load_image(imagepath, DIR)
249     if image:
250         return image
251
252     print("failed to load '%s' doesn't exist", imagepath)
253     return None
254
255 # def obj_image_load(imagepath, DIR, IMAGE_SEARCH):
256 #     '''
257 #     Mainly uses comprehensiveImageLoad
258 #     but tries to replace '_' with ' ' for Max's exporter replaces spaces with underscores.
259 #     '''
260
261 #     if '_' in imagepath:
262 #         image= BPyImage.comprehensiveImageLoad(imagepath, DIR, PLACE_HOLDER= False, RECURSIVE= IMAGE_SEARCH)
263 #         if image: return image
264 #         # Did the exporter rename the image?
265 #         image= BPyImage.comprehensiveImageLoad(imagepath.replace('_', ' '), DIR, PLACE_HOLDER= False, RECURSIVE= IMAGE_SEARCH)
266 #         if image: return image
267
268 #     # Return an image, placeholder if it dosnt exist
269 #     image= BPyImage.comprehensiveImageLoad(imagepath, DIR, PLACE_HOLDER= True, RECURSIVE= IMAGE_SEARCH)
270 #     return image
271
272
273 def create_materials(filepath, material_libs, unique_materials, unique_material_images, IMAGE_SEARCH):
274     '''
275     Create all the used materials in this obj,
276     assign colors and images to the materials from all referenced material libs
277     '''
278     DIR= os.path.dirname(filepath)
279
280     #==================================================================================#
281     # This function sets textures defined in .mtl file                                 #
282     #==================================================================================#
283     def load_material_image(blender_material, context_material_name, imagepath, type):
284
285         texture= bpy.data.textures.new(name=type, type='IMAGE')
286
287         # Absolute path - c:\.. etc would work here
288         image = obj_image_load(imagepath, DIR, IMAGE_SEARCH)
289         has_data = False
290
291         if image:
292             texture.image = image
293             has_data = image.has_data
294
295         # Adds textures for materials (rendering)
296         if type == 'Kd':
297             if has_data and image.depth == 32:
298                 # Image has alpha
299
300                 # XXX bitmask won't work?
301                 blender_material.add_texture(texture, 'UV', {'COLOR', 'ALPHA'})
302                 texture.mipmap = True
303                 texture.interpolation = True
304                 texture.use_alpha = True
305                 blender_material.use_transparency = True
306                 blender_material.alpha = 0.0
307             else:
308                 blender_material.add_texture(texture, 'UV', 'COLOR')
309
310             # adds textures to faces (Textured/Alt-Z mode)
311             # Only apply the diffuse texture to the face if the image has not been set with the inline usemat func.
312             unique_material_images[context_material_name]= image, has_data # set the texface image
313
314         elif type == 'Ka':
315             blender_material.add_texture(texture, 'UV', 'AMBIENT')
316 #             blender_material.setTexture(1, texture, Texture.TexCo.UV, Texture.MapTo.CMIR) # TODO- Add AMB to BPY API
317
318         elif type == 'Ks':
319             blender_material.add_texture(texture, 'UV', 'SPECULARITY')
320 #             blender_material.setTexture(2, texture, Texture.TexCo.UV, Texture.MapTo.SPEC)
321
322         elif type == 'Bump':
323             blender_material.add_texture(texture, 'UV', 'NORMAL')
324 #             blender_material.setTexture(3, texture, Texture.TexCo.UV, Texture.MapTo.NOR)
325         elif type == 'D':
326             blender_material.add_texture(texture, 'UV', 'ALPHA')
327             blender_material.z_transparency = True
328             blender_material.alpha = 0.0
329 #             blender_material.setTexture(4, texture, Texture.TexCo.UV, Texture.MapTo.ALPHA)
330 #             blender_material.mode |= Material.Modes.ZTRANSP
331 #             blender_material.alpha = 0.0
332             # Todo, unset deffuse material alpha if it has an alpha channel
333
334         elif type == 'refl':
335             blender_material.add_texture(texture, 'UV', 'REFLECTION')
336 #             blender_material.setTexture(5, texture, Texture.TexCo.UV, Texture.MapTo.REF)
337
338
339     # Add an MTL with the same name as the obj if no MTLs are spesified.
340     temp_mtl = os.path.splitext((os.path.basename(filepath)))[0] + '.mtl'
341
342     if os.path.exists(os.path.join(DIR, temp_mtl)) and temp_mtl not in material_libs:
343         material_libs.append( temp_mtl )
344     del temp_mtl
345
346     #Create new materials
347     for name in unique_materials: # .keys()
348         if name != None:
349             unique_materials[name]= bpy.data.materials.new(name)
350             unique_material_images[name]= None, False # assign None to all material images to start with, add to later.
351
352     unique_materials[None]= None
353     unique_material_images[None]= None, False
354
355     for libname in material_libs:
356         mtlpath= os.path.join(DIR, libname)
357         if not os.path.exists(mtlpath):
358             print ("\tError Missing MTL: '%s'" % mtlpath)
359         else:
360             #print '\t\tloading mtl: "%s"' % mtlpath
361             context_material= None
362             mtl= open(mtlpath, 'rU')
363             for line in mtl: #.xreadlines():
364                 if line.startswith('newmtl'):
365                     context_material_name= line_value(line.split())
366                     if context_material_name in unique_materials:
367                         context_material = unique_materials[ context_material_name ]
368                     else:
369                         context_material = None
370
371                 elif context_material:
372                     # we need to make a material to assign properties to it.
373                     line_split= line.split()
374                     line_lower= line.lower().lstrip()
375                     if line_lower.startswith('ka'):
376                         context_material.mirror_color = (float(line_split[1]), float(line_split[2]), float(line_split[3]))
377                     elif line_lower.startswith('kd'):
378                         context_material.diffuse_color = (float(line_split[1]), float(line_split[2]), float(line_split[3]))
379                     elif line_lower.startswith('ks'):
380                         context_material.specular_color = (float(line_split[1]), float(line_split[2]), float(line_split[3]))
381                     elif line_lower.startswith('ns'):
382                         context_material.specular_hardness = int((float(line_split[1])*0.51))
383                     elif line_lower.startswith('ni'): # Refraction index
384                         context_material.raytrace_transparency.ior = max(1, min(float(line_split[1]), 3)) # Between 1 and 3
385                     elif line_lower.startswith('d') or line_lower.startswith('tr'):
386                         context_material.alpha = float(line_split[1])
387                     elif line_lower.startswith('map_ka'):
388                         img_filepath= line_value(line.split())
389                         if img_filepath:
390                             load_material_image(context_material, context_material_name, img_filepath, 'Ka')
391                     elif line_lower.startswith('map_ks'):
392                         img_filepath= line_value(line.split())
393                         if img_filepath:
394                             load_material_image(context_material, context_material_name, img_filepath, 'Ks')
395                     elif line_lower.startswith('map_kd'):
396                         img_filepath= line_value(line.split())
397                         if img_filepath:
398                             load_material_image(context_material, context_material_name, img_filepath, 'Kd')
399                     elif line_lower.startswith('map_bump'):
400                         img_filepath= line_value(line.split())
401                         if img_filepath:
402                             load_material_image(context_material, context_material_name, img_filepath, 'Bump')
403                     elif line_lower.startswith('map_d') or line_lower.startswith('map_tr'): # Alpha map - Dissolve
404                         img_filepath= line_value(line.split())
405                         if img_filepath:
406                             load_material_image(context_material, context_material_name, img_filepath, 'D')
407
408                     elif line_lower.startswith('refl'): # Reflectionmap
409                         img_filepath= line_value(line.split())
410                         if img_filepath:
411                             load_material_image(context_material, context_material_name, img_filepath, 'refl')
412             mtl.close()
413
414
415
416
417 def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
418     '''
419     Takes vert_loc and faces, and separates into multiple sets of
420     (verts_loc, faces, unique_materials, dataname)
421     '''
422
423     filename = os.path.splitext((os.path.basename(filepath)))[0]
424
425     if not SPLIT_OB_OR_GROUP:
426         # use the filename for the object name since we arnt chopping up the mesh.
427         return [(verts_loc, faces, unique_materials, filename)]
428
429     def key_to_name(key):
430         # if the key is a tuple, join it to make a string
431         if not key:
432             return filename # assume its a string. make sure this is true if the splitting code is changed
433         else:
434             return key
435
436     # Return a key that makes the faces unique.
437     face_split_dict= {}
438
439     oldkey= -1 # initialize to a value that will never match the key
440
441     for face in faces:
442         key= face[4]
443
444         if oldkey != key:
445             # Check the key has changed.
446             try:
447                 verts_split, faces_split, unique_materials_split, vert_remap= face_split_dict[key]
448             except KeyError:
449                 faces_split= []
450                 verts_split= []
451                 unique_materials_split= {}
452                 vert_remap= [-1]*len(verts_loc)
453
454                 face_split_dict[key]= (verts_split, faces_split, unique_materials_split, vert_remap)
455
456             oldkey= key
457
458         face_vert_loc_indicies= face[0]
459
460         # Remap verts to new vert list and add where needed
461         for enum, i in enumerate(face_vert_loc_indicies):
462             if vert_remap[i] == -1:
463                 new_index= len(verts_split)
464                 vert_remap[i]= new_index # set the new remapped index so we only add once and can reference next time.
465                 face_vert_loc_indicies[enum] = new_index # remap to the local index
466                 verts_split.append( verts_loc[i] ) # add the vert to the local verts
467             else:
468                 face_vert_loc_indicies[enum] = vert_remap[i] # remap to the local index
469
470             matname= face[2]
471             if matname and matname not in unique_materials_split:
472                 unique_materials_split[matname] = unique_materials[matname]
473
474         faces_split.append(face)
475
476     # remove one of the itemas and reorder
477     return [(value[0], value[1], value[2], key_to_name(key)) for key, value in list(face_split_dict.items())]
478
479
480 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):
481     '''
482     Takes all the data gathered and generates a mesh, adding the new object to new_objects
483     deals with fgons, sharp edges and assigning materials
484     '''
485     if not has_ngons:
486         CREATE_FGONS= False
487
488     if unique_smooth_groups:
489         sharp_edges= {}
490         smooth_group_users = {context_smooth_group: {} for context_smooth_group in list(unique_smooth_groups.keys())}
491         context_smooth_group_old= -1
492
493     # Split fgons into tri's
494     fgon_edges= {} # Used for storing fgon keys
495     if CREATE_EDGES:
496         edges= []
497
498     context_object= None
499
500     # reverse loop through face indicies
501     for f_idx in range(len(faces)-1, -1, -1):
502
503         face_vert_loc_indicies,\
504         face_vert_tex_indicies,\
505         context_material,\
506         context_smooth_group,\
507         context_object= faces[f_idx]
508
509         len_face_vert_loc_indicies = len(face_vert_loc_indicies)
510
511         if len_face_vert_loc_indicies==1:
512             faces.pop(f_idx)# cant add single vert faces
513
514         elif not face_vert_tex_indicies or len_face_vert_loc_indicies == 2: # faces that have no texture coords are lines
515             if CREATE_EDGES:
516                 # generators are better in python 2.4+ but can't be used in 2.3
517                 # edges.extend( (face_vert_loc_indicies[i], face_vert_loc_indicies[i+1]) for i in xrange(len_face_vert_loc_indicies-1) )
518                 edges.extend( [(face_vert_loc_indicies[i], face_vert_loc_indicies[i+1]) for i in range(len_face_vert_loc_indicies-1)] )
519
520             faces.pop(f_idx)
521         else:
522
523             # Smooth Group
524             if unique_smooth_groups and context_smooth_group:
525                 # Is a part of of a smooth group and is a face
526                 if context_smooth_group_old is not context_smooth_group:
527                     edge_dict= smooth_group_users[context_smooth_group]
528                     context_smooth_group_old= context_smooth_group
529
530                 for i in range(len_face_vert_loc_indicies):
531                     i1= face_vert_loc_indicies[i]
532                     i2= face_vert_loc_indicies[i-1]
533                     if i1>i2: i1,i2= i2,i1
534
535                     try:
536                         edge_dict[i1,i2]+= 1
537                     except KeyError:
538                         edge_dict[i1,i2]=  1
539
540             # FGons into triangles
541             if has_ngons and len_face_vert_loc_indicies > 4:
542
543                 ngon_face_indices= BPyMesh_ngon(verts_loc, face_vert_loc_indicies)
544                 faces.extend(
545                     [(
546                     [face_vert_loc_indicies[ngon[0]], face_vert_loc_indicies[ngon[1]], face_vert_loc_indicies[ngon[2]] ],
547                     [face_vert_tex_indicies[ngon[0]], face_vert_tex_indicies[ngon[1]], face_vert_tex_indicies[ngon[2]] ],
548                     context_material,
549                     context_smooth_group,
550                     context_object)
551                     for ngon in ngon_face_indices]
552                 )
553
554                 # edges to make fgons
555                 if CREATE_FGONS:
556                     edge_users= {}
557                     for ngon in ngon_face_indices:
558                         for i in (0,1,2):
559                             i1= face_vert_loc_indicies[ngon[i  ]]
560                             i2= face_vert_loc_indicies[ngon[i-1]]
561                             if i1>i2: i1,i2= i2,i1
562
563                             try:
564                                 edge_users[i1,i2]+=1
565                             except KeyError:
566                                 edge_users[i1,i2]= 1
567
568                     for key, users in edge_users.items():
569                         if users>1:
570                             fgon_edges[key]= None
571
572                 # remove all after 3, means we dont have to pop this one.
573                 faces.pop(f_idx)
574
575
576     # Build sharp edges
577     if unique_smooth_groups:
578         for edge_dict in list(smooth_group_users.values()):
579             for key, users in list(edge_dict.items()):
580                 if users==1: # This edge is on the boundry of a group
581                     sharp_edges[key]= None
582
583
584     # map the material names to an index
585     material_mapping = {name: i for i, name in enumerate(unique_materials)} # enumerate over unique_materials keys()
586
587     materials= [None] * len(unique_materials)
588
589     for name, index in list(material_mapping.items()):
590         materials[index]= unique_materials[name]
591
592     me= bpy.data.meshes.new(dataname)
593
594     # make sure the list isnt too big
595     for material in materials:
596         me.materials.append(material)
597
598     me.vertices.add(len(verts_loc))
599     me.faces.add(len(faces))
600
601     # verts_loc is a list of (x, y, z) tuples
602     me.vertices.foreach_set("co", unpack_list(verts_loc))
603
604     # faces is a list of (vert_indices, texco_indices, ...) tuples
605     # XXX faces should contain either 3 or 4 verts
606     # XXX no check for valid face indices
607     me.faces.foreach_set("vertices_raw", unpack_face_list([f[0] for f in faces]))
608
609     if verts_tex and me.faces:
610         me.uv_textures.new()
611
612     context_material_old= -1 # avoid a dict lookup
613     mat= 0 # rare case it may be un-initialized.
614     me_faces= me.faces
615
616     for i, face in enumerate(faces):
617         if len(face[0]) < 2:
618             pass #raise "bad face"
619         elif len(face[0])==2:
620             if CREATE_EDGES:
621                 edges.append(face[0])
622         else:
623
624                 blender_face = me.faces[i]
625
626                 face_vert_loc_indicies,\
627                 face_vert_tex_indicies,\
628                 context_material,\
629                 context_smooth_group,\
630                 context_object= face
631
632
633
634                 if context_smooth_group:
635                     blender_face.use_smooth = True
636
637                 if context_material:
638                     if context_material_old is not context_material:
639                         mat= material_mapping[context_material]
640                         context_material_old= context_material
641
642                     blender_face.material_index= mat
643 #                     blender_face.mat= mat
644
645
646                 if verts_tex:
647
648                     blender_tface= me.uv_textures[0].data[i]
649
650                     if context_material:
651                         image, has_data = unique_material_images[context_material]
652                         if image: # Can be none if the material dosnt have an image.
653                             blender_tface.image = image
654                             blender_tface.use_image = True
655                             if has_data and image.depth == 32:
656                                 blender_tface.blend_type = 'ALPHA'
657
658                     # BUG - Evil eekadoodle problem where faces that have vert index 0 location at 3 or 4 are shuffled.
659                     if len(face_vert_loc_indicies)==4:
660                         if face_vert_loc_indicies[2]==0 or face_vert_loc_indicies[3]==0:
661                             face_vert_tex_indicies= face_vert_tex_indicies[2], face_vert_tex_indicies[3], face_vert_tex_indicies[0], face_vert_tex_indicies[1]
662                     else: # length of 3
663                         if face_vert_loc_indicies[2]==0:
664                             face_vert_tex_indicies= face_vert_tex_indicies[1], face_vert_tex_indicies[2], face_vert_tex_indicies[0]
665                     # END EEEKADOODLE FIX
666
667                     # assign material, uv's and image
668                     blender_tface.uv1= verts_tex[face_vert_tex_indicies[0]]
669                     blender_tface.uv2= verts_tex[face_vert_tex_indicies[1]]
670                     blender_tface.uv3= verts_tex[face_vert_tex_indicies[2]]
671
672                     if len(face_vert_loc_indicies)==4:
673                         blender_tface.uv4= verts_tex[face_vert_tex_indicies[3]]
674
675 #                     for ii, uv in enumerate(blender_face.uv):
676 #                         uv.x, uv.y=  verts_tex[face_vert_tex_indicies[ii]]
677     del me_faces
678 #     del ALPHA
679
680     if CREATE_EDGES:
681
682         me.edges.add(len(edges))
683
684         # edges should be a list of (a, b) tuples
685         me.edges.foreach_set("vertices", unpack_list(edges))
686 #         me_edges.extend( edges )
687
688 #     del me_edges
689
690     # Add edge faces.
691 #     me_edges= me.edges
692
693     def edges_match(e1, e2):
694         return (e1[0] == e2[0] and e1[1] == e2[1]) or (e1[0] == e2[1] and e1[1] == e2[0])
695
696     # XXX slow
697 #     if CREATE_FGONS and fgon_edges:
698 #         for fgon_edge in fgon_edges.keys():
699 #             for ed in me.edges:
700 #                 if edges_match(fgon_edge, ed.vertices):
701 #                     ed.is_fgon = True
702
703 #     if CREATE_FGONS and fgon_edges:
704 #         FGON= Mesh.EdgeFlags.FGON
705 #         for ed in me.findEdges( fgon_edges.keys() ):
706 #             if ed!=None:
707 #                 me_edges[ed].flag |= FGON
708 #         del FGON
709
710     # XXX slow
711 #     if unique_smooth_groups and sharp_edges:
712 #         for sharp_edge in sharp_edges.keys():
713 #             for ed in me.edges:
714 #                 if edges_match(sharp_edge, ed.vertices):
715 #                     ed.use_edge_sharp = True
716
717 #     if unique_smooth_groups and sharp_edges:
718 #         SHARP= Mesh.EdgeFlags.SHARP
719 #         for ed in me.findEdges( sharp_edges.keys() ):
720 #             if ed!=None:
721 #                 me_edges[ed].flag |= SHARP
722 #         del SHARP
723
724     me.update()
725 #     me.calcNormals()
726
727     ob= bpy.data.objects.new("Mesh", me)
728     new_objects.append(ob)
729
730     # Create the vertex groups. No need to have the flag passed here since we test for the
731     # content of the vertex_groups. If the user selects to NOT have vertex groups saved then
732     # the following test will never run
733     for group_name, group_indicies in vertex_groups.items():
734         group= ob.vertex_groups.new(group_name)
735         ob.vertex_groups.assign(group_indicies, group, 1.0, 'REPLACE')
736
737
738 def create_nurbs(context_nurbs, vert_loc, new_objects):
739     '''
740     Add nurbs object to blender, only support one type at the moment
741     '''
742     deg = context_nurbs.get('deg', (3,))
743     curv_range = context_nurbs.get('curv_range')
744     curv_idx = context_nurbs.get('curv_idx', [])
745     parm_u = context_nurbs.get('parm_u', [])
746     parm_v = context_nurbs.get('parm_v', [])
747     name = context_nurbs.get('name', 'ObjNurb')
748     cstype = context_nurbs.get('cstype')
749
750     if cstype == None:
751         print('\tWarning, cstype not found')
752         return
753     if cstype != 'bspline':
754         print('\tWarning, cstype is not supported (only bspline)')
755         return
756     if not curv_idx:
757         print('\tWarning, curv argument empty or not set')
758         return
759     if len(deg) > 1 or parm_v:
760         print('\tWarning, surfaces not supported')
761         return
762
763     cu = bpy.data.curves.new(name, 'CURVE')
764     cu.dimensions = '3D'
765
766     nu = cu.splines.new('NURBS')
767     nu.points.add(len(curv_idx) - 1) # a point is added to start with
768     nu.points.foreach_set("co", [co_axis for vt_idx in curv_idx for co_axis in (vert_loc[vt_idx] + (1.0,))])
769
770     nu.order_u = deg[0] + 1
771
772     # get for endpoint flag from the weighting
773     if curv_range and len(parm_u) > deg[0]+1:
774         do_endpoints = True
775         for i in range(deg[0]+1):
776
777             if abs(parm_u[i]-curv_range[0]) > 0.0001:
778                 do_endpoints = False
779                 break
780
781             if abs(parm_u[-(i+1)]-curv_range[1]) > 0.0001:
782                 do_endpoints = False
783                 break
784
785     else:
786         do_endpoints = False
787
788     if do_endpoints:
789         nu.use_endpoint_u = True
790
791
792     # close
793     '''
794     do_closed = False
795     if len(parm_u) > deg[0]+1:
796         for i in xrange(deg[0]+1):
797             #print curv_idx[i], curv_idx[-(i+1)]
798
799             if curv_idx[i]==curv_idx[-(i+1)]:
800                 do_closed = True
801                 break
802
803     if do_closed:
804         nu.use_cyclic_u = True
805     '''
806     
807     ob= bpy.data.objects.new("Nurb", cu)
808
809     new_objects.append(ob)
810
811
812 def strip_slash(line_split):
813     if line_split[-1][-1]== '\\':
814         if len(line_split[-1])==1:
815             line_split.pop() # remove the \ item
816         else:
817             line_split[-1]= line_split[-1][:-1] # remove the \ from the end last number
818         return True
819     return False
820
821
822
823 def get_float_func(filepath):
824     '''
825     find the float function for this obj file
826     - whether to replace commas or not
827     '''
828     file= open(filepath, 'rU')
829     for line in file: #.xreadlines():
830         line = line.lstrip()
831         if line.startswith('v'): # vn vt v
832             if ',' in line:
833                 return lambda f: float(f.replace(',', '.'))
834             elif '.' in line:
835                 return float
836
837     # incase all vert values were ints
838     return float
839
840 def load(operator, context, filepath,
841          CLAMP_SIZE= 0.0,
842          CREATE_FGONS= True,
843          CREATE_SMOOTH_GROUPS= True,
844          CREATE_EDGES= True,
845          SPLIT_OBJECTS= True,
846          SPLIT_GROUPS= True,
847          ROTATE_X90= True,
848          IMAGE_SEARCH=True,
849          POLYGROUPS=False):
850     '''
851     Called by the user interface or another script.
852     load_obj(path) - should give acceptable results.
853     This function passes the file and sends the data off
854         to be split into objects and then converted into mesh objects
855     '''
856     print('\nimporting obj %r' % filepath)
857
858     if SPLIT_OBJECTS or SPLIT_GROUPS:
859         POLYGROUPS = False
860
861     time_main= time.time()
862 #     time_main= sys.time()
863
864     verts_loc= []
865     verts_tex= []
866     faces= [] # tuples of the faces
867     material_libs= [] # filanems to material libs this uses
868     vertex_groups = {} # when POLYGROUPS is true
869
870     # Get the string to float conversion func for this file- is 'float' for almost all files.
871     float_func= get_float_func(filepath)
872
873     # Context variables
874     context_material= None
875     context_smooth_group= None
876     context_object= None
877     context_vgroup = None
878
879     # Nurbs
880     context_nurbs = {}
881     nurbs = []
882     context_parm = '' # used by nurbs too but could be used elsewhere
883
884     has_ngons= False
885     # has_smoothgroups= False - is explicit with len(unique_smooth_groups) being > 0
886
887     # Until we can use sets
888     unique_materials= {}
889     unique_material_images= {}
890     unique_smooth_groups= {}
891     # unique_obects= {} - no use for this variable since the objects are stored in the face.
892
893     # when there are faces that end with \
894     # it means they are multiline-
895     # since we use xreadline we cant skip to the next line
896     # so we need to know whether
897     context_multi_line= ''
898
899     print("\tparsing obj file...")
900     time_sub= time.time()
901 #     time_sub= sys.time()
902
903     file= open(filepath, 'rU')
904     for line in file: #.xreadlines():
905         line = line.lstrip() # rare cases there is white space at the start of the line
906
907         if line.startswith('v '):
908             line_split= line.split()
909             # rotate X90: (x,-z,y)
910             verts_loc.append( (float_func(line_split[1]), -float_func(line_split[3]), float_func(line_split[2])) )
911
912         elif line.startswith('vn '):
913             pass
914
915         elif line.startswith('vt '):
916             line_split= line.split()
917             verts_tex.append( (float_func(line_split[1]), float_func(line_split[2])) )
918
919         # Handel faces lines (as faces) and the second+ lines of fa multiline face here
920         # use 'f' not 'f ' because some objs (very rare have 'fo ' for faces)
921         elif line.startswith('f') or context_multi_line == 'f':
922
923             if context_multi_line:
924                 # use face_vert_loc_indicies and face_vert_tex_indicies previously defined and used the obj_face
925                 line_split= line.split()
926
927             else:
928                 line_split= line[2:].split()
929                 face_vert_loc_indicies= []
930                 face_vert_tex_indicies= []
931
932                 # Instance a face
933                 faces.append((\
934                 face_vert_loc_indicies,\
935                 face_vert_tex_indicies,\
936                 context_material,\
937                 context_smooth_group,\
938                 context_object\
939                 ))
940
941             if strip_slash(line_split):
942                 context_multi_line = 'f'
943             else:
944                 context_multi_line = ''
945
946             for v in line_split:
947                 obj_vert= v.split('/')
948
949                 vert_loc_index= int(obj_vert[0])-1
950                 # Add the vertex to the current group
951                 # *warning*, this wont work for files that have groups defined around verts
952                 if    POLYGROUPS and context_vgroup:
953                     vertex_groups[context_vgroup].append(vert_loc_index)
954
955                 # Make relative negative vert indicies absolute
956                 if vert_loc_index < 0:
957                     vert_loc_index= len(verts_loc) + vert_loc_index + 1
958
959                 face_vert_loc_indicies.append(vert_loc_index)
960
961                 if len(obj_vert)>1 and obj_vert[1]:
962                     # formatting for faces with normals and textures us
963                     # loc_index/tex_index/nor_index
964
965                     vert_tex_index= int(obj_vert[1])-1
966                     # Make relative negative vert indicies absolute
967                     if vert_tex_index < 0:
968                         vert_tex_index= len(verts_tex) + vert_tex_index + 1
969
970                     face_vert_tex_indicies.append(vert_tex_index)
971                 else:
972                     # dummy
973                     face_vert_tex_indicies.append(0)
974
975             if len(face_vert_loc_indicies) > 4:
976                 has_ngons= True
977
978         elif CREATE_EDGES and (line.startswith('l ') or context_multi_line == 'l'):
979             # very similar to the face load function above with some parts removed
980
981             if context_multi_line:
982                 # use face_vert_loc_indicies and face_vert_tex_indicies previously defined and used the obj_face
983                 line_split= line.split()
984
985             else:
986                 line_split= line[2:].split()
987                 face_vert_loc_indicies= []
988                 face_vert_tex_indicies= []
989
990                 # Instance a face
991                 faces.append((\
992                 face_vert_loc_indicies,\
993                 face_vert_tex_indicies,\
994                 context_material,\
995                 context_smooth_group,\
996                 context_object\
997                 ))
998
999             if strip_slash(line_split):
1000                 context_multi_line = 'l'
1001             else:
1002                 context_multi_line = ''
1003
1004             isline= line.startswith('l')
1005
1006             for v in line_split:
1007                 vert_loc_index= int(v)-1
1008
1009                 # Make relative negative vert indicies absolute
1010                 if vert_loc_index < 0:
1011                     vert_loc_index= len(verts_loc) + vert_loc_index + 1
1012
1013                 face_vert_loc_indicies.append(vert_loc_index)
1014
1015         elif line.startswith('s'):
1016             if CREATE_SMOOTH_GROUPS:
1017                 context_smooth_group= line_value(line.split())
1018                 if context_smooth_group=='off':
1019                     context_smooth_group= None
1020                 elif context_smooth_group: # is not None
1021                     unique_smooth_groups[context_smooth_group]= None
1022
1023         elif line.startswith('o'):
1024             if SPLIT_OBJECTS:
1025                 context_object= line_value(line.split())
1026                 # unique_obects[context_object]= None
1027
1028         elif line.startswith('g'):
1029             if SPLIT_GROUPS:
1030                 context_object= line_value(line.split())
1031                 # print 'context_object', context_object
1032                 # unique_obects[context_object]= None
1033             elif POLYGROUPS:
1034                 context_vgroup = line_value(line.split())
1035                 if context_vgroup and context_vgroup != '(null)':
1036                     vertex_groups.setdefault(context_vgroup, [])
1037                 else:
1038                     context_vgroup = None # dont assign a vgroup
1039
1040         elif line.startswith('usemtl'):
1041             context_material= line_value(line.split())
1042             unique_materials[context_material]= None
1043         elif line.startswith('mtllib'): # usemap or usemat
1044             material_libs = list(set(material_libs) | set(line.split()[1:])) # can have multiple mtllib filenames per line, mtllib can appear more than once, so make sure only occurance of material exists
1045
1046             # Nurbs support
1047         elif line.startswith('cstype '):
1048             context_nurbs['cstype']= line_value(line.split()) # 'rat bspline' / 'bspline'
1049         elif line.startswith('curv ') or context_multi_line == 'curv':
1050             line_split= line.split()
1051
1052             curv_idx = context_nurbs['curv_idx'] = context_nurbs.get('curv_idx', []) # incase were multiline
1053
1054             if not context_multi_line:
1055                 context_nurbs['curv_range'] = float_func(line_split[1]), float_func(line_split[2])
1056                 line_split[0:3] = [] # remove first 3 items
1057
1058             if strip_slash(line_split):
1059                 context_multi_line = 'curv'
1060             else:
1061                 context_multi_line = ''
1062
1063
1064             for i in line_split:
1065                 vert_loc_index = int(i)-1
1066
1067                 if vert_loc_index < 0:
1068                     vert_loc_index= len(verts_loc) + vert_loc_index + 1
1069
1070                 curv_idx.append(vert_loc_index)
1071
1072         elif line.startswith('parm') or context_multi_line == 'parm':
1073             line_split= line.split()
1074
1075             if context_multi_line:
1076                 context_multi_line = ''
1077             else:
1078                 context_parm = line_split[1]
1079                 line_split[0:2] = [] # remove first 2
1080
1081             if strip_slash(line_split):
1082                 context_multi_line = 'parm'
1083             else:
1084                 context_multi_line = ''
1085
1086             if context_parm.lower() == 'u':
1087                 context_nurbs.setdefault('parm_u', []).extend( [float_func(f) for f in line_split] )
1088             elif context_parm.lower() == 'v': # surfaces not suported yet
1089                 context_nurbs.setdefault('parm_v', []).extend( [float_func(f) for f in line_split] )
1090             # else: # may want to support other parm's ?
1091
1092         elif line.startswith('deg '):
1093             context_nurbs['deg']= [int(i) for i in line.split()[1:]]
1094         elif line.startswith('end'):
1095             # Add the nurbs curve
1096             if context_object:
1097                 context_nurbs['name'] = context_object
1098             nurbs.append(context_nurbs)
1099             context_nurbs = {}
1100             context_parm = ''
1101
1102         ''' # How to use usemap? depricated?
1103         elif line.startswith('usema'): # usemap or usemat
1104             context_image= line_value(line.split())
1105         '''
1106
1107     file.close()
1108     time_new= time.time()
1109 #     time_new= sys.time()
1110     print('%.4f sec' % (time_new-time_sub))
1111     time_sub= time_new
1112
1113
1114     print('\tloading materials and images...')
1115     create_materials(filepath, material_libs, unique_materials, unique_material_images, IMAGE_SEARCH)
1116
1117     time_new= time.time()
1118 #     time_new= sys.time()
1119     print('%.4f sec' % (time_new-time_sub))
1120     time_sub= time_new
1121
1122     if not ROTATE_X90:
1123         verts_loc[:] = [(v[0], v[2], -v[1]) for v in verts_loc]
1124
1125     # deselect all
1126     bpy.ops.object.select_all(action='DESELECT')
1127
1128     scene = context.scene
1129 #     scn.objects.selected = []
1130     new_objects= [] # put new objects here
1131
1132     print('\tbuilding geometry...\n\tverts:%i faces:%i materials: %i smoothgroups:%i ...' % ( len(verts_loc), len(faces), len(unique_materials), len(unique_smooth_groups) ))
1133     # Split the mesh by objects/materials, may
1134     if SPLIT_OBJECTS or SPLIT_GROUPS:    SPLIT_OB_OR_GROUP = True
1135     else:                                SPLIT_OB_OR_GROUP = False
1136
1137     for verts_loc_split, faces_split, unique_materials_split, dataname in split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
1138         # Create meshes from the data, warning 'vertex_groups' wont support splitting
1139         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)
1140
1141     # nurbs support
1142     for context_nurbs in nurbs:
1143         create_nurbs(context_nurbs, verts_loc, new_objects)
1144
1145     # Create new obj
1146     for obj in new_objects:
1147         base = scene.objects.link(obj)
1148         base.select = True
1149
1150     scene.update()
1151
1152
1153     axis_min= [ 1000000000]*3
1154     axis_max= [-1000000000]*3
1155
1156 #     if CLAMP_SIZE:
1157 #         # Get all object bounds
1158 #         for ob in new_objects:
1159 #             for v in ob.getBoundBox():
1160 #                 for axis, value in enumerate(v):
1161 #                     if axis_min[axis] > value:    axis_min[axis]= value
1162 #                     if axis_max[axis] < value:    axis_max[axis]= value
1163
1164 #         # Scale objects
1165 #         max_axis= max(axis_max[0]-axis_min[0], axis_max[1]-axis_min[1], axis_max[2]-axis_min[2])
1166 #         scale= 1.0
1167
1168 #         while CLAMP_SIZE < max_axis * scale:
1169 #             scale= scale/10.0
1170
1171 #         for ob in new_objects:
1172 #             ob.setSize(scale, scale, scale)
1173
1174     # Better rotate the vert locations
1175     #if not ROTATE_X90:
1176     #    for ob in new_objects:
1177     #        ob.RotX = -1.570796326794896558
1178
1179     time_new= time.time()
1180 #    time_new= sys.time()
1181
1182     print('finished importing: %r in %.4f sec.' % (filepath, (time_new-time_main)))
1183     return {'FINISHED'}
1184
1185
1186 # NOTES (all line numbers refer to 2.4x import_obj.py, not this file)
1187 # check later: line 489
1188 # can convert now: edge flags, edges: lines 508-528
1189 # ngon (uses python module BPyMesh): 384-414
1190 # NEXT clamp size: get bound box with RNA
1191 # get back to l 140 (here)
1192 # search image in bpy.config.textureDir - load_image
1193 # replaced BPyImage.comprehensiveImageLoad with a simplified version that only checks additional directory specified, but doesn't search dirs recursively (obj_image_load)
1194 # bitmask won't work? - 132
1195 # uses bpy.sys.time()
1196
1197 if __name__ == "__main__":
1198     register()