-
Bastien Montagne authoredBastien Montagne authored
import_obj.py 44.02 KiB
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Script copyright (C) Campbell Barton
# Contributors: Campbell Barton, Jiri Hnidek, Paolo Ciccone
"""
This script imports a Wavefront OBJ files to Blender.
Usage:
Run this script from "File->Import" menu and then load the desired OBJ file.
Note, This loads mesh objects and materials only, nurbs and curves are not supported.
http://wiki.blender.org/index.php/Scripts/Manual/Import/wavefront_obj
"""
import array
import os
import time
import bpy
import mathutils
from bpy_extras.io_utils import unpack_list
from bpy_extras.image_utils import load_image
def line_value(line_split):
"""
Returns 1 string representing the value for this line
None will be returned if theres only 1 word
"""
length = len(line_split)
if length == 1:
return None
elif length == 2:
return line_split[1]
elif length > 2:
return b' '.join(line_split[1:])
def obj_image_load(imagepath, DIR, recursive, relpath):
"""
Mainly uses comprehensiveImageLoad
but tries to replace '_' with ' ' for Max's exporter replaces spaces with underscores.
"""
if b'_' in imagepath:
image = load_image(imagepath.replace(b'_', b' '), DIR, recursive=recursive, relpath=relpath)
if image:
return image
return load_image(imagepath, DIR, recursive=recursive, place_holder=True, relpath=relpath)
def create_materials(filepath, relpath,
material_libs, unique_materials, unique_material_images,
use_image_search, float_func):
"""
Create all the used materials in this obj,
assign colors and images to the materials from all referenced material libs
"""
DIR = os.path.dirname(filepath)
context_material_vars = set()
def load_material_image(blender_material, context_material_name, imagepath, type):
"""
Set textures defined in .mtl file.
"""
texture = bpy.data.textures.new(name=type, type='IMAGE')
# Absolute path - c:\.. etc would work here
image = obj_image_load(imagepath, DIR, use_image_search, relpath)
if image is not None:
texture.image = image
# Adds textures for materials (rendering)
if type == 'Kd':
mtex = blender_material.texture_slots.add()
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_color_diffuse = True
# adds textures to faces (Textured/Alt-Z mode)
# Only apply the diffuse texture to the face if the image has not been set with the inline usemat func.
unique_material_images[context_material_name] = image # set the texface image
elif type == 'Ka':
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_ambient = True
elif type == 'Ks':
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_specular = True
elif type == 'Bump':
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_normal = True
elif type == 'D':
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_alpha = True
blender_material.use_transparency = True
blender_material.transparency_method = 'Z_TRANSPARENCY'
if "alpha" not in context_material_vars:
blender_material.alpha = 0.0
# Todo, unset deffuse material alpha if it has an alpha channel
elif type == 'disp':
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_displacement = True
elif type == 'refl':
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'REFLECTION'
mtex.use_map_color_diffuse = True
else:
raise Exception("invalid type %r" % type)
# Add an MTL with the same name as the obj if no MTLs are spesified.
temp_mtl = os.path.splitext((os.path.basename(filepath)))[0] + b'.mtl'
if os.path.exists(os.path.join(DIR, temp_mtl)) and temp_mtl not in material_libs:
material_libs.append(temp_mtl)
del temp_mtl
# Create new materials
for name in unique_materials: # .keys()
if name is not None:
unique_materials[name] = bpy.data.materials.new(name.decode('utf-8', "replace"))
unique_material_images[name] = None # assign None to all material images to start with, add to later.
unique_materials[None] = None
unique_material_images[None] = None
for libname in material_libs:
# print(libname)
mtlpath = os.path.join(DIR, libname)
if not os.path.exists(mtlpath):
print("\tMaterial not found MTL: %r" % mtlpath)
else:
# print('\t\tloading mtl: %e' % mtlpath)
context_material = None
mtl = open(mtlpath, 'rb')
for line in mtl: # .readlines():
line = line.strip()
if not line or line.startswith(b'#'):
continue
line_split = line.split()
line_id = line_split[0].lower()
if line_id == b'newmtl':
context_material_name = line_value(line_split)
context_material = unique_materials.get(context_material_name)
context_material_vars.clear()
elif context_material:
# we need to make a material to assign properties to it.
if line_id == b'ka':
context_material.mirror_color = (
float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3]))
elif line_id == b'kd':
context_material.diffuse_color = (
float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3]))
elif line_id == b'ks':
context_material.specular_color = (
float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3]))
elif line_id == b'ns':
context_material.specular_hardness = int((float_func(line_split[1]) * 0.51))
elif line_id == b'ni': # Refraction index (between 1 and 3).
context_material.raytrace_transparency.ior = max(1, min(float_func(line_split[1]), 3))
context_material_vars.add("ior")
elif line_id == b'd': # dissolve (trancparency)
context_material.alpha = float_func(line_split[1])
context_material.use_transparency = True
context_material.transparency_method = 'Z_TRANSPARENCY'
context_material_vars.add("alpha")
elif line_id == b'tr': # trancelucency
context_material.translucency = float_func(line_split[1])
elif line_id == b'tf':
# rgb, filter color, blender has no support for this.
pass
elif line_id == b'illum':
illum = int(line_split[1])
do_ambient = True
do_highlight = False
do_reflection = False
do_transparency = False
do_glass = False
do_fresnel = False
do_raytrace = False
# inline comments are from the spec, v4.2
if illum == 0:
# Color on and Ambient off
do_ambient = False
elif illum == 1:
# Color on and Ambient on
pass
elif illum == 2:
# Highlight on
do_highlight = True
elif illum == 3:
# Reflection on and Ray trace on
do_reflection = True
do_raytrace = True
elif illum == 4:
# Transparency: Glass on
# Reflection: Ray trace on
do_transparency = True
do_reflection = True
do_glass = True
do_raytrace = True
elif illum == 5:
# Reflection: Fresnel on and Ray trace on
do_reflection = True
do_fresnel = True
do_raytrace = True
elif illum == 6:
# Transparency: Refraction on
# Reflection: Fresnel off and Ray trace on
do_transparency = True
do_reflection = True
do_raytrace = True
elif illum == 7:
# Transparency: Refraction on
# Reflection: Fresnel on and Ray trace on
do_transparency = True
do_reflection = True
do_fresnel = True
do_raytrace = True
elif illum == 8:
# Reflection on and Ray trace off
do_reflection = True
elif illum == 9:
# Transparency: Glass on
# Reflection: Ray trace off
do_transparency = True
do_reflection = True
do_glass = True
elif illum == 10:
# Casts shadows onto invisible surfaces
# blender cant do this
pass
if do_ambient:
context_material.ambient = 1.0
else:
context_material.ambient = 0.0
if do_highlight:
# FIXME, how else to use this?
context_material.specular_intensity = 1.0
if do_reflection:
context_material.raytrace_mirror.use = True
context_material.raytrace_mirror.reflect_factor = 1.0
if do_transparency:
context_material.use_transparency = True
context_material.transparency_method = 'RAYTRACE' if do_raytrace else 'Z_TRANSPARENCY'
if "alpha" not in context_material_vars:
context_material.alpha = 0.0
if do_glass:
if "ior" not in context_material_vars:
context_material.raytrace_transparency.ior = 1.5
if do_fresnel:
context_material.raytrace_mirror.fresnel = 1.0 # could be any value for 'ON'
"""
if do_raytrace:
context_material.use_raytrace = True
else:
context_material.use_raytrace = False
"""
# XXX, this is not following the OBJ spec, but this was
# written when raytracing wasnt default, annoying to disable for blender users.
context_material.use_raytrace = True
elif line_id == b'map_ka':
img_filepath = line_value(line.split())
if img_filepath:
load_material_image(context_material, context_material_name, img_filepath, 'Ka')
elif line_id == b'map_ks':
img_filepath = line_value(line.split())
if img_filepath:
load_material_image(context_material, context_material_name, img_filepath, 'Ks')
elif line_id == b'map_kd':
img_filepath = line_value(line.split())
if img_filepath:
load_material_image(context_material, context_material_name, img_filepath, 'Kd')
elif line_id in {b'map_bump', b'bump'}: # 'bump' is incorrect but some files use it.
img_filepath = line_value(line.split())
if img_filepath:
load_material_image(context_material, context_material_name, img_filepath, 'Bump')
elif line_id in {b'map_d', b'map_tr'}: # Alpha map - Dissolve
img_filepath = line_value(line.split())
if img_filepath:
load_material_image(context_material, context_material_name, img_filepath, 'D')
elif line_id in {b'map_disp', b'disp'}: # displacementmap
img_filepath = line_value(line.split())
if img_filepath:
load_material_image(context_material, context_material_name, img_filepath, 'disp')
elif line_id in {b'map_refl', b'refl'}: # reflectionmap
img_filepath = line_value(line.split())
if img_filepath:
load_material_image(context_material, context_material_name, img_filepath, 'refl')
else:
print("\t%r:%r (ignored)" % (filepath, line))
mtl.close()
def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
"""
Takes vert_loc and faces, and separates into multiple sets of
(verts_loc, faces, unique_materials, dataname)
"""
filename = os.path.splitext((os.path.basename(filepath)))[0]
if not SPLIT_OB_OR_GROUP or not faces:
# use the filename for the object name since we aren't chopping up the mesh.
return [(verts_loc, faces, unique_materials, filename)]
def key_to_name(key):
# if the key is a tuple, join it to make a string
if not key:
return filename # assume its a string. make sure this is true if the splitting code is changed
else:
return key
# Return a key that makes the faces unique.
face_split_dict = {}
oldkey = -1 # initialize to a value that will never match the key
for face in faces:
key = face[5]
if oldkey != key:
# Check the key has changed.
(verts_split, faces_split,
unique_materials_split, vert_remap) = face_split_dict.setdefault(key, ([], [], {}, {}))
oldkey = key
face_vert_loc_indices = face[0]
# Remap verts to new vert list and add where needed
for enum, i in enumerate(face_vert_loc_indices):
map_index = vert_remap.get(i)
if map_index is None:
map_index = len(verts_split)
vert_remap[i] = map_index # set the new remapped index so we only add once and can reference next time.
verts_split.append(verts_loc[i]) # add the vert to the local verts
face_vert_loc_indices[enum] = map_index # remap to the local index
matname = face[3]
if matname and matname not in unique_materials_split:
unique_materials_split[matname] = unique_materials[matname]
faces_split.append(face)
# remove one of the items and reorder
return [(verts_split, faces_split, unique_materials_split, key_to_name(key))
for key, (verts_split, faces_split, unique_materials_split, _) in face_split_dict.items()]
def create_mesh(new_objects,
use_edges,
verts_loc,
verts_nor,
verts_tex,
faces,
unique_materials,
unique_material_images,
unique_smooth_groups,
vertex_groups,
dataname,
):
"""
Takes all the data gathered and generates a mesh, adding the new object to new_objects
deals with ngons, sharp edges and assigning materials
"""
if unique_smooth_groups:
sharp_edges = set()
smooth_group_users = {context_smooth_group: {} for context_smooth_group in unique_smooth_groups.keys()}
context_smooth_group_old = -1
fgon_edges = set() # Used for storing fgon keys whe we need to tesselate/untesselate them (ngons with hole).
edges = []
tot_loops = 0
context_object = None
# reverse loop through face indices
for f_idx in range(len(faces) - 1, -1, -1):
(face_vert_loc_indices,
face_vert_nor_indices,
face_vert_tex_indices,
context_material,
context_smooth_group,
context_object,
face_invalid_blenpoly,
) = faces[f_idx]
len_face_vert_loc_indices = len(face_vert_loc_indices)
if len_face_vert_loc_indices == 1:
faces.pop(f_idx) # cant add single vert faces
elif not face_vert_tex_indices or len_face_vert_loc_indices == 2: # faces that have no texture coords are lines
if use_edges:
edges.extend((face_vert_loc_indices[i], face_vert_loc_indices[i + 1])
for i in range(len_face_vert_loc_indices - 1))
faces.pop(f_idx)
else:
# Smooth Group
if unique_smooth_groups and context_smooth_group:
# Is a part of of a smooth group and is a face
if context_smooth_group_old is not context_smooth_group:
edge_dict = smooth_group_users[context_smooth_group]
context_smooth_group_old = context_smooth_group
prev_vidx = face_vert_loc_indices[-1]
for vidx in face_vert_loc_indices:
edge_key = (prev_vidx, vidx) if (prev_vidx < vidx) else (vidx, prev_vidx)
prev_vidx = vidx
edge_dict[edge_key] = edge_dict.get(edge_key, 0) + 1
# NGons into triangles
if face_invalid_blenpoly:
from bpy_extras.mesh_utils import ngon_tessellate
ngon_face_indices = ngon_tessellate(verts_loc, face_vert_loc_indices)
faces.extend([([face_vert_loc_indices[ngon[0]],
face_vert_loc_indices[ngon[1]],
face_vert_loc_indices[ngon[2]],
],
[face_vert_nor_indices[ngon[0]],
face_vert_nor_indices[ngon[1]],
face_vert_nor_indices[ngon[2]],
],
[face_vert_tex_indices[ngon[0]],
face_vert_tex_indices[ngon[1]],
face_vert_tex_indices[ngon[2]],
],
context_material,
context_smooth_group,
context_object,
[],
)
for ngon in ngon_face_indices]
)
tot_loops += 3 * len(ngon_face_indices)
# edges to make ngons
edge_users = set()
for ngon in ngon_face_indices:
prev_vidx = face_vert_loc_indices[ngon[-1]]
for ngidx in ngon:
vidx = face_vert_loc_indices[ngidx]
edge_key = (prev_vidx, vidx) if (prev_vidx < vidx) else (vidx, prev_vidx)
prev_vidx = vidx
if edge_key in edge_users:
fgon_edges.add(edge_key)
else:
edge_users.add(edge_key)
faces.pop(f_idx)
else:
tot_loops += len_face_vert_loc_indices
# Build sharp edges
if unique_smooth_groups:
for edge_dict in smooth_group_users.values():
for key, users in edge_dict.items():
if users == 1: # This edge is on the boundry of a group
sharp_edges.add(key)
# map the material names to an index
material_mapping = {name: i for i, name in enumerate(unique_materials)} # enumerate over unique_materials keys()
materials = [None] * len(unique_materials)
for name, index in material_mapping.items():
materials[index] = unique_materials[name]
me = bpy.data.meshes.new(dataname.decode('utf-8', "replace"))
# make sure the list isnt too big
for material in materials:
me.materials.append(material)
me.vertices.add(len(verts_loc))
me.loops.add(tot_loops)
me.polygons.add(len(faces))
# verts_loc is a list of (x, y, z) tuples
me.vertices.foreach_set("co", unpack_list(verts_loc))
loops_vert_idx = []
faces_loop_start = []
faces_loop_total = []
lidx = 0
for f in faces:
vidx = f[0]
nbr_vidx = len(vidx)
loops_vert_idx.extend(vidx)
faces_loop_start.append(lidx)
faces_loop_total.append(nbr_vidx)
lidx += nbr_vidx
me.loops.foreach_set("vertex_index", loops_vert_idx)
me.polygons.foreach_set("loop_start", faces_loop_start)
me.polygons.foreach_set("loop_total", faces_loop_total)
if verts_nor and me.loops:
# Note: we store 'temp' normals in loops, since validate() may alter final mesh,
# we can only set custom lnors *after* calling it.
me.create_normals_split()
if verts_tex and me.polygons:
me.uv_textures.new()
context_material_old = -1 # avoid a dict lookup
mat = 0 # rare case it may be un-initialized.
for i, (face, blen_poly) in enumerate(zip(faces, me.polygons)):
if len(face[0]) < 3:
raise Exception("bad face") # Shall not happen, we got rid of those earlier!
(face_vert_loc_indices,
face_vert_nor_indices,
face_vert_tex_indices,
context_material,
context_smooth_group,
context_object,
face_invalid_blenpoly,
) = face
if context_smooth_group:
blen_poly.use_smooth = True
if context_material:
if context_material_old is not context_material:
mat = material_mapping[context_material]
context_material_old = context_material
blen_poly.material_index = mat
if verts_nor:
for face_noidx, lidx in zip(face_vert_nor_indices, blen_poly.loop_indices):
me.loops[lidx].normal[:] = verts_nor[face_noidx]
if verts_tex:
if context_material:
image = unique_material_images[context_material]
if image: # Can be none if the material dosnt have an image.
me.uv_textures[0].data[i].image = image
blen_uvs = me.uv_layers[0]
for face_uvidx, lidx in zip(face_vert_tex_indices, blen_poly.loop_indices):
blen_uvs.data[lidx].uv = verts_tex[face_uvidx]
use_edges = use_edges and bool(edges)
if use_edges:
me.edges.add(len(edges))
# edges should be a list of (a, b) tuples
me.edges.foreach_set("vertices", unpack_list(edges))
me.validate(cleanup_cddata=False) # *Very* important to not remove lnors here!
me.update(calc_edges=use_edges)
# Un-tessellate as much as possible, in case we had to triangulate some ngons...
if fgon_edges:
import bmesh
bm = bmesh.new()
bm.from_mesh(me)
verts = bm.verts[:]
get = bm.edges.get
edges = [get((verts[vidx1], verts[vidx2])) for vidx1, vidx2 in fgon_edges]
try:
bmesh.ops.dissolve_edges(bm, edges=edges, use_verts=False)
except:
# Possible dissolve fails for some edges, but don't fail silently in case this is a real bug.
import traceback
traceback.print_exc()
bm.to_mesh(me)
bm.free()
# XXX If validate changes the geometry, this is likely to be broken...
if unique_smooth_groups and sharp_edges:
for e in me.edges:
if e.key in sharp_edges:
e.use_edge_sharp = True
me.show_edge_sharp = True
if verts_nor:
clnors = array.array('f', [0.0] * (len(me.loops) * 3))
me.loops.foreach_get("normal", clnors)
if not unique_smooth_groups:
me.polygons.foreach_set("use_smooth", [True] * len(me.polygons))
me.normals_split_custom_set(tuple(zip(*(iter(clnors),) * 3)))
me.use_auto_smooth = True
me.show_edge_sharp = True
ob = bpy.data.objects.new(me.name, me)
new_objects.append(ob)
# Create the vertex groups. No need to have the flag passed here since we test for the
# content of the vertex_groups. If the user selects to NOT have vertex groups saved then
# the following test will never run
for group_name, group_indices in vertex_groups.items():
group = ob.vertex_groups.new(group_name.decode('utf-8', "replace"))
group.add(group_indices, 1.0, 'REPLACE')
def create_nurbs(context_nurbs, vert_loc, new_objects):
"""
Add nurbs object to blender, only support one type at the moment
"""
deg = context_nurbs.get(b'deg', (3,))
curv_range = context_nurbs.get(b'curv_range')
curv_idx = context_nurbs.get(b'curv_idx', [])
parm_u = context_nurbs.get(b'parm_u', [])
parm_v = context_nurbs.get(b'parm_v', [])
name = context_nurbs.get(b'name', b'ObjNurb')
cstype = context_nurbs.get(b'cstype')
if cstype is None:
print('\tWarning, cstype not found')
return
if cstype != b'bspline':
print('\tWarning, cstype is not supported (only bspline)')
return
if not curv_idx:
print('\tWarning, curv argument empty or not set')
return
if len(deg) > 1 or parm_v:
print('\tWarning, surfaces not supported')
return
cu = bpy.data.curves.new(name.decode('utf-8', "replace"), 'CURVE')
cu.dimensions = '3D'
nu = cu.splines.new('NURBS')
nu.points.add(len(curv_idx) - 1) # a point is added to start with
nu.points.foreach_set("co", [co_axis for vt_idx in curv_idx for co_axis in (vert_loc[vt_idx] + (1.0,))])
nu.order_u = deg[0] + 1
# get for endpoint flag from the weighting
if curv_range and len(parm_u) > deg[0] + 1:
do_endpoints = True
for i in range(deg[0] + 1):
if abs(parm_u[i] - curv_range[0]) > 0.0001:
do_endpoints = False
break
if abs(parm_u[-(i + 1)] - curv_range[1]) > 0.0001:
do_endpoints = False
break
else:
do_endpoints = False
if do_endpoints:
nu.use_endpoint_u = True
# close
'''
do_closed = False
if len(parm_u) > deg[0]+1:
for i in xrange(deg[0]+1):
#print curv_idx[i], curv_idx[-(i+1)]
if curv_idx[i]==curv_idx[-(i+1)]:
do_closed = True
break
if do_closed:
nu.use_cyclic_u = True
'''
ob = bpy.data.objects.new(name.decode('utf-8', "replace"), cu)
new_objects.append(ob)
def strip_slash(line_split):
if line_split[-1][-1] == 92: # '\' char
if len(line_split[-1]) == 1:
line_split.pop() # remove the \ item
else:
line_split[-1] = line_split[-1][:-1] # remove the \ from the end last number
return True
return False
def get_float_func(filepath):
"""
find the float function for this obj file
- whether to replace commas or not
"""
file = open(filepath, 'rb')
for line in file: # .readlines():
line = line.lstrip()
if line.startswith(b'v'): # vn vt v
if b',' in line:
file.close()
return lambda f: float(f.replace(b',', b'.'))
elif b'.' in line:
file.close()
return float
file.close()
# in case all vert values were ints
return float
def load(operator, context, filepath,
global_clamp_size=0.0,
use_smooth_groups=True,
use_edges=True,
use_split_objects=True,
use_split_groups=True,
use_image_search=True,
use_groups_as_vgroups=False,
relpath=None,
global_matrix=None,
):
"""
Called by the user interface or another script.
load_obj(path) - should give acceptable results.
This function passes the file and sends the data off
to be split into objects and then converted into mesh objects
"""
def handle_vec(line_start, context_multi_line, line_split, tag, data, vec, vec_len):
ret_context_multi_line = tag if strip_slash(line_split) else b''
if line_start == tag:
vec[:] = [float_func(v) for v in line_split[1:]]
elif context_multi_line == tag:
vec += [float_func(v) for v in line_split]
if not ret_context_multi_line:
data.append(tuple(vec[:vec_len]))
return ret_context_multi_line
def create_face(context_material, context_smooth_group, context_object):
face_vert_loc_indices = []
face_vert_nor_indices = []
face_vert_tex_indices = []
return (
face_vert_loc_indices,
face_vert_nor_indices,
face_vert_tex_indices,
context_material,
context_smooth_group,
context_object,
[], # If non-empty, that face is a Blender-invalid ngon (holes...), need a mutable object for that...
)
print('\nimporting obj %r' % filepath)
filepath = os.fsencode(filepath)
if global_matrix is None:
global_matrix = mathutils.Matrix()
if use_split_objects or use_split_groups:
use_groups_as_vgroups = False
time_main = time.time()
verts_loc = []
verts_nor = []
verts_tex = []
faces = [] # tuples of the faces
material_libs = [] # filanems to material libs this uses
vertex_groups = {} # when use_groups_as_vgroups is true
# Get the string to float conversion func for this file- is 'float' for almost all files.
float_func = get_float_func(filepath)
# Context variables
context_material = None
context_smooth_group = None
context_object = None
context_vgroup = None
# Nurbs
context_nurbs = {}
nurbs = []
context_parm = b'' # used by nurbs too but could be used elsewhere
# Until we can use sets
unique_materials = {}
unique_material_images = {}
unique_smooth_groups = {}
# unique_obects= {} - no use for this variable since the objects are stored in the face.
# when there are faces that end with \
# it means they are multiline-
# since we use xreadline we cant skip to the next line
# so we need to know whether
context_multi_line = b''
# Per-face handling data.
face_vert_loc_indices = None
face_vert_nor_indices = None
face_vert_tex_indices = None
face_items_usage = set()
face_invalid_blenpoly = None
prev_vidx = None
face = None
vec = []
print("\tparsing obj file...")
time_sub = time.time()
file = open(filepath, 'rb')
for line in file: # .readlines():
line_split = line.split()
if not line_split:
continue
line_start = line_split[0] # we compare with this a _lot_
if line_start == b'v' or context_multi_line == b'v':
context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'v', verts_loc, vec, 3)
elif line_start == b'vn' or context_multi_line == b'vn':
context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'vn', verts_nor, vec, 3)
elif line_start == b'vt' or context_multi_line == b'vt':
context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'vt', verts_tex, vec, 2)
# Handle faces lines (as faces) and the second+ lines of fa multiline face here
# use 'f' not 'f ' because some objs (very rare have 'fo ' for faces)
elif line_start == b'f' or context_multi_line == b'f':
if not context_multi_line:
line_split = line_split[1:]
# Instantiate a face
face = create_face(context_material, context_smooth_group, context_object)
(face_vert_loc_indices, face_vert_nor_indices, face_vert_tex_indices,
_1, _2, _3, face_invalid_blenpoly) = face
faces.append(face)
face_items_usage.clear()
# Else, use face_vert_loc_indices and face_vert_tex_indices previously defined and used the obj_face
context_multi_line = b'f' if strip_slash(line_split) else b''
for v in line_split:
obj_vert = v.split(b'/')
idx = int(obj_vert[0]) - 1
vert_loc_index = (idx + len(verts_loc) + 1) if (idx < 0) else idx
# Add the vertex to the current group
# *warning*, this wont work for files that have groups defined around verts
if use_groups_as_vgroups and context_vgroup:
vertex_groups[context_vgroup].append(vert_loc_index)
# This a first round to quick-detect ngons that *may* use a same edge more than once.
# Potential candidate will be re-checked once we have done parsing the whole face.
if not face_invalid_blenpoly:
# If we use more than once a same vertex, invalid ngon is suspected.
if vert_loc_index in face_items_usage:
face_invalid_blenpoly.append(True)
else:
face_items_usage.add(vert_loc_index)
face_vert_loc_indices.append(vert_loc_index)
# formatting for faces with normals and textures is
# loc_index/tex_index/nor_index
if len(obj_vert) > 1 and obj_vert[1]:
idx = int(obj_vert[1]) - 1
face_vert_tex_indices.append((idx + len(verts_tex) + 1) if (idx < 0) else idx)
else:
# dummy
face_vert_tex_indices.append(0)
if len(obj_vert) > 2 and obj_vert[2]:
idx = int(obj_vert[2]) - 1
face_vert_nor_indices.append((idx + len(verts_nor) + 1) if (idx < 0) else idx)
else:
# dummy
face_vert_nor_indices.append(0)
if not context_multi_line:
# Means we have finished a face, we have to do final check if ngon is suspected to be blender-invalid...
if face_invalid_blenpoly:
face_invalid_blenpoly.clear()
face_items_usage.clear()
prev_vidx = face_vert_loc_indices[-1]
for vidx in face_vert_loc_indices:
edge_key = (prev_vidx, vidx) if (prev_vidx < vidx) else (vidx, prev_vidx)
if edge_key in face_items_usage:
face_invalid_blenpoly.append(True)
break
face_items_usage.add(edge_key)
prev_vidx = vidx
elif use_edges and (line_start == b'l' or context_multi_line == b'l'):
# very similar to the face load function above with some parts removed
if not context_multi_line:
line_split = line_split[1:]
# Instantiate a face
face = create_face(context_material, context_smooth_group, context_object)
face_vert_loc_indices, _1, _2, _3, _4, _5 = face
faces.append(face)
# Else, use face_vert_loc_indices and face_vert_tex_indices previously defined and used the obj_face
context_multi_line = b'l' if strip_slash(line_split) else b''
for v in line_split:
obj_vert = v.split(b'/')
idx = int(obj_vert[0]) - 1
face_vert_loc_indices.append((idx + len(verts_loc) + 1) if (idx < 0) else idx)
elif line_start == b's':
if use_smooth_groups:
context_smooth_group = line_value(line_split)
if context_smooth_group == b'off':
context_smooth_group = None
elif context_smooth_group: # is not None
unique_smooth_groups[context_smooth_group] = None
elif line_start == b'o':
if use_split_objects:
context_object = line_value(line_split)
# unique_obects[context_object]= None
elif line_start == b'g':
if use_split_groups:
context_object = line_value(line.split())
# print 'context_object', context_object
# unique_obects[context_object]= None
elif use_groups_as_vgroups:
context_vgroup = line_value(line.split())
if context_vgroup and context_vgroup != b'(null)':
vertex_groups.setdefault(context_vgroup, [])
else:
context_vgroup = None # dont assign a vgroup
elif line_start == b'usemtl':
context_material = line_value(line.split())
unique_materials[context_material] = None
elif line_start == b'mtllib': # usemap or usemat
# can have multiple mtllib filenames per line, mtllib can appear more than once,
# so make sure only occurrence of material exists
material_libs = list(set(material_libs) | set(line.split()[1:]))
# Nurbs support
elif line_start == b'cstype':
context_nurbs[b'cstype'] = line_value(line.split()) # 'rat bspline' / 'bspline'
elif line_start == b'curv' or context_multi_line == b'curv':
curv_idx = context_nurbs[b'curv_idx'] = context_nurbs.get(b'curv_idx', []) # in case were multiline
if not context_multi_line:
context_nurbs[b'curv_range'] = float_func(line_split[1]), float_func(line_split[2])
line_split[0:3] = [] # remove first 3 items
if strip_slash(line_split):
context_multi_line = b'curv'
else:
context_multi_line = b''
for i in line_split:
vert_loc_index = int(i) - 1
if vert_loc_index < 0:
vert_loc_index = len(verts_loc) + vert_loc_index + 1
curv_idx.append(vert_loc_index)
elif line_start == b'parm' or context_multi_line == b'parm':
if context_multi_line:
context_multi_line = b''
else:
context_parm = line_split[1]
line_split[0:2] = [] # remove first 2
if strip_slash(line_split):
context_multi_line = b'parm'
else:
context_multi_line = b''
if context_parm.lower() == b'u':
context_nurbs.setdefault(b'parm_u', []).extend([float_func(f) for f in line_split])
elif context_parm.lower() == b'v': # surfaces not supported yet
context_nurbs.setdefault(b'parm_v', []).extend([float_func(f) for f in line_split])
# else: # may want to support other parm's ?
elif line_start == b'deg':
context_nurbs[b'deg'] = [int(i) for i in line.split()[1:]]
elif line_start == b'end':
# Add the nurbs curve
if context_object:
context_nurbs[b'name'] = context_object
nurbs.append(context_nurbs)
context_nurbs = {}
context_parm = b''
''' # How to use usemap? depricated?
elif line_start == b'usema': # usemap or usemat
context_image= line_value(line_split)
'''
file.close()
time_new = time.time()
print("%.4f sec" % (time_new - time_sub))
time_sub = time_new
print('\tloading materials and images...')
create_materials(filepath, relpath, material_libs, unique_materials,
unique_material_images, use_image_search, float_func)
time_new = time.time()
print("%.4f sec" % (time_new - time_sub))
time_sub = time_new
# deselect all
if bpy.ops.object.select_all.poll():
bpy.ops.object.select_all(action='DESELECT')
scene = context.scene
new_objects = [] # put new objects here
print('\tbuilding geometry...\n\tverts:%i faces:%i materials: %i smoothgroups:%i ...' %
(len(verts_loc), len(faces), len(unique_materials), len(unique_smooth_groups)))
# Split the mesh by objects/materials, may
SPLIT_OB_OR_GROUP = bool(use_split_objects or use_split_groups)
for data in split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
verts_loc_split, faces_split, unique_materials_split, dataname = data
# Create meshes from the data, warning 'vertex_groups' wont support splitting
create_mesh(new_objects,
use_edges,
verts_loc_split,
verts_nor,
verts_tex,
faces_split,
unique_materials_split,
unique_material_images,
unique_smooth_groups,
vertex_groups,
dataname,
)
# nurbs support
for context_nurbs in nurbs:
create_nurbs(context_nurbs, verts_loc, new_objects)
# Create new obj
for obj in new_objects:
base = scene.objects.link(obj)
base.select = True
# we could apply this anywhere before scaling.
obj.matrix_world = global_matrix
scene.update()
axis_min = [1000000000] * 3
axis_max = [-1000000000] * 3
if global_clamp_size:
# Get all object bounds
for ob in new_objects:
for v in ob.bound_box:
for axis, value in enumerate(v):
if axis_min[axis] > value:
axis_min[axis] = value
if axis_max[axis] < value:
axis_max[axis] = value
# Scale objects
max_axis = max(axis_max[0] - axis_min[0], axis_max[1] - axis_min[1], axis_max[2] - axis_min[2])
scale = 1.0
while global_clamp_size < max_axis * scale:
scale = scale / 10.0
for obj in new_objects:
obj.scale = scale, scale, scale
time_new = time.time()
print("finished importing: %r in %.4f sec." % (filepath, (time_new - time_main)))
return {'FINISHED'}