Tag addons for blender 2.59 release
[blender-addons.git] / io_scene_obj / import_obj.py
blob06304dea51baea318234651a3e694cbe0df2b9f5
1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 # Script copyright (C) Campbell Barton
22 # Contributors: Campbell Barton, Jiri Hnidek, Paolo Ciccone
24 """
25 This script imports a Wavefront OBJ files to Blender.
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.
31 http://wiki.blender.org/index.php/Scripts/Manual/Import/wavefront_obj
32 """
34 import os
35 import time
36 import bpy
37 import mathutils
38 from bpy_extras.io_utils import unpack_list, unpack_face_list
39 from bpy_extras.image_utils import load_image
42 def line_value(line_split):
43 '''
44 Returns 1 string represneting the value for this line
45 None will be returned if theres only 1 word
46 '''
47 length = len(line_split)
48 if length == 1:
49 return None
51 elif length == 2:
52 return line_split[1]
54 elif length > 2:
55 return b' '.join(line_split[1:])
58 def obj_image_load(imagepath, DIR, use_image_search):
59 if b'_' in imagepath:
60 image = load_image(imagepath.replace(b'_', b' '), DIR)
61 if image:
62 return image
64 image = load_image(imagepath, DIR)
65 if image:
66 return image
68 print("failed to load %r doesn't exist" % imagepath)
69 return None
71 # def obj_image_load(imagepath, DIR, use_image_search):
72 # '''
73 # Mainly uses comprehensiveImageLoad
74 # but tries to replace '_' with ' ' for Max's exporter replaces spaces with underscores.
75 # '''
77 # if '_' in imagepath:
78 # image= BPyImage.comprehensiveImageLoad(imagepath, DIR, PLACE_HOLDER= False, RECURSIVE= use_image_search)
79 # if image: return image
80 # # Did the exporter rename the image?
81 # image= BPyImage.comprehensiveImageLoad(imagepath.replace('_', ' '), DIR, PLACE_HOLDER= False, RECURSIVE= use_image_search)
82 # if image: return image
84 # # Return an image, placeholder if it dosnt exist
85 # image= BPyImage.comprehensiveImageLoad(imagepath, DIR, PLACE_HOLDER= True, RECURSIVE= use_image_search)
86 # return image
89 def create_materials(filepath, material_libs, unique_materials, unique_material_images, use_image_search):
90 '''
91 Create all the used materials in this obj,
92 assign colors and images to the materials from all referenced material libs
93 '''
94 DIR = os.path.dirname(filepath)
96 #==================================================================================#
97 # This function sets textures defined in .mtl file #
98 #==================================================================================#
99 def load_material_image(blender_material, context_material_name, imagepath, type):
101 texture = bpy.data.textures.new(name=type, type='IMAGE')
103 # Absolute path - c:\.. etc would work here
104 image = obj_image_load(imagepath, DIR, use_image_search)
105 has_data = False
107 if image:
108 texture.image = image
109 has_data = image.has_data
111 # Adds textures for materials (rendering)
112 if type == 'Kd':
113 if has_data and image.depth == 32:
114 # Image has alpha
116 mtex = blender_material.texture_slots.add()
117 mtex.texture = texture
118 mtex.texture_coords = 'UV'
119 mtex.use_map_color_diffuse = True
120 mtex.use_map_alpha = True
122 texture.use_mipmap = True
123 texture.use_interpolation = True
124 texture.use_alpha = True
125 blender_material.use_transparency = True
126 blender_material.alpha = 0.0
127 else:
128 mtex = blender_material.texture_slots.add()
129 mtex.texture = texture
130 mtex.texture_coords = 'UV'
131 mtex.use_map_color_diffuse = True
133 # adds textures to faces (Textured/Alt-Z mode)
134 # Only apply the diffuse texture to the face if the image has not been set with the inline usemat func.
135 unique_material_images[context_material_name] = image, has_data # set the texface image
137 elif type == 'Ka':
138 mtex = blender_material.texture_slots.add()
139 mtex.use_map_color_diffuse = False
141 mtex.texture = texture
142 mtex.texture_coords = 'UV'
143 mtex.use_map_ambient = True
145 elif type == 'Ks':
146 mtex = blender_material.texture_slots.add()
147 mtex.use_map_color_diffuse = False
149 mtex.texture = texture
150 mtex.texture_coords = 'UV'
151 mtex.use_map_specular = True
153 elif type == 'Bump':
154 mtex = blender_material.texture_slots.add()
155 mtex.use_map_color_diffuse = False
157 mtex.texture = texture
158 mtex.texture_coords = 'UV'
159 mtex.use_map_normal = True
161 elif type == 'D':
162 mtex = blender_material.texture_slots.add()
163 mtex.use_map_color_diffuse = False
165 mtex.texture = texture
166 mtex.texture_coords = 'UV'
167 mtex.use_map_alpha = True
168 blender_material.use_transparency = True
169 blender_material.transparency_method = 'Z_TRANSPARENCY'
170 blender_material.alpha = 0.0
171 # Todo, unset deffuse material alpha if it has an alpha channel
173 elif type == 'refl':
174 mtex = blender_material.texture_slots.add()
175 mtex.use_map_color_diffuse = False
177 mtex.texture = texture
178 mtex.texture_coords = 'UV'
179 mtex.use_map_reflect = True
181 else:
182 raise Exception("invalid type %r" % type)
184 # Add an MTL with the same name as the obj if no MTLs are spesified.
185 temp_mtl = os.path.splitext((os.path.basename(filepath)))[0] + b'.mtl'
187 if os.path.exists(os.path.join(DIR, temp_mtl)) and temp_mtl not in material_libs:
188 material_libs.append(temp_mtl)
189 del temp_mtl
191 #Create new materials
192 for name in unique_materials: # .keys()
193 if name is not None:
194 unique_materials[name] = bpy.data.materials.new(name.decode('utf-8', "replace"))
195 unique_material_images[name] = None, False # assign None to all material images to start with, add to later.
197 unique_materials[None] = None
198 unique_material_images[None] = None, False
200 for libname in material_libs:
201 # print(libname)
202 mtlpath = os.path.join(DIR, libname)
203 if not os.path.exists(mtlpath):
204 print ("\tMaterial not found MTL: %r" % mtlpath)
205 else:
206 #print('\t\tloading mtl: %e' % mtlpath)
207 context_material = None
208 mtl = open(mtlpath, 'rb')
209 for line in mtl: # .readlines():
210 line = line.strip()
211 if not line or line.startswith(b'#'):
212 pass
213 elif line.startswith(b'newmtl'):
214 context_material_name = line_value(line.split())
215 context_material = unique_materials.get(context_material_name)
217 elif context_material:
218 # we need to make a material to assign properties to it.
219 line_split = line.split()
220 line_lower = line.lower().lstrip()
221 if line_lower.startswith(b'ka'):
222 context_material.mirror_color = float(line_split[1]), float(line_split[2]), float(line_split[3])
223 elif line_lower.startswith(b'kd'):
224 context_material.diffuse_color = float(line_split[1]), float(line_split[2]), float(line_split[3])
225 elif line_lower.startswith(b'ks'):
226 context_material.specular_color = float(line_split[1]), float(line_split[2]), float(line_split[3])
227 elif line_lower.startswith(b'ns'):
228 context_material.specular_hardness = int((float(line_split[1]) * 0.51))
229 elif line_lower.startswith(b'ni'): # Refraction index
230 context_material.raytrace_transparency.ior = max(1, min(float(line_split[1]), 3)) # between 1 and 3
231 elif line_lower.startswith(b'd') or line_lower.startswith(b'tr'):
232 context_material.alpha = float(line_split[1])
233 context_material.use_transparency = True
234 context_material.transparency_method = 'Z_TRANSPARENCY'
235 elif line_lower.startswith(b'tf'):
236 # rgb, filter color, blender has no support for this.
237 pass
238 elif line_lower.startswith(b'illum'):
239 illum = int(line_split[1])
241 do_ambient = True
242 do_highlight = False
243 do_reflection = False
244 do_transparency = False
245 do_glass = False
246 do_fresnel = False
247 do_raytrace = False
249 # inline comments are from the spec, v4.2
250 if illum == 0:
251 # Color on and Ambient off
252 do_ambient = False
253 elif illum == 1:
254 # Color on and Ambient on
255 pass
256 elif illum == 2:
257 # Highlight on
258 do_highlight = True
259 elif illum == 3:
260 # Reflection on and Ray trace on
261 do_reflection = True
262 do_raytrace = True
263 elif illum == 4:
264 # Transparency: Glass on
265 # Reflection: Ray trace on
266 do_transparency = True
267 do_reflection = True
268 do_glass = True
269 do_raytrace = True
270 elif illum == 5:
271 # Reflection: Fresnel on and Ray trace on
272 do_reflection = True
273 do_fresnel = True
274 do_raytrace = True
275 elif illum == 6:
276 # Transparency: Refraction on
277 # Reflection: Fresnel off and Ray trace on
278 do_transparency = True
279 do_reflection = True
280 do_raytrace = True
281 elif illum == 7:
282 # Transparency: Refraction on
283 # Reflection: Fresnel on and Ray trace on
284 do_transparency = True
285 do_reflection = True
286 do_fresnel = True
287 do_raytrace = True
288 elif illum == 8:
289 # Reflection on and Ray trace off
290 do_reflection = True
291 elif illum == 9:
292 # Transparency: Glass on
293 # Reflection: Ray trace off
294 do_transparency = True
295 do_reflection = True
296 do_glass = True
297 elif illum == 10:
298 # Casts shadows onto invisible surfaces
300 # blender cant do this
301 pass
303 if do_ambient:
304 context_material.ambient = 1.0
305 else:
306 context_material.ambient = 0.0
308 if do_highlight:
309 # FIXME, how else to use this?
310 context_material.specular_intensity = 1.0
312 if do_reflection:
313 context_material.raytrace_mirror.use = True
314 context_material.raytrace_mirror.reflect_factor = 1.0
316 if do_transparency:
317 context_material.use_transparency = True
318 context_material.transparency_method = 'RAYTRACE' if do_raytrace else 'Z_TRANSPARENCY'
319 context_material.alpha = 0.0
321 if do_glass:
322 context_material.raytrace_transparency.ior = 1.5
324 if do_fresnel:
325 context_material.raytrace_mirror.fresnel = 1.0 # could be any value for 'ON'
328 if do_raytrace:
329 context_material.use_raytrace = True
330 else:
331 context_material.use_raytrace = False
333 # XXX, this is not following the OBJ spec, but this was
334 # written when raytracing wasnt default, annoying to disable for blender users.
335 context_material.use_raytrace = True
337 elif line_lower.startswith(b'map_ka'):
338 img_filepath = line_value(line.split())
339 if img_filepath:
340 load_material_image(context_material, context_material_name, img_filepath, 'Ka')
341 elif line_lower.startswith(b'map_ks'):
342 img_filepath = line_value(line.split())
343 if img_filepath:
344 load_material_image(context_material, context_material_name, img_filepath, 'Ks')
345 elif line_lower.startswith(b'map_kd'):
346 img_filepath = line_value(line.split())
347 if img_filepath:
348 load_material_image(context_material, context_material_name, img_filepath, 'Kd')
349 elif line_lower.startswith(b'map_bump') or line_lower.startswith(b'bump'): # 'bump' is incorrect but some files use it.
350 img_filepath = line_value(line.split())
351 if img_filepath:
352 load_material_image(context_material, context_material_name, img_filepath, 'Bump')
353 elif line_lower.startswith(b'map_d') or line_lower.startswith(b'map_tr'): # Alpha map - Dissolve
354 img_filepath = line_value(line.split())
355 if img_filepath:
356 load_material_image(context_material, context_material_name, img_filepath, 'D')
358 elif line_lower.startswith(b'refl'): # reflectionmap
359 img_filepath = line_value(line.split())
360 if img_filepath:
361 load_material_image(context_material, context_material_name, img_filepath, 'refl')
362 else:
363 print("\t%r:%r (ignored)" % (filepath, line))
364 mtl.close()
367 def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
369 Takes vert_loc and faces, and separates into multiple sets of
370 (verts_loc, faces, unique_materials, dataname)
373 filename = os.path.splitext((os.path.basename(filepath)))[0]
375 if not SPLIT_OB_OR_GROUP:
376 # use the filename for the object name since we arnt chopping up the mesh.
377 return [(verts_loc, faces, unique_materials, filename)]
379 def key_to_name(key):
380 # if the key is a tuple, join it to make a string
381 if not key:
382 return filename # assume its a string. make sure this is true if the splitting code is changed
383 else:
384 return key
386 # Return a key that makes the faces unique.
387 face_split_dict = {}
389 oldkey = -1 # initialize to a value that will never match the key
391 for face in faces:
392 key = face[4]
394 if oldkey != key:
395 # Check the key has changed.
396 try:
397 verts_split, faces_split, unique_materials_split, vert_remap = face_split_dict[key]
398 except KeyError:
399 faces_split = []
400 verts_split = []
401 unique_materials_split = {}
402 vert_remap = [-1] * len(verts_loc)
404 face_split_dict[key] = (verts_split, faces_split, unique_materials_split, vert_remap)
406 oldkey = key
408 face_vert_loc_indices = face[0]
410 # Remap verts to new vert list and add where needed
411 for enum, i in enumerate(face_vert_loc_indices):
412 if vert_remap[i] == -1:
413 new_index = len(verts_split)
414 vert_remap[i] = new_index # set the new remapped index so we only add once and can reference next time.
415 face_vert_loc_indices[enum] = new_index # remap to the local index
416 verts_split.append(verts_loc[i]) # add the vert to the local verts
417 else:
418 face_vert_loc_indices[enum] = vert_remap[i] # remap to the local index
420 matname = face[2]
421 if matname and matname not in unique_materials_split:
422 unique_materials_split[matname] = unique_materials[matname]
424 faces_split.append(face)
426 # remove one of the itemas and reorder
427 return [(value[0], value[1], value[2], key_to_name(key)) for key, value in list(face_split_dict.items())]
430 def create_mesh(new_objects,
431 has_ngons,
432 use_ngons,
433 use_edges,
434 verts_loc,
435 verts_tex,
436 faces,
437 unique_materials,
438 unique_material_images,
439 unique_smooth_groups,
440 vertex_groups,
441 dataname,
444 Takes all the data gathered and generates a mesh, adding the new object to new_objects
445 deals with fgons, sharp edges and assigning materials
447 from bpy_extras.mesh_utils import ngon_tesselate
449 if not has_ngons:
450 use_ngons = False
452 if unique_smooth_groups:
453 sharp_edges = {}
454 smooth_group_users = {context_smooth_group: {} for context_smooth_group in list(unique_smooth_groups.keys())}
455 context_smooth_group_old = -1
457 # Split fgons into tri's
458 fgon_edges = {} # Used for storing fgon keys
459 if use_edges:
460 edges = []
462 context_object = None
464 # reverse loop through face indices
465 for f_idx in range(len(faces) - 1, -1, -1):
467 (face_vert_loc_indices,
468 face_vert_tex_indices,
469 context_material,
470 context_smooth_group,
471 context_object,
472 ) = faces[f_idx]
474 len_face_vert_loc_indices = len(face_vert_loc_indices)
476 if len_face_vert_loc_indices == 1:
477 faces.pop(f_idx) # cant add single vert faces
479 elif not face_vert_tex_indices or len_face_vert_loc_indices == 2: # faces that have no texture coords are lines
480 if use_edges:
481 # generators are better in python 2.4+ but can't be used in 2.3
482 # edges.extend( (face_vert_loc_indices[i], face_vert_loc_indices[i+1]) for i in xrange(len_face_vert_loc_indices-1) )
483 edges.extend([(face_vert_loc_indices[i], face_vert_loc_indices[i + 1]) for i in range(len_face_vert_loc_indices - 1)])
485 faces.pop(f_idx)
486 else:
488 # Smooth Group
489 if unique_smooth_groups and context_smooth_group:
490 # Is a part of of a smooth group and is a face
491 if context_smooth_group_old is not context_smooth_group:
492 edge_dict = smooth_group_users[context_smooth_group]
493 context_smooth_group_old = context_smooth_group
495 for i in range(len_face_vert_loc_indices):
496 i1 = face_vert_loc_indices[i]
497 i2 = face_vert_loc_indices[i - 1]
498 if i1 > i2:
499 i1, i2 = i2, i1
501 try:
502 edge_dict[i1, i2] += 1
503 except KeyError:
504 edge_dict[i1, i2] = 1
506 # FGons into triangles
507 if has_ngons and len_face_vert_loc_indices > 4:
509 ngon_face_indices = ngon_tesselate(verts_loc, face_vert_loc_indices)
510 faces.extend([([face_vert_loc_indices[ngon[0]],
511 face_vert_loc_indices[ngon[1]],
512 face_vert_loc_indices[ngon[2]],
514 [face_vert_tex_indices[ngon[0]],
515 face_vert_tex_indices[ngon[1]],
516 face_vert_tex_indices[ngon[2]],
518 context_material,
519 context_smooth_group,
520 context_object,
522 for ngon in ngon_face_indices]
525 # edges to make fgons
526 if use_ngons:
527 edge_users = {}
528 for ngon in ngon_face_indices:
529 for i in (0, 1, 2):
530 i1 = face_vert_loc_indices[ngon[i]]
531 i2 = face_vert_loc_indices[ngon[i - 1]]
532 if i1 > i2:
533 i1, i2 = i2, i1
535 try:
536 edge_users[i1, i2] += 1
537 except KeyError:
538 edge_users[i1, i2] = 1
540 for key, users in edge_users.items():
541 if users > 1:
542 fgon_edges[key] = None
544 # remove all after 3, means we dont have to pop this one.
545 faces.pop(f_idx)
547 # Build sharp edges
548 if unique_smooth_groups:
549 for edge_dict in list(smooth_group_users.values()):
550 for key, users in list(edge_dict.items()):
551 if users == 1: # This edge is on the boundry of a group
552 sharp_edges[key] = None
554 # map the material names to an index
555 material_mapping = {name: i for i, name in enumerate(unique_materials)} # enumerate over unique_materials keys()
557 materials = [None] * len(unique_materials)
559 for name, index in list(material_mapping.items()):
560 materials[index] = unique_materials[name]
562 me = bpy.data.meshes.new(dataname.decode('utf-8', "replace"))
564 # make sure the list isnt too big
565 for material in materials:
566 me.materials.append(material)
568 me.vertices.add(len(verts_loc))
569 me.faces.add(len(faces))
571 # verts_loc is a list of (x, y, z) tuples
572 me.vertices.foreach_set("co", unpack_list(verts_loc))
574 # faces is a list of (vert_indices, texco_indices, ...) tuples
575 # XXX faces should contain either 3 or 4 verts
576 # XXX no check for valid face indices
577 me.faces.foreach_set("vertices_raw", unpack_face_list([f[0] for f in faces]))
579 if verts_tex and me.faces:
580 me.uv_textures.new()
582 context_material_old = -1 # avoid a dict lookup
583 mat = 0 # rare case it may be un-initialized.
584 me_faces = me.faces
586 for i, face in enumerate(faces):
587 if len(face[0]) < 2:
588 pass # raise "bad face"
589 elif len(face[0]) == 2:
590 if use_edges:
591 edges.append(face[0])
592 else:
594 blender_face = me.faces[i]
596 (face_vert_loc_indices,
597 face_vert_tex_indices,
598 context_material,
599 context_smooth_group,
600 context_object,
601 ) = face
603 if context_smooth_group:
604 blender_face.use_smooth = True
606 if context_material:
607 if context_material_old is not context_material:
608 mat = material_mapping[context_material]
609 context_material_old = context_material
611 blender_face.material_index = mat
612 # blender_face.mat= mat
614 if verts_tex:
616 blender_tface = me.uv_textures[0].data[i]
618 if context_material:
619 image, has_data = unique_material_images[context_material]
620 if image: # Can be none if the material dosnt have an image.
621 blender_tface.image = image
622 blender_tface.use_image = True
623 if has_data and image.depth == 32:
624 blender_tface.blend_type = 'ALPHA'
626 # BUG - Evil eekadoodle problem where faces that have vert index 0 location at 3 or 4 are shuffled.
627 if len(face_vert_loc_indices) == 4:
628 if face_vert_loc_indices[2] == 0 or face_vert_loc_indices[3] == 0:
629 face_vert_tex_indices = face_vert_tex_indices[2], face_vert_tex_indices[3], face_vert_tex_indices[0], face_vert_tex_indices[1]
630 else: # length of 3
631 if face_vert_loc_indices[2] == 0:
632 face_vert_tex_indices = face_vert_tex_indices[1], face_vert_tex_indices[2], face_vert_tex_indices[0]
633 # END EEEKADOODLE FIX
635 # assign material, uv's and image
636 blender_tface.uv1 = verts_tex[face_vert_tex_indices[0]]
637 blender_tface.uv2 = verts_tex[face_vert_tex_indices[1]]
638 blender_tface.uv3 = verts_tex[face_vert_tex_indices[2]]
640 if len(face_vert_loc_indices) == 4:
641 blender_tface.uv4 = verts_tex[face_vert_tex_indices[3]]
643 # for ii, uv in enumerate(blender_face.uv):
644 # uv.x, uv.y= verts_tex[face_vert_tex_indices[ii]]
645 del me_faces
646 # del ALPHA
648 if use_edges and not edges:
649 use_edges = False
651 if use_edges:
652 me.edges.add(len(edges))
654 # edges should be a list of (a, b) tuples
655 me.edges.foreach_set("vertices", unpack_list(edges))
656 # me_edges.extend( edges )
658 # del me_edges
660 # Add edge faces.
661 # me_edges= me.edges
663 def edges_match(e1, e2):
664 return (e1[0] == e2[0] and e1[1] == e2[1]) or (e1[0] == e2[1] and e1[1] == e2[0])
666 # XXX slow
667 # if use_ngons and fgon_edges:
668 # for fgon_edge in fgon_edges.keys():
669 # for ed in me.edges:
670 # if edges_match(fgon_edge, ed.vertices):
671 # ed.is_fgon = True
673 # if use_ngons and fgon_edges:
674 # FGON= Mesh.EdgeFlags.FGON
675 # for ed in me.findEdges( fgon_edges.keys() ):
676 # if ed is not None:
677 # me_edges[ed].flag |= FGON
678 # del FGON
680 # XXX slow
681 # if unique_smooth_groups and sharp_edges:
682 # for sharp_edge in sharp_edges.keys():
683 # for ed in me.edges:
684 # if edges_match(sharp_edge, ed.vertices):
685 # ed.use_edge_sharp = True
687 # if unique_smooth_groups and sharp_edges:
688 # SHARP= Mesh.EdgeFlags.SHARP
689 # for ed in me.findEdges( sharp_edges.keys() ):
690 # if ed is not None:
691 # me_edges[ed].flag |= SHARP
692 # del SHARP
694 me.validate()
695 me.update(calc_edges=use_edges)
697 ob = bpy.data.objects.new("Mesh", me)
698 new_objects.append(ob)
700 # Create the vertex groups. No need to have the flag passed here since we test for the
701 # content of the vertex_groups. If the user selects to NOT have vertex groups saved then
702 # the following test will never run
703 for group_name, group_indices in vertex_groups.items():
704 group = ob.vertex_groups.new(group_name.decode('utf-8', "replace"))
705 group.add(group_indices, 1.0, 'REPLACE')
708 def create_nurbs(context_nurbs, vert_loc, new_objects):
710 Add nurbs object to blender, only support one type at the moment
712 deg = context_nurbs.get(b'deg', (3,))
713 curv_range = context_nurbs.get(b'curv_range')
714 curv_idx = context_nurbs.get(b'curv_idx', [])
715 parm_u = context_nurbs.get(b'parm_u', [])
716 parm_v = context_nurbs.get(b'parm_v', [])
717 name = context_nurbs.get(b'name', b'ObjNurb')
718 cstype = context_nurbs.get(b'cstype')
720 if cstype is None:
721 print('\tWarning, cstype not found')
722 return
723 if cstype != b'bspline':
724 print('\tWarning, cstype is not supported (only bspline)')
725 return
726 if not curv_idx:
727 print('\tWarning, curv argument empty or not set')
728 return
729 if len(deg) > 1 or parm_v:
730 print('\tWarning, surfaces not supported')
731 return
733 cu = bpy.data.curves.new(name.decode('utf-8', "replace"), 'CURVE')
734 cu.dimensions = '3D'
736 nu = cu.splines.new('NURBS')
737 nu.points.add(len(curv_idx) - 1) # a point is added to start with
738 nu.points.foreach_set("co", [co_axis for vt_idx in curv_idx for co_axis in (vert_loc[vt_idx] + (1.0,))])
740 nu.order_u = deg[0] + 1
742 # get for endpoint flag from the weighting
743 if curv_range and len(parm_u) > deg[0] + 1:
744 do_endpoints = True
745 for i in range(deg[0] + 1):
747 if abs(parm_u[i] - curv_range[0]) > 0.0001:
748 do_endpoints = False
749 break
751 if abs(parm_u[-(i + 1)] - curv_range[1]) > 0.0001:
752 do_endpoints = False
753 break
755 else:
756 do_endpoints = False
758 if do_endpoints:
759 nu.use_endpoint_u = True
761 # close
763 do_closed = False
764 if len(parm_u) > deg[0]+1:
765 for i in xrange(deg[0]+1):
766 #print curv_idx[i], curv_idx[-(i+1)]
768 if curv_idx[i]==curv_idx[-(i+1)]:
769 do_closed = True
770 break
772 if do_closed:
773 nu.use_cyclic_u = True
776 ob = bpy.data.objects.new(name.decode('utf-8', "replace"), cu)
778 new_objects.append(ob)
781 def strip_slash(line_split):
782 if line_split[-1][-1] == 92: # '\' char
783 if len(line_split[-1]) == 1:
784 line_split.pop() # remove the \ item
785 else:
786 line_split[-1] = line_split[-1][:-1] # remove the \ from the end last number
787 return True
788 return False
791 def get_float_func(filepath):
793 find the float function for this obj file
794 - whether to replace commas or not
796 file = open(filepath, 'rb')
797 for line in file: # .readlines():
798 line = line.lstrip()
799 if line.startswith(b'v'): # vn vt v
800 if b',' in line:
801 file.close()
802 return lambda f: float(f.replace(b',', b'.'))
803 elif b'.' in line:
804 file.close()
805 return float
807 file.close()
808 # incase all vert values were ints
809 return float
812 def load(operator, context, filepath,
813 global_clamp_size=0.0,
814 use_ngons=True,
815 use_smooth_groups=True,
816 use_edges=True,
817 use_split_objects=True,
818 use_split_groups=True,
819 use_image_search=True,
820 use_groups_as_vgroups=False,
821 global_matrix=None,
824 Called by the user interface or another script.
825 load_obj(path) - should give acceptable results.
826 This function passes the file and sends the data off
827 to be split into objects and then converted into mesh objects
829 print('\nimporting obj %r' % filepath)
831 filepath = os.fsencode(filepath)
833 if global_matrix is None:
834 global_matrix = mathutils.Matrix()
836 if use_split_objects or use_split_groups:
837 use_groups_as_vgroups = False
839 time_main = time.time()
841 verts_loc = []
842 verts_tex = []
843 faces = [] # tuples of the faces
844 material_libs = [] # filanems to material libs this uses
845 vertex_groups = {} # when use_groups_as_vgroups is true
847 # Get the string to float conversion func for this file- is 'float' for almost all files.
848 float_func = get_float_func(filepath)
850 # Context variables
851 context_material = None
852 context_smooth_group = None
853 context_object = None
854 context_vgroup = None
856 # Nurbs
857 context_nurbs = {}
858 nurbs = []
859 context_parm = b'' # used by nurbs too but could be used elsewhere
861 has_ngons = False
862 # has_smoothgroups= False - is explicit with len(unique_smooth_groups) being > 0
864 # Until we can use sets
865 unique_materials = {}
866 unique_material_images = {}
867 unique_smooth_groups = {}
868 # unique_obects= {} - no use for this variable since the objects are stored in the face.
870 # when there are faces that end with \
871 # it means they are multiline-
872 # since we use xreadline we cant skip to the next line
873 # so we need to know whether
874 context_multi_line = b''
876 print("\tparsing obj file...")
877 time_sub = time.time()
878 # time_sub= sys.time()
880 file = open(filepath, 'rb')
881 for line in file: # .readlines():
882 line = line.lstrip() # rare cases there is white space at the start of the line
884 if line.startswith(b"v "):
885 line_split = line.split()
886 verts_loc.append((float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])))
888 elif line.startswith(b"vn "):
889 pass
891 elif line.startswith(b"vt "):
892 line_split = line.split()
893 verts_tex.append((float_func(line_split[1]), float_func(line_split[2])))
895 # Handel faces lines (as faces) and the second+ lines of fa multiline face here
896 # use 'f' not 'f ' because some objs (very rare have 'fo ' for faces)
897 elif line.startswith(b'f') or context_multi_line == b'f':
899 if context_multi_line:
900 # use face_vert_loc_indices and face_vert_tex_indices previously defined and used the obj_face
901 line_split = line.split()
903 else:
904 line_split = line[2:].split()
905 face_vert_loc_indices = []
906 face_vert_tex_indices = []
908 # Instance a face
909 faces.append((face_vert_loc_indices,
910 face_vert_tex_indices,
911 context_material,
912 context_smooth_group,
913 context_object,
916 if strip_slash(line_split):
917 context_multi_line = b'f'
918 else:
919 context_multi_line = b''
921 for v in line_split:
922 obj_vert = v.split(b'/')
923 vert_loc_index = int(obj_vert[0]) - 1
924 # Add the vertex to the current group
925 # *warning*, this wont work for files that have groups defined around verts
926 if use_groups_as_vgroups and context_vgroup:
927 vertex_groups[context_vgroup].append(vert_loc_index)
929 # Make relative negative vert indices absolute
930 if vert_loc_index < 0:
931 vert_loc_index = len(verts_loc) + vert_loc_index + 1
933 face_vert_loc_indices.append(vert_loc_index)
935 if len(obj_vert) > 1 and obj_vert[1]:
936 # formatting for faces with normals and textures us
937 # loc_index/tex_index/nor_index
939 vert_tex_index = int(obj_vert[1]) - 1
940 # Make relative negative vert indices absolute
941 if vert_tex_index < 0:
942 vert_tex_index = len(verts_tex) + vert_tex_index + 1
944 face_vert_tex_indices.append(vert_tex_index)
945 else:
946 # dummy
947 face_vert_tex_indices.append(0)
949 if len(face_vert_loc_indices) > 4:
950 has_ngons = True
952 elif use_edges and (line.startswith(b'l ') or context_multi_line == b'l'):
953 # very similar to the face load function above with some parts removed
955 if context_multi_line:
956 # use face_vert_loc_indices and face_vert_tex_indices previously defined and used the obj_face
957 line_split = line.split()
959 else:
960 line_split = line[2:].split()
961 face_vert_loc_indices = []
962 face_vert_tex_indices = []
964 # Instance a face
965 faces.append((face_vert_loc_indices,
966 face_vert_tex_indices,
967 context_material,
968 context_smooth_group,
969 context_object,
972 if strip_slash(line_split):
973 context_multi_line = b'l'
974 else:
975 context_multi_line = b''
977 # isline = line.startswith(b'l') # UNUSED
979 for v in line_split:
980 vert_loc_index = int(v) - 1
982 # Make relative negative vert indices absolute
983 if vert_loc_index < 0:
984 vert_loc_index = len(verts_loc) + vert_loc_index + 1
986 face_vert_loc_indices.append(vert_loc_index)
988 elif line.startswith(b's'):
989 if use_smooth_groups:
990 context_smooth_group = line_value(line.split())
991 if context_smooth_group == b'off':
992 context_smooth_group = None
993 elif context_smooth_group: # is not None
994 unique_smooth_groups[context_smooth_group] = None
996 elif line.startswith(b'o'):
997 if use_split_objects:
998 context_object = line_value(line.split())
999 # unique_obects[context_object]= None
1001 elif line.startswith(b'g'):
1002 if use_split_groups:
1003 context_object = line_value(line.split())
1004 # print 'context_object', context_object
1005 # unique_obects[context_object]= None
1006 elif use_groups_as_vgroups:
1007 context_vgroup = line_value(line.split())
1008 if context_vgroup and context_vgroup != b'(null)':
1009 vertex_groups.setdefault(context_vgroup, [])
1010 else:
1011 context_vgroup = None # dont assign a vgroup
1013 elif line.startswith(b'usemtl'):
1014 context_material = line_value(line.split())
1015 unique_materials[context_material] = None
1016 elif line.startswith(b'mtllib'): # usemap or usemat
1017 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
1019 # Nurbs support
1020 elif line.startswith(b'cstype '):
1021 context_nurbs[b'cstype'] = line_value(line.split()) # 'rat bspline' / 'bspline'
1022 elif line.startswith(b'curv ') or context_multi_line == b'curv':
1023 line_split = line.split()
1025 curv_idx = context_nurbs[b'curv_idx'] = context_nurbs.get(b'curv_idx', []) # incase were multiline
1027 if not context_multi_line:
1028 context_nurbs[b'curv_range'] = float_func(line_split[1]), float_func(line_split[2])
1029 line_split[0:3] = [] # remove first 3 items
1031 if strip_slash(line_split):
1032 context_multi_line = b'curv'
1033 else:
1034 context_multi_line = b''
1036 for i in line_split:
1037 vert_loc_index = int(i) - 1
1039 if vert_loc_index < 0:
1040 vert_loc_index = len(verts_loc) + vert_loc_index + 1
1042 curv_idx.append(vert_loc_index)
1044 elif line.startswith(b'parm') or context_multi_line == b'parm':
1045 line_split = line.split()
1047 if context_multi_line:
1048 context_multi_line = b''
1049 else:
1050 context_parm = line_split[1]
1051 line_split[0:2] = [] # remove first 2
1053 if strip_slash(line_split):
1054 context_multi_line = b'parm'
1055 else:
1056 context_multi_line = b''
1058 if context_parm.lower() == b'u':
1059 context_nurbs.setdefault(b'parm_u', []).extend([float_func(f) for f in line_split])
1060 elif context_parm.lower() == b'v': # surfaces not supported yet
1061 context_nurbs.setdefault(b'parm_v', []).extend([float_func(f) for f in line_split])
1062 # else: # may want to support other parm's ?
1064 elif line.startswith(b'deg '):
1065 context_nurbs[b'deg'] = [int(i) for i in line.split()[1:]]
1066 elif line.startswith(b'end'):
1067 # Add the nurbs curve
1068 if context_object:
1069 context_nurbs[b'name'] = context_object
1070 nurbs.append(context_nurbs)
1071 context_nurbs = {}
1072 context_parm = b''
1074 ''' # How to use usemap? depricated?
1075 elif line.startswith(b'usema'): # usemap or usemat
1076 context_image= line_value(line.split())
1079 file.close()
1080 time_new = time.time()
1081 print("%.4f sec" % (time_new - time_sub))
1082 time_sub = time_new
1084 print('\tloading materials and images...')
1085 create_materials(filepath, material_libs, unique_materials, unique_material_images, use_image_search)
1087 time_new = time.time()
1088 print("%.4f sec" % (time_new - time_sub))
1089 time_sub = time_new
1091 # deselect all
1092 if bpy.ops.object.select_all.poll():
1093 bpy.ops.object.select_all(action='DESELECT')
1095 scene = context.scene
1096 # scn.objects.selected = []
1097 new_objects = [] # put new objects here
1099 print('\tbuilding geometry...\n\tverts:%i faces:%i materials: %i smoothgroups:%i ...' % (len(verts_loc), len(faces), len(unique_materials), len(unique_smooth_groups)))
1100 # Split the mesh by objects/materials, may
1101 if use_split_objects or use_split_groups:
1102 SPLIT_OB_OR_GROUP = True
1103 else:
1104 SPLIT_OB_OR_GROUP = False
1106 for verts_loc_split, faces_split, unique_materials_split, dataname in split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
1107 # Create meshes from the data, warning 'vertex_groups' wont support splitting
1108 create_mesh(new_objects,
1109 has_ngons,
1110 use_ngons,
1111 use_edges,
1112 verts_loc_split,
1113 verts_tex,
1114 faces_split,
1115 unique_materials_split,
1116 unique_material_images,
1117 unique_smooth_groups,
1118 vertex_groups,
1119 dataname,
1122 # nurbs support
1123 for context_nurbs in nurbs:
1124 create_nurbs(context_nurbs, verts_loc, new_objects)
1126 # Create new obj
1127 for obj in new_objects:
1128 base = scene.objects.link(obj)
1129 base.select = True
1131 # we could apply this anywhere before scaling.
1132 obj.matrix_world = global_matrix
1134 scene.update()
1136 axis_min = [1000000000] * 3
1137 axis_max = [-1000000000] * 3
1139 if global_clamp_size:
1140 # Get all object bounds
1141 for ob in new_objects:
1142 for v in ob.bound_box:
1143 for axis, value in enumerate(v):
1144 if axis_min[axis] > value:
1145 axis_min[axis] = value
1146 if axis_max[axis] < value:
1147 axis_max[axis] = value
1149 # Scale objects
1150 max_axis = max(axis_max[0] - axis_min[0], axis_max[1] - axis_min[1], axis_max[2] - axis_min[2])
1151 scale = 1.0
1153 while global_clamp_size < max_axis * scale:
1154 scale = scale / 10.0
1156 for obj in new_objects:
1157 obj.scale = scale, scale, scale
1159 time_new = time.time()
1161 print("finished importing: %r in %.4f sec." % (filepath, (time_new - time_main)))
1162 return {'FINISHED'}
1165 # NOTES (all line numbers refer to 2.4x import_obj.py, not this file)
1166 # check later: line 489
1167 # can convert now: edge flags, edges: lines 508-528
1168 # ngon (uses python module BPyMesh): 384-414
1169 # NEXT clamp size: get bound box with RNA
1170 # get back to l 140 (here)
1171 # search image in bpy.config.textureDir - load_image
1172 # replaced BPyImage.comprehensiveImageLoad with a simplified version that only checks additional directory specified, but doesn't search dirs recursively (obj_image_load)
1173 # bitmask won't work? - 132
1174 # uses bpy.sys.time()