Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'io_scene_obj')
-rw-r--r--io_scene_obj/__init__.py259
-rw-r--r--io_scene_obj/export_obj.py791
-rw-r--r--io_scene_obj/import_obj.py1148
3 files changed, 2198 insertions, 0 deletions
diff --git a/io_scene_obj/__init__.py b/io_scene_obj/__init__.py
new file mode 100644
index 00000000..660a38fc
--- /dev/null
+++ b/io_scene_obj/__init__.py
@@ -0,0 +1,259 @@
+# ##### 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>
+
+bl_info = {
+ "name": "Wavefront OBJ format",
+ "author": "Campbell Barton",
+ "blender": (2, 5, 7),
+ "api": 35622,
+ "location": "File > Import-Export",
+ "description": "Import-Export OBJ, Import OBJ mesh, UV's, materials and textures",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/Wavefront_OBJ",
+ "tracker_url": "",
+ "support": 'OFFICIAL',
+ "category": "Import-Export"}
+
+# To support reload properly, try to access a package var, if it's there, reload everything
+if "bpy" in locals():
+ import imp
+ if "import_obj" in locals():
+ imp.reload(import_obj)
+ if "export_obj" in locals():
+ imp.reload(export_obj)
+
+
+import bpy
+from bpy.props import BoolProperty, FloatProperty, StringProperty, EnumProperty
+from bpy_extras.io_utils import ExportHelper, ImportHelper, path_reference_mode, axis_conversion
+
+
+class ImportOBJ(bpy.types.Operator, ImportHelper):
+ '''Load a Wavefront OBJ File'''
+ bl_idname = "import_scene.obj"
+ bl_label = "Import OBJ"
+ bl_options = {'PRESET'}
+
+ filename_ext = ".obj"
+ filter_glob = StringProperty(default="*.obj;*.mtl", options={'HIDDEN'})
+
+ use_ngons = BoolProperty(name="NGons", description="Import faces with more then 4 verts as fgons", default=True)
+ use_edges = BoolProperty(name="Lines", description="Import lines and faces with 2 verts as edge", default=True)
+ use_smooth_groups = BoolProperty(name="Smooth Groups", description="Surround smooth groups by sharp edges", default=True)
+
+ use_split_objects = BoolProperty(name="Object", description="Import OBJ Objects into Blender Objects", default=True)
+ use_split_groups = BoolProperty(name="Group", description="Import OBJ Groups into Blender Objects", default=True)
+
+ use_groups_as_vgroups = BoolProperty(name="Poly Groups", description="Import OBJ groups as vertex groups.", default=False)
+
+ use_image_search = BoolProperty(name="Image Search", description="Search subdirs for any assosiated images (Warning, may be slow)", default=True)
+
+ split_mode = EnumProperty(
+ name="Split",
+ items=(('ON', "Split", "Split geometry, omits unused verts"),
+ ('OFF', "Keep Vert Order", "Maintain vertex order from file"),
+ ),
+ )
+
+ global_clamp_size = FloatProperty(name="Clamp Scale", description="Clamp the size to this maximum (Zero to Disable)", min=0.0, max=1000.0, soft_min=0.0, soft_max=1000.0, default=0.0)
+ axis_forward = EnumProperty(
+ name="Forward",
+ items=(('X', "X Forward", ""),
+ ('Y', "Y Forward", ""),
+ ('Z', "Z Forward", ""),
+ ('-X', "-X Forward", ""),
+ ('-Y', "-Y Forward", ""),
+ ('-Z', "-Z Forward", ""),
+ ),
+ default='-Z',
+ )
+
+ axis_up = EnumProperty(
+ name="Up",
+ items=(('X', "X Up", ""),
+ ('Y', "Y Up", ""),
+ ('Z', "Z Up", ""),
+ ('-X', "-X Up", ""),
+ ('-Y', "-Y Up", ""),
+ ('-Z', "-Z Up", ""),
+ ),
+ default='Y',
+ )
+
+ # fake prop, only disables split.
+ # keep_vertex_order = BoolProperty(name="Keep Vert Order", description="Keep vert and face order, disables split options, enable for morph targets", default= True)
+
+ def execute(self, context):
+ # print("Selected: " + context.active_object.name)
+ from . import import_obj
+ from mathutils import Matrix
+
+ if self.split_mode == 'OFF':
+ self.use_split_objects = False
+ self.use_split_groups = False
+ else:
+ self.use_groups_as_vgroups = False
+
+ keywords = self.as_keywords(ignore=("axis_forward", "axis_up", "filter_glob", "split_mode"))
+
+ global_matrix = axis_conversion(from_forward=self.axis_forward, from_up=self.axis_up).to_4x4()
+ keywords["global_matrix"] = global_matrix
+
+ return import_obj.load(self, context, **keywords)
+
+ def draw(self, context):
+ layout = self.layout
+
+ row = layout.row(align=True)
+ row.prop(self, "use_ngons")
+ row.prop(self, "use_edges")
+
+ layout.prop(self, "use_smooth_groups")
+
+ box = layout.box()
+ row = box.row()
+ row.prop(self, "split_mode", expand=True)
+
+ row = box.row()
+ if self.split_mode == 'ON':
+ row.label(text="Split by:")
+ row.prop(self, "use_split_objects")
+ row.prop(self, "use_split_groups")
+ else:
+ row.prop(self, "use_groups_as_vgroups")
+
+ row = layout.split(percentage=0.67)
+ row.prop(self, "global_clamp_size")
+ layout.prop(self, "axis_forward")
+ layout.prop(self, "axis_up")
+
+ layout.prop(self, "use_image_search")
+
+
+class ExportOBJ(bpy.types.Operator, ExportHelper):
+ '''Save a Wavefront OBJ File'''
+
+ bl_idname = "export_scene.obj"
+ bl_label = 'Export OBJ'
+ bl_options = {'PRESET'}
+
+ filename_ext = ".obj"
+ filter_glob = StringProperty(default="*.obj;*.mtl", options={'HIDDEN'})
+
+ # List of operator properties, the attributes will be assigned
+ # to the class instance from the operator settings before calling.
+
+ # context group
+ use_selection = BoolProperty(name="Selection Only", description="Export selected objects only", default=False)
+ use_all_scenes = BoolProperty(name="All Scenes", description="", default=False)
+ use_animation = BoolProperty(name="Animation", description="", default=False)
+
+ # object group
+ use_apply_modifiers = BoolProperty(name="Apply Modifiers", description="Apply modifiers (preview resolution)", default=True)
+
+ # extra data group
+ use_edges = BoolProperty(name="Edges", description="", default=True)
+ use_normals = BoolProperty(name="Normals", description="", default=False)
+ use_hq_normals = BoolProperty(name="High Quality Normals", description="", default=True)
+ use_uvs = BoolProperty(name="UVs", description="", default=True)
+ use_materials = BoolProperty(name="Materials", description="", default=True)
+ # copy_images = BoolProperty(name="Copy Images", description="", default=False)
+ use_triangles = BoolProperty(name="Triangulate", description="", default=False)
+ use_vertex_groups = BoolProperty(name="Polygroups", description="", default=False)
+ use_nurbs = BoolProperty(name="Nurbs", description="", default=False)
+
+ # grouping group
+ use_blen_objects = BoolProperty(name="Objects as OBJ Objects", description="", default=True)
+ group_by_object = BoolProperty(name="Objects as OBJ Groups ", description="", default=False)
+ group_by_material = BoolProperty(name="Material Groups", description="", default=False)
+ keep_vertex_order = BoolProperty(name="Keep Vertex Order", description="", default=False)
+
+ global_scale = FloatProperty(name="Scale", description="Scale all data, (Note! some imports dont support scaled armatures)", min=0.01, max=1000.0, soft_min=0.01, soft_max=1000.0, default=1.0)
+
+ axis_forward = EnumProperty(
+ name="Forward",
+ items=(('X', "X Forward", ""),
+ ('Y', "Y Forward", ""),
+ ('Z', "Z Forward", ""),
+ ('-X', "-X Forward", ""),
+ ('-Y', "-Y Forward", ""),
+ ('-Z', "-Z Forward", ""),
+ ),
+ default='-Z',
+ )
+
+ axis_up = EnumProperty(
+ name="Up",
+ items=(('X', "X Up", ""),
+ ('Y', "Y Up", ""),
+ ('Z', "Z Up", ""),
+ ('-X', "-X Up", ""),
+ ('-Y', "-Y Up", ""),
+ ('-Z', "-Z Up", ""),
+ ),
+ default='Y',
+ )
+
+ path_mode = path_reference_mode
+
+ def execute(self, context):
+ from . import export_obj
+
+ from mathutils import Matrix
+ keywords = self.as_keywords(ignore=("axis_forward", "axis_up", "global_scale", "check_existing", "filter_glob"))
+
+ global_matrix = Matrix()
+ global_matrix[0][0] = global_matrix[1][1] = global_matrix[2][2] = self.global_scale
+ global_matrix = global_matrix * axis_conversion(to_forward=self.axis_forward, to_up=self.axis_up).to_4x4()
+ keywords["global_matrix"] = global_matrix
+ return export_obj.save(self, context, **keywords)
+
+
+def menu_func_import(self, context):
+ self.layout.operator(ImportOBJ.bl_idname, text="Wavefront (.obj)")
+
+
+def menu_func_export(self, context):
+ self.layout.operator(ExportOBJ.bl_idname, text="Wavefront (.obj)")
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+ bpy.types.INFO_MT_file_import.append(menu_func_import)
+ bpy.types.INFO_MT_file_export.append(menu_func_export)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+
+ bpy.types.INFO_MT_file_import.remove(menu_func_import)
+ bpy.types.INFO_MT_file_export.remove(menu_func_export)
+
+
+# CONVERSION ISSUES
+# - matrix problem
+# - duplis - only tested dupliverts
+# - all scenes export
+# + normals calculation
+
+if __name__ == "__main__":
+ register()
diff --git a/io_scene_obj/export_obj.py b/io_scene_obj/export_obj.py
new file mode 100644
index 00000000..594a4167
--- /dev/null
+++ b/io_scene_obj/export_obj.py
@@ -0,0 +1,791 @@
+# ##### 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>
+
+import os
+import time
+import shutil
+
+import bpy
+import mathutils
+import bpy_extras.io_utils
+
+
+def name_compat(name):
+ if name is None:
+ return 'None'
+ else:
+ return name.replace(' ', '_')
+
+
+def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict):
+
+ world = scene.world
+ if world:
+ worldAmb = world.ambient_color[:]
+ else:
+ worldAmb = 0.0, 0.0, 0.0
+
+ source_dir = bpy.data.filepath
+ dest_dir = os.path.dirname(filepath)
+
+ file = open(filepath, "w", encoding="utf8", newline="\n")
+ file.write('# Blender MTL File: %r\n' % os.path.basename(bpy.data.filepath))
+ file.write('# Material Count: %i\n' % len(mtl_dict))
+
+ mtl_dict_values = list(mtl_dict.values())
+ mtl_dict_values.sort(key=lambda m: m[0])
+
+ # Write material/image combinations we have used.
+ # Using mtl_dict.values() directly gives un-predictable order.
+ for mtl_mat_name, mat, face_img in mtl_dict_values:
+
+ # Get the Blender data for the material and the image.
+ # Having an image named None will make a bug, dont do it :)
+
+ file.write('newmtl %s\n' % mtl_mat_name) # Define a new material: matname_imgname
+
+ if mat:
+ # convert from blenders spec to 0 - 1000 range.
+ if mat.specular_shader == 'WARDISO':
+ tspec = (0.4 - mat.specular_slope) / 0.0004
+ else:
+ tspec = (mat.specular_hardness - 1) * 1.9607843137254901
+ file.write('Ns %.6f\n' % tspec)
+ del tspec
+
+ file.write('Ka %.6f %.6f %.6f\n' % tuple(c * mat.ambient for c in worldAmb)) # Ambient, uses mirror colour,
+ file.write('Kd %.6f %.6f %.6f\n' % tuple(c * mat.diffuse_intensity for c in mat.diffuse_color)) # Diffuse
+ file.write('Ks %.6f %.6f %.6f\n' % tuple(c * mat.specular_intensity for c in mat.specular_color)) # Specular
+ if hasattr(mat, "ior"):
+ file.write('Ni %.6f\n' % mat.ior) # Refraction index
+ else:
+ file.write('Ni %.6f\n' % 1.0)
+ file.write('d %.6f\n' % mat.alpha) # Alpha (obj uses 'd' for dissolve)
+
+ # 0 to disable lighting, 1 for ambient & diffuse only (specular color set to black), 2 for full lighting.
+ if mat.use_shadeless:
+ file.write('illum 0\n') # ignore lighting
+ elif mat.specular_intensity == 0:
+ file.write('illum 1\n') # no specular.
+ else:
+ file.write('illum 2\n') # light normaly
+
+ else:
+ #write a dummy material here?
+ file.write('Ns 0\n')
+ file.write('Ka %.6f %.6f %.6f\n' % tuple(c for c in worldAmb)) # Ambient, uses mirror colour,
+ file.write('Kd 0.8 0.8 0.8\n')
+ file.write('Ks 0.8 0.8 0.8\n')
+ file.write('d 1\n') # No alpha
+ file.write('illum 2\n') # light normaly
+
+ # Write images!
+ if face_img: # We have an image on the face!
+ # write relative image path
+ rel = bpy_extras.io_utils.path_reference(face_img.filepath, source_dir, dest_dir, path_mode, "", copy_set)
+ file.write('map_Kd %s\n' % rel) # Diffuse mapping image
+
+ if mat: # No face image. if we havea material search for MTex image.
+ image_map = {}
+ # backwards so topmost are highest priority
+ for mtex in reversed(mat.texture_slots):
+ if mtex and mtex.texture.type == 'IMAGE':
+ image = mtex.texture.image
+ if image:
+ # texface overrides others
+ if mtex.use_map_color_diffuse and face_img is None:
+ image_map["map_Kd"] = image
+ if mtex.use_map_ambient:
+ image_map["map_Ka"] = image
+ if mtex.use_map_specular:
+ image_map["map_Ks"] = image
+ if mtex.use_map_alpha:
+ image_map["map_d"] = image
+ if mtex.use_map_translucency:
+ image_map["map_Tr"] = image
+ if mtex.use_map_normal:
+ image_map["map_Bump"] = image
+ if mtex.use_map_hardness:
+ image_map["map_Ns"] = image
+
+ for key, image in image_map.items():
+ filepath = bpy_extras.io_utils.path_reference(image.filepath, source_dir, dest_dir, path_mode, "", copy_set)
+ file.write('%s %s\n' % (key, repr(filepath)[1:-1]))
+
+ file.write('\n\n')
+
+ file.close()
+
+
+def test_nurbs_compat(ob):
+ if ob.type != 'CURVE':
+ return False
+
+ for nu in ob.data.splines:
+ if nu.point_count_v == 1 and nu.type != 'BEZIER': # not a surface and not bezier
+ return True
+
+ return False
+
+
+def write_nurb(file, ob, ob_mat):
+ tot_verts = 0
+ cu = ob.data
+
+ # use negative indices
+ for nu in cu.splines:
+ if nu.type == 'POLY':
+ DEG_ORDER_U = 1
+ else:
+ DEG_ORDER_U = nu.order_u - 1 # odd but tested to be correct
+
+ if nu.type == 'BEZIER':
+ print("\tWarning, bezier curve:", ob.name, "only poly and nurbs curves supported")
+ continue
+
+ if nu.point_count_v > 1:
+ print("\tWarning, surface:", ob.name, "only poly and nurbs curves supported")
+ continue
+
+ if len(nu.points) <= DEG_ORDER_U:
+ print("\tWarning, order_u is lower then vert count, skipping:", ob.name)
+ continue
+
+ pt_num = 0
+ do_closed = nu.use_cyclic_u
+ do_endpoints = (do_closed == 0) and nu.use_endpoint_u
+
+ for pt in nu.points:
+ pt = pt.co.to_3d() * ob_mat
+ file.write('v %.6f %.6f %.6f\n' % (pt[0], pt[1], pt[2]))
+ pt_num += 1
+ tot_verts += pt_num
+
+ file.write('g %s\n' % (name_compat(ob.name))) # name_compat(ob.getData(1)) could use the data name too
+ file.write('cstype bspline\n') # not ideal, hard coded
+ file.write('deg %d\n' % DEG_ORDER_U) # not used for curves but most files have it still
+
+ curve_ls = [-(i + 1) for i in range(pt_num)]
+
+ # 'curv' keyword
+ if do_closed:
+ if DEG_ORDER_U == 1:
+ pt_num += 1
+ curve_ls.append(-1)
+ else:
+ pt_num += DEG_ORDER_U
+ curve_ls = curve_ls + curve_ls[0:DEG_ORDER_U]
+
+ file.write('curv 0.0 1.0 %s\n' % (" ".join([str(i) for i in curve_ls]))) # Blender has no U and V values for the curve
+
+ # 'parm' keyword
+ tot_parm = (DEG_ORDER_U + 1) + pt_num
+ tot_parm_div = float(tot_parm - 1)
+ parm_ls = [(i / tot_parm_div) for i in range(tot_parm)]
+
+ if do_endpoints: # end points, force param
+ for i in range(DEG_ORDER_U + 1):
+ parm_ls[i] = 0.0
+ parm_ls[-(1 + i)] = 1.0
+
+ file.write("parm u %s\n" % " ".join(["%.6f" % i for i in parm_ls]))
+
+ file.write('end\n')
+
+ return tot_verts
+
+
+def write_file(filepath, objects, scene,
+ EXPORT_TRI=False,
+ EXPORT_EDGES=False,
+ EXPORT_NORMALS=False,
+ EXPORT_NORMALS_HQ=False,
+ EXPORT_UV=True,
+ EXPORT_MTL=True,
+ EXPORT_APPLY_MODIFIERS=True,
+ EXPORT_BLEN_OBS=True,
+ EXPORT_GROUP_BY_OB=False,
+ EXPORT_GROUP_BY_MAT=False,
+ EXPORT_KEEP_VERT_ORDER=False,
+ EXPORT_POLYGROUPS=False,
+ EXPORT_CURVE_AS_NURBS=True,
+ EXPORT_GLOBAL_MATRIX=None,
+ EXPORT_PATH_MODE='AUTO',
+ ):
+ '''
+ Basic write function. The context and options must be already set
+ This can be accessed externaly
+ eg.
+ write( 'c:\\test\\foobar.obj', Blender.Object.GetSelected() ) # Using default options.
+ '''
+
+ if EXPORT_GLOBAL_MATRIX is None:
+ EXPORT_GLOBAL_MATRIX = mathutils.Matrix()
+
+ # XXX
+ import math
+
+ def veckey3d(v):
+ return round(v.x, 6), round(v.y, 6), round(v.z, 6)
+
+ def veckey2d(v):
+ return round(v[0], 6), round(v[1], 6)
+
+ def findVertexGroupName(face, vWeightMap):
+ """
+ Searches the vertexDict to see what groups is assigned to a given face.
+ We use a frequency system in order to sort out the name because a given vetex can
+ belong to two or more groups at the same time. To find the right name for the face
+ we list all the possible vertex group names with their frequency and then sort by
+ frequency in descend order. The top element is the one shared by the highest number
+ of vertices is the face's group
+ """
+ weightDict = {}
+ for vert_index in face.vertices:
+# for vert in face:
+ vWeights = vWeightMap[vert_index]
+# vWeights = vWeightMap[vert]
+ for vGroupName, weight in vWeights:
+ weightDict[vGroupName] = weightDict.get(vGroupName, 0) + weight
+
+ if weightDict:
+ alist = [(weight, vGroupName) for vGroupName, weight in weightDict.items()] # sort least to greatest amount of weight
+ alist.sort()
+ return(alist[-1][1]) # highest value last
+ else:
+ return '(null)'
+
+ print('OBJ Export path: %r' % filepath)
+ temp_mesh_name = '~tmp-mesh'
+
+ time1 = time.clock()
+# time1 = sys.time()
+# scn = Scene.GetCurrent()
+
+ file = open(filepath, "w", encoding="utf8", newline="\n")
+
+ # Write Header
+ file.write('# Blender v%s OBJ File: %r\n' % (bpy.app.version_string, os.path.basename(bpy.data.filepath)))
+ file.write('# www.blender.org\n')
+
+ # Tell the obj file what material file to use.
+ if EXPORT_MTL:
+ mtlfilepath = os.path.splitext(filepath)[0] + ".mtl"
+ file.write('mtllib %s\n' % repr(os.path.basename(mtlfilepath))[1:-1]) # filepath can contain non utf8 chars, use repr
+
+ # Initialize totals, these are updated each object
+ totverts = totuvco = totno = 1
+
+ face_vert_index = 1
+
+ globalNormals = {}
+
+ # A Dict of Materials
+ # (material.name, image.name):matname_imagename # matname_imagename has gaps removed.
+ mtl_dict = {}
+
+ copy_set = set()
+
+ # Get all meshes
+ for ob_main in objects:
+
+ # ignore dupli children
+ if ob_main.parent and ob_main.parent.dupli_type != 'NONE':
+ # XXX
+ print(ob_main.name, 'is a dupli child - ignoring')
+ continue
+
+ obs = []
+ if ob_main.dupli_type != 'NONE':
+ # XXX
+ print('creating dupli_list on', ob_main.name)
+ ob_main.dupli_list_create(scene)
+
+ obs = [(dob.object, dob.matrix) for dob in ob_main.dupli_list]
+
+ # XXX debug print
+ print(ob_main.name, 'has', len(obs), 'dupli children')
+ else:
+ obs = [(ob_main, ob_main.matrix_world)]
+
+ for ob, ob_mat in obs:
+
+ # Nurbs curve support
+ if EXPORT_CURVE_AS_NURBS and test_nurbs_compat(ob):
+ ob_mat = EXPORT_GLOBAL_MATRIX * ob_mat
+ totverts += write_nurb(file, ob, ob_mat)
+ continue
+ # END NURBS
+
+ try:
+ me = ob.to_mesh(scene, EXPORT_APPLY_MODIFIERS, 'PREVIEW')
+ except RuntimeError:
+ me = None
+
+ if me is None:
+ continue
+
+ me.transform(EXPORT_GLOBAL_MATRIX * ob_mat)
+
+# # Will work for non meshes now! :)
+# me= BPyMesh.getMeshFromObject(ob, containerMesh, EXPORT_APPLY_MODIFIERS, EXPORT_POLYGROUPS, scn)
+# if not me:
+# continue
+
+ if EXPORT_UV:
+ faceuv = len(me.uv_textures) > 0
+ if faceuv:
+ uv_layer = me.uv_textures.active.data[:]
+ else:
+ faceuv = False
+
+ me_verts = me.vertices[:]
+
+ # Make our own list so it can be sorted to reduce context switching
+ face_index_pairs = [(face, index) for index, face in enumerate(me.faces)]
+ # faces = [ f for f in me.faces ]
+
+ if EXPORT_EDGES:
+ edges = me.edges
+ else:
+ edges = []
+
+ if not (len(face_index_pairs) + len(edges) + len(me.vertices)): # Make sure there is somthing to write
+
+ # clean up
+ bpy.data.meshes.remove(me)
+
+ continue # dont bother with this mesh.
+
+ # XXX
+ # High Quality Normals
+ if EXPORT_NORMALS and face_index_pairs:
+ me.calc_normals()
+# if EXPORT_NORMALS_HQ:
+# BPyMesh.meshCalcNormals(me)
+# else:
+# # transforming normals is incorrect
+# # when the matrix is scaled,
+# # better to recalculate them
+# me.calcNormals()
+
+ materials = me.materials
+
+ materialNames = []
+ materialItems = [m for m in materials]
+ if materials:
+ for mat in materials:
+ if mat:
+ materialNames.append(mat.name)
+ else:
+ materialNames.append(None)
+ # Cant use LC because some materials are None.
+ # materialNames = map(lambda mat: mat.name, materials) # Bug Blender, dosent account for null materials, still broken.
+
+ # Possible there null materials, will mess up indices
+ # but at least it will export, wait until Blender gets fixed.
+ materialNames.extend((16 - len(materialNames)) * [None])
+ materialItems.extend((16 - len(materialItems)) * [None])
+
+ # Sort by Material, then images
+ # so we dont over context switch in the obj file.
+ if EXPORT_KEEP_VERT_ORDER:
+ pass
+ elif faceuv:
+ face_index_pairs.sort(key=lambda a: (a[0].material_index, hash(uv_layer[a[1]].image), a[0].use_smooth))
+ elif len(materials) > 1:
+ face_index_pairs.sort(key=lambda a: (a[0].material_index, a[0].use_smooth))
+ else:
+ # no materials
+ face_index_pairs.sort(key=lambda a: a[0].use_smooth)
+# if EXPORT_KEEP_VERT_ORDER:
+# pass
+# elif faceuv:
+# try: faces.sort(key = lambda a: (a.mat, a.image, a.use_smooth))
+# except: faces.sort(lambda a,b: cmp((a.mat, a.image, a.use_smooth), (b.mat, b.image, b.use_smooth)))
+# elif len(materials) > 1:
+# try: faces.sort(key = lambda a: (a.mat, a.use_smooth))
+# except: faces.sort(lambda a,b: cmp((a.mat, a.use_smooth), (b.mat, b.use_smooth)))
+# else:
+# # no materials
+# try: faces.sort(key = lambda a: a.use_smooth)
+# except: faces.sort(lambda a,b: cmp(a.use_smooth, b.use_smooth))
+
+ # Set the default mat to no material and no image.
+ contextMat = 0, 0 # Can never be this, so we will label a new material teh first chance we get.
+ contextSmooth = None # Will either be true or false, set bad to force initialization switch.
+
+ if EXPORT_BLEN_OBS or EXPORT_GROUP_BY_OB:
+ name1 = ob.name
+ name2 = ob.data.name
+ if name1 == name2:
+ obnamestring = name_compat(name1)
+ else:
+ obnamestring = '%s_%s' % (name_compat(name1), name_compat(name2))
+
+ if EXPORT_BLEN_OBS:
+ file.write('o %s\n' % obnamestring) # Write Object name
+ else: # if EXPORT_GROUP_BY_OB:
+ file.write('g %s\n' % obnamestring)
+
+ # Vert
+ for v in me_verts:
+ file.write('v %.6f %.6f %.6f\n' % v.co[:])
+
+ # UV
+ if faceuv:
+ uv_face_mapping = [[0, 0, 0, 0] for i in range(len(face_index_pairs))] # a bit of a waste for tri's :/
+
+ uv_dict = {} # could use a set() here
+ uv_layer = me.uv_textures.active.data
+ for f, f_index in face_index_pairs:
+ for uv_index, uv in enumerate(uv_layer[f_index].uv):
+ uvkey = veckey2d(uv)
+ try:
+ uv_face_mapping[f_index][uv_index] = uv_dict[uvkey]
+ except:
+ uv_face_mapping[f_index][uv_index] = uv_dict[uvkey] = len(uv_dict)
+ file.write('vt %.6f %.6f\n' % uv[:])
+
+ uv_unique_count = len(uv_dict)
+# del uv, uvkey, uv_dict, f_index, uv_index
+ # Only need uv_unique_count and uv_face_mapping
+
+ # NORMAL, Smooth/Non smoothed.
+ if EXPORT_NORMALS:
+ for f, f_index in face_index_pairs:
+ if f.use_smooth:
+ for v_idx in f.vertices:
+ v = me_verts[v_idx]
+ noKey = veckey3d(v.normal)
+ if noKey not in globalNormals:
+ globalNormals[noKey] = totno
+ totno += 1
+ file.write('vn %.6f %.6f %.6f\n' % noKey)
+ else:
+ # Hard, 1 normal from the face.
+ noKey = veckey3d(f.normal)
+ if noKey not in globalNormals:
+ globalNormals[noKey] = totno
+ totno += 1
+ file.write('vn %.6f %.6f %.6f\n' % noKey)
+
+ if not faceuv:
+ f_image = None
+
+ # XXX
+ if EXPORT_POLYGROUPS:
+ # Retrieve the list of vertex groups
+ vertGroupNames = [g.name for g in ob.vertex_groups]
+
+ currentVGroup = ''
+ # Create a dictionary keyed by face id and listing, for each vertex, the vertex groups it belongs to
+ vgroupsMap = [[] for _i in range(len(me_verts))]
+ for v_idx, v in enumerate(me.vertices):
+ for g in v.groups:
+ vgroupsMap[v_idx].append((vertGroupNames[g.group], g.weight))
+
+ for f, f_index in face_index_pairs:
+ f_smooth = f.use_smooth
+ f_mat = min(f.material_index, len(materialNames) - 1)
+
+ if faceuv:
+ tface = uv_layer[f_index]
+ f_image = tface.image
+
+ # MAKE KEY
+ if faceuv and f_image: # Object is always true.
+ key = materialNames[f_mat], f_image.name
+ else:
+ key = materialNames[f_mat], None # No image, use None instead.
+
+ # Write the vertex group
+ if EXPORT_POLYGROUPS:
+ if ob.vertex_groups:
+ # find what vertext group the face belongs to
+ theVGroup = findVertexGroupName(f, vgroupsMap)
+ if theVGroup != currentVGroup:
+ currentVGroup = theVGroup
+ file.write('g %s\n' % theVGroup)
+
+ # CHECK FOR CONTEXT SWITCH
+ if key == contextMat:
+ pass # Context already switched, dont do anything
+ else:
+ if key[0] is None and key[1] is None:
+ # Write a null material, since we know the context has changed.
+ if EXPORT_GROUP_BY_MAT:
+ # can be mat_image or (null)
+ file.write("g %s_%s\n" % (name_compat(ob.name), name_compat(ob.data.name))) # can be mat_image or (null)
+ file.write("usemtl (null)\n") # mat, image
+
+ else:
+ mat_data = mtl_dict.get(key)
+ if not mat_data:
+ # First add to global dict so we can export to mtl
+ # Then write mtl
+
+ # Make a new names from the mat and image name,
+ # converting any spaces to underscores with name_compat.
+
+ # If none image dont bother adding it to the name
+ if key[1] is None:
+ mat_data = mtl_dict[key] = ("%s" % name_compat(key[0])), materialItems[f_mat], f_image
+ else:
+ mat_data = mtl_dict[key] = ("%s_%s" % (name_compat(key[0]), name_compat(key[1]))), materialItems[f_mat], f_image
+
+ if EXPORT_GROUP_BY_MAT:
+ file.write("g %s_%s_%s\n" % (name_compat(ob.name), name_compat(ob.data.name), mat_data[0])) # can be mat_image or (null)
+
+ file.write("usemtl %s\n" % mat_data[0]) # can be mat_image or (null)
+
+ contextMat = key
+ if f_smooth != contextSmooth:
+ if f_smooth: # on now off
+ file.write('s 1\n')
+ contextSmooth = f_smooth
+ else: # was off now on
+ file.write('s off\n')
+ contextSmooth = f_smooth
+
+ f_v_orig = [(vi, me_verts[v_idx]) for vi, v_idx in enumerate(f.vertices)]
+
+ if not EXPORT_TRI or len(f_v_orig) == 3:
+ f_v_iter = (f_v_orig, )
+ else:
+ f_v_iter = (f_v_orig[0], f_v_orig[1], f_v_orig[2]), (f_v_orig[0], f_v_orig[2], f_v_orig[3])
+
+ # support for triangulation
+ for f_v in f_v_iter:
+ file.write('f')
+
+ if faceuv:
+ if EXPORT_NORMALS:
+ if f_smooth: # Smoothed, use vertex normals
+ for vi, v in f_v:
+ file.write(" %d/%d/%d" % \
+ (v.index + totverts,
+ totuvco + uv_face_mapping[f_index][vi],
+ globalNormals[veckey3d(v.normal)])) # vert, uv, normal
+
+ else: # No smoothing, face normals
+ no = globalNormals[veckey3d(f.normal)]
+ for vi, v in f_v:
+ file.write(" %d/%d/%d" % \
+ (v.index + totverts,
+ totuvco + uv_face_mapping[f_index][vi],
+ no)) # vert, uv, normal
+ else: # No Normals
+ for vi, v in f_v:
+ file.write(" %d/%d" % (\
+ v.index + totverts,\
+ totuvco + uv_face_mapping[f_index][vi])) # vert, uv
+
+ face_vert_index += len(f_v)
+
+ else: # No UV's
+ if EXPORT_NORMALS:
+ if f_smooth: # Smoothed, use vertex normals
+ for vi, v in f_v:
+ file.write(" %d//%d" %
+ (v.index + totverts, globalNormals[veckey3d(v.normal)]))
+ else: # No smoothing, face normals
+ no = globalNormals[veckey3d(f.normal)]
+ for vi, v in f_v:
+ file.write(" %d//%d" % (v.index + totverts, no))
+ else: # No Normals
+ for vi, v in f_v:
+ file.write(" %d" % (v.index + totverts))
+
+ file.write('\n')
+
+ # Write edges.
+ if EXPORT_EDGES:
+ for ed in edges:
+ if ed.is_loose:
+ file.write('f %d %d\n' % (ed.vertices[0] + totverts, ed.vertices[1] + totverts))
+
+ # Make the indices global rather then per mesh
+ totverts += len(me_verts)
+ if faceuv:
+ totuvco += uv_unique_count
+
+ # clean up
+ bpy.data.meshes.remove(me)
+
+ if ob_main.dupli_type != 'NONE':
+ ob_main.dupli_list_clear()
+
+ file.close()
+
+ # Now we have all our materials, save them
+ if EXPORT_MTL:
+ write_mtl(scene, mtlfilepath, EXPORT_PATH_MODE, copy_set, mtl_dict)
+
+ # copy all collected files.
+ bpy_extras.io_utils.path_reference_copy(copy_set)
+
+ print("OBJ Export time: %.2f" % (time.clock() - time1))
+
+
+def _write(context, filepath,
+ EXPORT_TRI, # ok
+ EXPORT_EDGES,
+ EXPORT_NORMALS, # not yet
+ EXPORT_NORMALS_HQ, # not yet
+ EXPORT_UV, # ok
+ EXPORT_MTL,
+ EXPORT_APPLY_MODIFIERS, # ok
+ EXPORT_BLEN_OBS,
+ EXPORT_GROUP_BY_OB,
+ EXPORT_GROUP_BY_MAT,
+ EXPORT_KEEP_VERT_ORDER,
+ EXPORT_POLYGROUPS,
+ EXPORT_CURVE_AS_NURBS,
+ EXPORT_SEL_ONLY, # ok
+ EXPORT_ALL_SCENES, # XXX not working atm
+ EXPORT_ANIMATION,
+ EXPORT_GLOBAL_MATRIX,
+ EXPORT_PATH_MODE,
+ ): # Not used
+
+ base_name, ext = os.path.splitext(filepath)
+ context_name = [base_name, '', '', ext] # Base name, scene name, frame number, extension
+
+ orig_scene = context.scene
+
+ # Exit edit mode before exporting, so current object states are exported properly.
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+# if EXPORT_ALL_SCENES:
+# export_scenes = bpy.data.scenes
+# else:
+# export_scenes = [orig_scene]
+
+ # XXX only exporting one scene atm since changing
+ # current scene is not possible.
+ # Brecht says that ideally in 2.5 we won't need such a function,
+ # allowing multiple scenes open at once.
+ export_scenes = [orig_scene]
+
+ # Export all scenes.
+ for scene in export_scenes:
+ orig_frame = scene.frame_current
+
+ if EXPORT_ALL_SCENES: # Add scene name into the context_name
+ context_name[1] = '_%s' % bpy.path.clean_name(scene.name) # WARNING, its possible that this could cause a collision. we could fix if were feeling parranoied.
+
+ # Export an animation?
+ if EXPORT_ANIMATION:
+ scene_frames = range(scene.frame_start, scene.frame_end + 1) # Up to and including the end frame.
+ else:
+ scene_frames = [orig_frame] # Dont export an animation.
+
+ # Loop through all frames in the scene and export.
+ for frame in scene_frames:
+ if EXPORT_ANIMATION: # Add frame to the filepath.
+ context_name[2] = '_%.6d' % frame
+
+ scene.frame_set(frame, 0.0)
+ if EXPORT_SEL_ONLY:
+ objects = context.selected_objects
+ else:
+ objects = scene.objects
+
+ full_path = ''.join(context_name)
+
+ # erm... bit of a problem here, this can overwrite files when exporting frames. not too bad.
+ # EXPORT THE FILE.
+ write_file(full_path, objects, scene,
+ EXPORT_TRI,
+ EXPORT_EDGES,
+ EXPORT_NORMALS,
+ EXPORT_NORMALS_HQ,
+ EXPORT_UV,
+ EXPORT_MTL,
+ EXPORT_APPLY_MODIFIERS,
+ EXPORT_BLEN_OBS,
+ EXPORT_GROUP_BY_OB,
+ EXPORT_GROUP_BY_MAT,
+ EXPORT_KEEP_VERT_ORDER,
+ EXPORT_POLYGROUPS,
+ EXPORT_CURVE_AS_NURBS,
+ EXPORT_GLOBAL_MATRIX,
+ EXPORT_PATH_MODE,
+ )
+
+ scene.frame_set(orig_frame, 0.0)
+
+ # Restore old active scene.
+# orig_scene.makeCurrent()
+# Window.WaitCursor(0)
+
+
+'''
+Currently the exporter lacks these features:
+* multiple scene export (only active scene is written)
+* particles
+'''
+
+
+def save(operator, context, filepath="",
+ use_triangles=False,
+ use_edges=True,
+ use_normals=False,
+ use_hq_normals=False,
+ use_uvs=True,
+ use_materials=True,
+ use_apply_modifiers=True,
+ use_blen_objects=True,
+ group_by_object=False,
+ group_by_material=False,
+ keep_vertex_order=False,
+ use_vertex_groups=False,
+ use_nurbs=True,
+ use_selection=True,
+ use_all_scenes=False,
+ use_animation=False,
+ global_matrix=None,
+ path_mode='AUTO'
+ ):
+
+ _write(context, filepath,
+ EXPORT_TRI=use_triangles,
+ EXPORT_EDGES=use_edges,
+ EXPORT_NORMALS=use_normals,
+ EXPORT_NORMALS_HQ=use_hq_normals,
+ EXPORT_UV=use_uvs,
+ EXPORT_MTL=use_materials,
+ EXPORT_APPLY_MODIFIERS=use_apply_modifiers,
+ EXPORT_BLEN_OBS=use_blen_objects,
+ EXPORT_GROUP_BY_OB=group_by_object,
+ EXPORT_GROUP_BY_MAT=group_by_material,
+ EXPORT_KEEP_VERT_ORDER=keep_vertex_order,
+ EXPORT_POLYGROUPS=use_vertex_groups,
+ EXPORT_CURVE_AS_NURBS=use_nurbs,
+ EXPORT_SEL_ONLY=use_selection,
+ EXPORT_ALL_SCENES=use_all_scenes,
+ EXPORT_ANIMATION=use_animation,
+ EXPORT_GLOBAL_MATRIX=global_matrix,
+ EXPORT_PATH_MODE=path_mode,
+ )
+
+ return {'FINISHED'}
diff --git a/io_scene_obj/import_obj.py b/io_scene_obj/import_obj.py
new file mode 100644
index 00000000..697c6508
--- /dev/null
+++ b/io_scene_obj/import_obj.py
@@ -0,0 +1,1148 @@
+# ##### 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 os
+import time
+import bpy
+import mathutils
+from bpy_extras.io_utils import unpack_list, unpack_face_list
+from bpy_extras.image_utils import load_image
+
+
+def line_value(line_split):
+ '''
+ Returns 1 string represneting 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, use_image_search):
+ if b'_' in imagepath:
+ image = load_image(imagepath.replace(b'_', b' '), DIR)
+ if image:
+ return image
+
+ image = load_image(imagepath, DIR)
+ if image:
+ return image
+
+ print("failed to load %r doesn't exist" % imagepath)
+ return None
+
+# def obj_image_load(imagepath, DIR, use_image_search):
+# '''
+# Mainly uses comprehensiveImageLoad
+# but tries to replace '_' with ' ' for Max's exporter replaces spaces with underscores.
+# '''
+
+# if '_' in imagepath:
+# image= BPyImage.comprehensiveImageLoad(imagepath, DIR, PLACE_HOLDER= False, RECURSIVE= use_image_search)
+# if image: return image
+# # Did the exporter rename the image?
+# image= BPyImage.comprehensiveImageLoad(imagepath.replace('_', ' '), DIR, PLACE_HOLDER= False, RECURSIVE= use_image_search)
+# if image: return image
+
+# # Return an image, placeholder if it dosnt exist
+# image= BPyImage.comprehensiveImageLoad(imagepath, DIR, PLACE_HOLDER= True, RECURSIVE= use_image_search)
+# return image
+
+
+def create_materials(filepath, material_libs, unique_materials, unique_material_images, use_image_search):
+ '''
+ 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)
+
+ #==================================================================================#
+ # This function sets textures defined in .mtl file #
+ #==================================================================================#
+ def load_material_image(blender_material, context_material_name, imagepath, type):
+
+ 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)
+ has_data = False
+
+ if image:
+ texture.image = image
+ has_data = image.has_data
+
+ # Adds textures for materials (rendering)
+ if type == 'Kd':
+ if has_data and image.depth == 32:
+ # Image has alpha
+
+ mtex = blender_material.texture_slots.add()
+ mtex.texture = texture
+ mtex.texture_coords = 'UV'
+ mtex.use_map_color_diffuse = True
+ mtex.use_map_alpha = True
+
+ texture.use_mipmap = True
+ texture.use_interpolation = True
+ texture.use_alpha = True
+ blender_material.use_transparency = True
+ blender_material.alpha = 0.0
+ else:
+ 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, has_data # 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'
+ blender_material.alpha = 0.0
+ # Todo, unset deffuse material alpha if it has an alpha channel
+
+ elif type == 'refl':
+ mtex = blender_material.texture_slots.add()
+ mtex.use_map_color_diffuse = False
+
+ mtex.texture = texture
+ mtex.texture_coords = 'UV'
+ mtex.use_map_reflect = 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, False # assign None to all material images to start with, add to later.
+
+ unique_materials[None] = None
+ unique_material_images[None] = None, False
+
+ 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'#'):
+ pass
+ elif line.startswith(b'newmtl'):
+ context_material_name = line_value(line.split())
+ context_material = unique_materials.get(context_material_name)
+
+ elif context_material:
+ # we need to make a material to assign properties to it.
+ line_split = line.split()
+ line_lower = line.lower().lstrip()
+ if line_lower.startswith(b'ka'):
+ context_material.mirror_color = float(line_split[1]), float(line_split[2]), float(line_split[3])
+ elif line_lower.startswith(b'kd'):
+ context_material.diffuse_color = float(line_split[1]), float(line_split[2]), float(line_split[3])
+ elif line_lower.startswith(b'ks'):
+ context_material.specular_color = float(line_split[1]), float(line_split[2]), float(line_split[3])
+ elif line_lower.startswith(b'ns'):
+ context_material.specular_hardness = int((float(line_split[1]) * 0.51))
+ elif line_lower.startswith(b'ni'): # Refraction index
+ context_material.raytrace_transparency.ior = max(1, min(float(line_split[1]), 3)) # between 1 and 3
+ elif line_lower.startswith(b'd') or line_lower.startswith(b'tr'):
+ context_material.alpha = float(line_split[1])
+ context_material.use_transparency = True
+ context_material.transparency_method = 'Z_TRANSPARENCY'
+ elif line_lower.startswith(b'tf'):
+ # rgb, filter color, blender has no support for this.
+ pass
+ elif line_lower.startswith(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'
+ context_material.alpha = 0.0
+
+ if do_glass:
+ 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_lower.startswith(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_lower.startswith(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_lower.startswith(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_lower.startswith(b'map_bump') or line_lower.startswith(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_lower.startswith(b'map_d') or line_lower.startswith(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_lower.startswith(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:
+ # use the filename for the object name since we arnt 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[4]
+
+ if oldkey != key:
+ # Check the key has changed.
+ try:
+ verts_split, faces_split, unique_materials_split, vert_remap = face_split_dict[key]
+ except KeyError:
+ faces_split = []
+ verts_split = []
+ unique_materials_split = {}
+ vert_remap = [-1] * len(verts_loc)
+
+ face_split_dict[key] = (verts_split, faces_split, unique_materials_split, vert_remap)
+
+ 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):
+ if vert_remap[i] == -1:
+ new_index = len(verts_split)
+ vert_remap[i] = new_index # set the new remapped index so we only add once and can reference next time.
+ face_vert_loc_indices[enum] = new_index # remap to the local index
+ verts_split.append(verts_loc[i]) # add the vert to the local verts
+ else:
+ face_vert_loc_indices[enum] = vert_remap[i] # remap to the local index
+
+ matname = face[2]
+ if matname and matname not in unique_materials_split:
+ unique_materials_split[matname] = unique_materials[matname]
+
+ faces_split.append(face)
+
+ # remove one of the itemas and reorder
+ return [(value[0], value[1], value[2], key_to_name(key)) for key, value in list(face_split_dict.items())]
+
+
+def create_mesh(new_objects, has_ngons, use_ngons, use_edges, verts_loc, 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 fgons, sharp edges and assigning materials
+ '''
+ from bpy_extras.mesh_utils import ngon_tesselate
+
+ if not has_ngons:
+ use_ngons = False
+
+ if unique_smooth_groups:
+ sharp_edges = {}
+ smooth_group_users = {context_smooth_group: {} for context_smooth_group in list(unique_smooth_groups.keys())}
+ context_smooth_group_old = -1
+
+ # Split fgons into tri's
+ fgon_edges = {} # Used for storing fgon keys
+ if use_edges:
+ edges = []
+
+ context_object = None
+
+ # reverse loop through face indices
+ for f_idx in range(len(faces) - 1, -1, -1):
+
+ face_vert_loc_indices,\
+ face_vert_tex_indices,\
+ context_material,\
+ context_smooth_group,\
+ context_object = 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:
+ # generators are better in python 2.4+ but can't be used in 2.3
+ # edges.extend( (face_vert_loc_indices[i], face_vert_loc_indices[i+1]) for i in xrange(len_face_vert_loc_indices-1) )
+ 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
+
+ for i in range(len_face_vert_loc_indices):
+ i1 = face_vert_loc_indices[i]
+ i2 = face_vert_loc_indices[i - 1]
+ if i1 > i2:
+ i1, i2 = i2, i1
+
+ try:
+ edge_dict[i1, i2] += 1
+ except KeyError:
+ edge_dict[i1, i2] = 1
+
+ # FGons into triangles
+ if has_ngons and len_face_vert_loc_indices > 4:
+
+ ngon_face_indices = ngon_tesselate(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_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]
+ )
+
+ # edges to make fgons
+ if use_ngons:
+ edge_users = {}
+ for ngon in ngon_face_indices:
+ for i in (0, 1, 2):
+ i1 = face_vert_loc_indices[ngon[i]]
+ i2 = face_vert_loc_indices[ngon[i - 1]]
+ if i1 > i2:
+ i1, i2 = i2, i1
+
+ try:
+ edge_users[i1, i2] += 1
+ except KeyError:
+ edge_users[i1, i2] = 1
+
+ for key, users in edge_users.items():
+ if users > 1:
+ fgon_edges[key] = None
+
+ # remove all after 3, means we dont have to pop this one.
+ faces.pop(f_idx)
+
+ # Build sharp edges
+ if unique_smooth_groups:
+ for edge_dict in list(smooth_group_users.values()):
+ for key, users in list(edge_dict.items()):
+ if users == 1: # This edge is on the boundry of a group
+ sharp_edges[key] = None
+
+ # 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 list(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.faces.add(len(faces))
+
+ # verts_loc is a list of (x, y, z) tuples
+ me.vertices.foreach_set("co", unpack_list(verts_loc))
+
+ # faces is a list of (vert_indices, texco_indices, ...) tuples
+ # XXX faces should contain either 3 or 4 verts
+ # XXX no check for valid face indices
+ me.faces.foreach_set("vertices_raw", unpack_face_list([f[0] for f in faces]))
+
+ if verts_tex and me.faces:
+ me.uv_textures.new()
+
+ context_material_old = -1 # avoid a dict lookup
+ mat = 0 # rare case it may be un-initialized.
+ me_faces = me.faces
+
+ for i, face in enumerate(faces):
+ if len(face[0]) < 2:
+ pass # raise "bad face"
+ elif len(face[0]) == 2:
+ if use_edges:
+ edges.append(face[0])
+ else:
+
+ blender_face = me.faces[i]
+
+ face_vert_loc_indices,\
+ face_vert_tex_indices,\
+ context_material,\
+ context_smooth_group,\
+ context_object = face
+
+ if context_smooth_group:
+ blender_face.use_smooth = True
+
+ if context_material:
+ if context_material_old is not context_material:
+ mat = material_mapping[context_material]
+ context_material_old = context_material
+
+ blender_face.material_index = mat
+# blender_face.mat= mat
+
+ if verts_tex:
+
+ blender_tface = me.uv_textures[0].data[i]
+
+ if context_material:
+ image, has_data = unique_material_images[context_material]
+ if image: # Can be none if the material dosnt have an image.
+ blender_tface.image = image
+ blender_tface.use_image = True
+ if has_data and image.depth == 32:
+ blender_tface.blend_type = 'ALPHA'
+
+ # BUG - Evil eekadoodle problem where faces that have vert index 0 location at 3 or 4 are shuffled.
+ if len(face_vert_loc_indices) == 4:
+ if face_vert_loc_indices[2] == 0 or face_vert_loc_indices[3] == 0:
+ face_vert_tex_indices = face_vert_tex_indices[2], face_vert_tex_indices[3], face_vert_tex_indices[0], face_vert_tex_indices[1]
+ else: # length of 3
+ if face_vert_loc_indices[2] == 0:
+ face_vert_tex_indices = face_vert_tex_indices[1], face_vert_tex_indices[2], face_vert_tex_indices[0]
+ # END EEEKADOODLE FIX
+
+ # assign material, uv's and image
+ blender_tface.uv1 = verts_tex[face_vert_tex_indices[0]]
+ blender_tface.uv2 = verts_tex[face_vert_tex_indices[1]]
+ blender_tface.uv3 = verts_tex[face_vert_tex_indices[2]]
+
+ if len(face_vert_loc_indices) == 4:
+ blender_tface.uv4 = verts_tex[face_vert_tex_indices[3]]
+
+# for ii, uv in enumerate(blender_face.uv):
+# uv.x, uv.y= verts_tex[face_vert_tex_indices[ii]]
+ del me_faces
+# del ALPHA
+
+ if use_edges and not edges:
+ use_edges = False
+
+ 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_edges.extend( edges )
+
+# del me_edges
+
+ # Add edge faces.
+# me_edges= me.edges
+
+ def edges_match(e1, e2):
+ return (e1[0] == e2[0] and e1[1] == e2[1]) or (e1[0] == e2[1] and e1[1] == e2[0])
+
+ # XXX slow
+# if use_ngons and fgon_edges:
+# for fgon_edge in fgon_edges.keys():
+# for ed in me.edges:
+# if edges_match(fgon_edge, ed.vertices):
+# ed.is_fgon = True
+
+# if use_ngons and fgon_edges:
+# FGON= Mesh.EdgeFlags.FGON
+# for ed in me.findEdges( fgon_edges.keys() ):
+# if ed is not None:
+# me_edges[ed].flag |= FGON
+# del FGON
+
+ # XXX slow
+# if unique_smooth_groups and sharp_edges:
+# for sharp_edge in sharp_edges.keys():
+# for ed in me.edges:
+# if edges_match(sharp_edge, ed.vertices):
+# ed.use_edge_sharp = True
+
+# if unique_smooth_groups and sharp_edges:
+# SHARP= Mesh.EdgeFlags.SHARP
+# for ed in me.findEdges( sharp_edges.keys() ):
+# if ed is not None:
+# me_edges[ed].flag |= SHARP
+# del SHARP
+
+ me.validate()
+ me.update(calc_edges=use_edges)
+
+ ob = bpy.data.objects.new("Mesh", 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()
+ # incase all vert values were ints
+ return float
+
+
+def load(operator, context, filepath,
+ global_clamp_size=0.0,
+ use_ngons=True,
+ use_smooth_groups=True,
+ use_edges=True,
+ use_split_objects=True,
+ use_split_groups=True,
+ use_image_search=True,
+ use_groups_as_vgroups=False,
+ 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
+ '''
+ 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_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
+
+ has_ngons = False
+ # has_smoothgroups= False - is explicit with len(unique_smooth_groups) being > 0
+
+ # 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''
+
+ print("\tparsing obj file...")
+ time_sub = time.time()
+# time_sub= sys.time()
+
+ file = open(filepath, 'rb')
+ for line in file: # .readlines():
+ line = line.lstrip() # rare cases there is white space at the start of the line
+
+ if line.startswith(b"v "):
+ line_split = line.split()
+ verts_loc.append((float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])))
+
+ elif line.startswith(b"vn "):
+ pass
+
+ elif line.startswith(b"vt "):
+ line_split = line.split()
+ verts_tex.append((float_func(line_split[1]), float_func(line_split[2])))
+
+ # Handel 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.startswith(b'f') or context_multi_line == b'f':
+
+ if context_multi_line:
+ # use face_vert_loc_indices and face_vert_tex_indices previously defined and used the obj_face
+ line_split = line.split()
+
+ else:
+ line_split = line[2:].split()
+ face_vert_loc_indices = []
+ face_vert_tex_indices = []
+
+ # Instance a face
+ faces.append((\
+ face_vert_loc_indices,\
+ face_vert_tex_indices,\
+ context_material,\
+ context_smooth_group,\
+ context_object\
+ ))
+
+ if strip_slash(line_split):
+ context_multi_line = b'f'
+ else:
+ context_multi_line = b''
+
+ for v in line_split:
+ obj_vert = v.split(b'/')
+ vert_loc_index = int(obj_vert[0]) - 1
+ # 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)
+
+ # Make relative negative vert indices absolute
+ if vert_loc_index < 0:
+ vert_loc_index = len(verts_loc) + vert_loc_index + 1
+
+ face_vert_loc_indices.append(vert_loc_index)
+
+ if len(obj_vert) > 1 and obj_vert[1]:
+ # formatting for faces with normals and textures us
+ # loc_index/tex_index/nor_index
+
+ vert_tex_index = int(obj_vert[1]) - 1
+ # Make relative negative vert indices absolute
+ if vert_tex_index < 0:
+ vert_tex_index = len(verts_tex) + vert_tex_index + 1
+
+ face_vert_tex_indices.append(vert_tex_index)
+ else:
+ # dummy
+ face_vert_tex_indices.append(0)
+
+ if len(face_vert_loc_indices) > 4:
+ has_ngons = True
+
+ elif use_edges and (line.startswith(b'l ') or context_multi_line == b'l'):
+ # very similar to the face load function above with some parts removed
+
+ if context_multi_line:
+ # use face_vert_loc_indices and face_vert_tex_indices previously defined and used the obj_face
+ line_split = line.split()
+
+ else:
+ line_split = line[2:].split()
+ face_vert_loc_indices = []
+ face_vert_tex_indices = []
+
+ # Instance a face
+ faces.append((\
+ face_vert_loc_indices,\
+ face_vert_tex_indices,\
+ context_material,\
+ context_smooth_group,\
+ context_object\
+ ))
+
+ if strip_slash(line_split):
+ context_multi_line = b'l'
+ else:
+ context_multi_line = b''
+
+ isline = line.startswith(b'l')
+
+ for v in line_split:
+ vert_loc_index = int(v) - 1
+
+ # Make relative negative vert indices absolute
+ if vert_loc_index < 0:
+ vert_loc_index = len(verts_loc) + vert_loc_index + 1
+
+ face_vert_loc_indices.append(vert_loc_index)
+
+ elif line.startswith(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.startswith(b'o'):
+ if use_split_objects:
+ context_object = line_value(line.split())
+ # unique_obects[context_object]= None
+
+ elif line.startswith(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.startswith(b'usemtl'):
+ context_material = line_value(line.split())
+ unique_materials[context_material] = None
+ elif line.startswith(b'mtllib'): # usemap or usemat
+ 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
+
+ # Nurbs support
+ elif line.startswith(b'cstype '):
+ context_nurbs[b'cstype'] = line_value(line.split()) # 'rat bspline' / 'bspline'
+ elif line.startswith(b'curv ') or context_multi_line == b'curv':
+ line_split = line.split()
+
+ curv_idx = context_nurbs[b'curv_idx'] = context_nurbs.get(b'curv_idx', []) # incase 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.startswith(b'parm') or context_multi_line == b'parm':
+ line_split = line.split()
+
+ 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.startswith(b'deg '):
+ context_nurbs[b'deg'] = [int(i) for i in line.split()[1:]]
+ elif line.startswith(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.startswith(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, material_libs, unique_materials, unique_material_images, use_image_search)
+
+ 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
+# scn.objects.selected = []
+ 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
+ if use_split_objects or use_split_groups:
+ SPLIT_OB_OR_GROUP = True
+ else:
+ SPLIT_OB_OR_GROUP = False
+
+ for verts_loc_split, faces_split, unique_materials_split, dataname in split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
+ # Create meshes from the data, warning 'vertex_groups' wont support splitting
+ create_mesh(new_objects, has_ngons, use_ngons, use_edges, verts_loc_split, 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'}
+
+
+# NOTES (all line numbers refer to 2.4x import_obj.py, not this file)
+# check later: line 489
+# can convert now: edge flags, edges: lines 508-528
+# ngon (uses python module BPyMesh): 384-414
+# NEXT clamp size: get bound box with RNA
+# get back to l 140 (here)
+# search image in bpy.config.textureDir - load_image
+# replaced BPyImage.comprehensiveImageLoad with a simplified version that only checks additional directory specified, but doesn't search dirs recursively (obj_image_load)
+# bitmask won't work? - 132
+# uses bpy.sys.time()
+
+if __name__ == "__main__":
+ register()