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 #####
21 # Script copyright (C) Campbell Barton
22 # Contributors: Campbell Barton, Jiri Hnidek, Paolo Ciccone
25 This script imports a Wavefront OBJ files to Blender.
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
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
):
44 Returns 1 string represneting the value for this line
45 None will be returned if theres only 1 word
47 length
= len(line_split
)
55 return b
' '.join(line_split
[1:])
58 def obj_image_load(imagepath
, DIR
, use_image_search
):
60 image
= load_image(imagepath
.replace(b
'_', b
' '), DIR
)
64 image
= load_image(imagepath
, DIR
)
68 print("failed to load %r doesn't exist" % imagepath
)
71 # def obj_image_load(imagepath, DIR, use_image_search):
73 # Mainly uses comprehensiveImageLoad
74 # but tries to replace '_' with ' ' for Max's exporter replaces spaces with underscores.
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)
89 def create_materials(filepath
, material_libs
, unique_materials
, unique_material_images
, use_image_search
):
91 Create all the used materials in this obj,
92 assign colors and images to the materials from all referenced material libs
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
)
108 texture
.image
= image
109 has_data
= image
.has_data
111 # Adds textures for materials (rendering)
113 if has_data
and image
.depth
== 32:
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
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
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
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
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
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
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
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
)
191 #Create new materials
192 for name
in unique_materials
: # .keys()
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
:
202 mtlpath
= os
.path
.join(DIR
, libname
)
203 if not os
.path
.exists(mtlpath
):
204 print ("\tMaterial not found MTL: %r" % mtlpath
)
206 #print('\t\tloading mtl: %e' % mtlpath)
207 context_material
= None
208 mtl
= open(mtlpath
, 'rb')
209 for line
in mtl
: # .readlines():
211 if not line
or line
.startswith(b
'#'):
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.
238 elif line_lower
.startswith(b
'illum'):
239 illum
= int(line_split
[1])
243 do_reflection
= False
244 do_transparency
= False
249 # inline comments are from the spec, v4.2
251 # Color on and Ambient off
254 # Color on and Ambient on
260 # Reflection on and Ray trace on
264 # Transparency: Glass on
265 # Reflection: Ray trace on
266 do_transparency
= True
271 # Reflection: Fresnel on and Ray trace on
276 # Transparency: Refraction on
277 # Reflection: Fresnel off and Ray trace on
278 do_transparency
= True
282 # Transparency: Refraction on
283 # Reflection: Fresnel on and Ray trace on
284 do_transparency
= True
289 # Reflection on and Ray trace off
292 # Transparency: Glass on
293 # Reflection: Ray trace off
294 do_transparency
= True
298 # Casts shadows onto invisible surfaces
300 # blender cant do this
304 context_material
.ambient
= 1.0
306 context_material
.ambient
= 0.0
309 # FIXME, how else to use this?
310 context_material
.specular_intensity
= 1.0
313 context_material
.raytrace_mirror
.use
= True
314 context_material
.raytrace_mirror
.reflect_factor
= 1.0
317 context_material
.use_transparency
= True
318 context_material
.transparency_method
= 'RAYTRACE' if do_raytrace
else 'Z_TRANSPARENCY'
319 context_material
.alpha
= 0.0
322 context_material
.raytrace_transparency
.ior
= 1.5
325 context_material
.raytrace_mirror
.fresnel
= 1.0 # could be any value for 'ON'
329 context_material.use_raytrace = True
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())
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())
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())
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())
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())
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())
361 load_material_image(context_material
, context_material_name
, img_filepath
, 'refl')
363 print("\t%r:%r (ignored)" % (filepath
, line
))
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
382 return filename
# assume its a string. make sure this is true if the splitting code is changed
386 # Return a key that makes the faces unique.
389 oldkey
= -1 # initialize to a value that will never match the key
395 # Check the key has changed.
397 verts_split
, faces_split
, unique_materials_split
, vert_remap
= face_split_dict
[key
]
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
)
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
418 face_vert_loc_indices
[enum
] = vert_remap
[i
] # remap to the local index
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
,
438 unique_material_images
,
439 unique_smooth_groups
,
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
452 if unique_smooth_groups
:
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
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
,
470 context_smooth_group
,
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
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)])
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]
502 edge_dict
[i1
, i2
] += 1
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]],
519 context_smooth_group
,
522 for ngon
in ngon_face_indices
]
525 # edges to make fgons
528 for ngon
in ngon_face_indices
:
530 i1
= face_vert_loc_indices
[ngon
[i
]]
531 i2
= face_vert_loc_indices
[ngon
[i
- 1]]
536 edge_users
[i1
, i2
] += 1
538 edge_users
[i1
, i2
] = 1
540 for key
, users
in edge_users
.items():
542 fgon_edges
[key
] = None
544 # remove all after 3, means we dont have to pop this one.
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
:
582 context_material_old
= -1 # avoid a dict lookup
583 mat
= 0 # rare case it may be un-initialized.
586 for i
, face
in enumerate(faces
):
588 pass # raise "bad face"
589 elif len(face
[0]) == 2:
591 edges
.append(face
[0])
594 blender_face
= me
.faces
[i
]
596 (face_vert_loc_indices
,
597 face_vert_tex_indices
,
599 context_smooth_group
,
603 if context_smooth_group
:
604 blender_face
.use_smooth
= True
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
616 blender_tface
= me
.uv_textures
[0].data
[i
]
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]
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]]
648 if use_edges
and not 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 )
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])
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):
673 # if use_ngons and fgon_edges:
674 # FGON= Mesh.EdgeFlags.FGON
675 # for ed in me.findEdges( fgon_edges.keys() ):
677 # me_edges[ed].flag |= FGON
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() ):
691 # me_edges[ed].flag |= SHARP
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')
721 print('\tWarning, cstype not found')
723 if cstype
!= b
'bspline':
724 print('\tWarning, cstype is not supported (only bspline)')
727 print('\tWarning, curv argument empty or not set')
729 if len(deg
) > 1 or parm_v
:
730 print('\tWarning, surfaces not supported')
733 cu
= bpy
.data
.curves
.new(name
.decode('utf-8', "replace"), 'CURVE')
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:
745 for i
in range(deg
[0] + 1):
747 if abs(parm_u
[i
] - curv_range
[0]) > 0.0001:
751 if abs(parm_u
[-(i
+ 1)] - curv_range
[1]) > 0.0001:
759 nu
.use_endpoint_u
= True
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)]:
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
786 line_split
[-1] = line_split
[-1][:-1] # remove the \ from the end last number
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():
799 if line
.startswith(b
'v'): # vn vt v
802 return lambda f
: float(f
.replace(b
',', b
'.'))
808 # incase all vert values were ints
812 def load(operator
, context
, filepath
,
813 global_clamp_size
=0.0,
815 use_smooth_groups
=True,
817 use_split_objects
=True,
818 use_split_groups
=True,
819 use_image_search
=True,
820 use_groups_as_vgroups
=False,
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()
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
)
851 context_material
= None
852 context_smooth_group
= None
853 context_object
= None
854 context_vgroup
= None
859 context_parm
= b
'' # used by nurbs too but could be used elsewhere
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 "):
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()
904 line_split
= line
[2:].split()
905 face_vert_loc_indices
= []
906 face_vert_tex_indices
= []
909 faces
.append((face_vert_loc_indices
,
910 face_vert_tex_indices
,
912 context_smooth_group
,
916 if strip_slash(line_split
):
917 context_multi_line
= b
'f'
919 context_multi_line
= b
''
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
)
947 face_vert_tex_indices
.append(0)
949 if len(face_vert_loc_indices
) > 4:
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()
960 line_split
= line
[2:].split()
961 face_vert_loc_indices
= []
962 face_vert_tex_indices
= []
965 faces
.append((face_vert_loc_indices
,
966 face_vert_tex_indices
,
968 context_smooth_group
,
972 if strip_slash(line_split
):
973 context_multi_line
= b
'l'
975 context_multi_line
= b
''
977 # isline = line.startswith(b'l') # UNUSED
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
, [])
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
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'
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
''
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'
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
1069 context_nurbs
[b
'name'] = context_object
1070 nurbs
.append(context_nurbs
)
1074 ''' # How to use usemap? depricated?
1075 elif line.startswith(b'usema'): # usemap or usemat
1076 context_image= line_value(line.split())
1080 time_new
= time
.time()
1081 print("%.4f sec" % (time_new
- time_sub
))
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
))
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
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
,
1115 unique_materials_split
,
1116 unique_material_images
,
1117 unique_smooth_groups
,
1123 for context_nurbs
in nurbs
:
1124 create_nurbs(context_nurbs
, verts_loc
, new_objects
)
1127 for obj
in new_objects
:
1128 base
= scene
.objects
.link(obj
)
1131 # we could apply this anywhere before scaling.
1132 obj
.matrix_world
= global_matrix
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
1150 max_axis
= max(axis_max
[0] - axis_min
[0], axis_max
[1] - axis_min
[1], axis_max
[2] - axis_min
[2])
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
)))
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()