diff options
Diffstat (limited to 'io_scene_obj')
-rw-r--r-- | io_scene_obj/__init__.py | 16 | ||||
-rw-r--r-- | io_scene_obj/export_obj.py | 64 | ||||
-rw-r--r-- | io_scene_obj/import_obj.py | 207 |
3 files changed, 183 insertions, 104 deletions
diff --git a/io_scene_obj/__init__.py b/io_scene_obj/__init__.py index f8c179ee..aff0b345 100644 --- a/io_scene_obj/__init__.py +++ b/io_scene_obj/__init__.py @@ -21,14 +21,12 @@ bl_info = { "name": "Wavefront OBJ format", "author": "Campbell Barton, Bastien Montagne", - "version": (2, 2, 1), - "blender": (2, 74, 0), + "version": (2, 3, 0), + "blender": (2, 76, 0), "location": "File > Import-Export", - "description": "Import-Export OBJ, Import OBJ mesh, UV's, " - "materials and textures", + "description": "Import-Export OBJ, Import OBJ mesh, UV's, materials and textures", "warning": "", - "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Import-Export/Wavefront_OBJ", + "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/Wavefront_OBJ", "support": 'OFFICIAL', "category": "Import-Export"} @@ -144,9 +142,9 @@ class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper): if bpy.data.is_saved and context.user_preferences.filepaths.use_relative_paths: import os - keywords["relpath"] = os.path.dirname((bpy.data.path_resolve("filepath", False).as_bytes())) + keywords["relpath"] = os.path.dirname(bpy.data.filepath) - return import_obj.load(self, context, **keywords) + return import_obj.load(context, **keywords) def draw(self, context): layout = self.layout @@ -305,7 +303,7 @@ class ExportOBJ(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper): ).to_4x4()) keywords["global_matrix"] = global_matrix - return export_obj.save(self, context, **keywords) + return export_obj.save(context, **keywords) def menu_func_import(self, context): diff --git a/io_scene_obj/export_obj.py b/io_scene_obj/export_obj.py index 7399c2e1..26ca8a04 100644 --- a/io_scene_obj/export_obj.py +++ b/io_scene_obj/export_obj.py @@ -44,7 +44,7 @@ def mesh_triangulate(me): def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict): - from mathutils import Color + from mathutils import Color, Vector world = scene.world if world: @@ -90,6 +90,9 @@ def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict): fw('Ka %.6f %.6f %.6f\n' % (mat.ambient, mat.ambient, mat.ambient)) # Do not use world color! fw('Kd %.6f %.6f %.6f\n' % (mat.diffuse_intensity * mat.diffuse_color)[:]) # Diffuse fw('Ks %.6f %.6f %.6f\n' % (mat.specular_intensity * mat.specular_color)[:]) # Specular + # Emission, not in original MTL standard but seems pretty common, see T45766. + # XXX Blender has no color emission, it's using diffuse color instead... + fw('Ke %.6f %.6f %.6f\n' % (mat.emit * mat.diffuse_color)[:]) if hasattr(mat, "raytrace_transparency") and hasattr(mat.raytrace_transparency, "ior"): fw('Ni %.6f\n' % mat.raytrace_transparency.ior) # Refraction index else: @@ -149,35 +152,43 @@ def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict): # texface overrides others if (mtex.use_map_color_diffuse and (face_img is None) and (mtex.use_map_warp is False) and (mtex.texture_coords != 'REFLECTION')): - image_map["map_Kd"] = image + image_map["map_Kd"] = (mtex, image) if mtex.use_map_ambient: - image_map["map_Ka"] = image + image_map["map_Ka"] = (mtex, image) # this is the Spec intensity channel but Ks stands for specular Color ''' if mtex.use_map_specular: - image_map["map_Ks"] = image + image_map["map_Ks"] = (mtex, image) ''' if mtex.use_map_color_spec: # specular color - image_map["map_Ks"] = image + image_map["map_Ks"] = (mtex, image) if mtex.use_map_hardness: # specular hardness/glossiness - image_map["map_Ns"] = image + image_map["map_Ns"] = (mtex, image) if mtex.use_map_alpha: - image_map["map_d"] = image + image_map["map_d"] = (mtex, image) if mtex.use_map_translucency: - image_map["map_Tr"] = image + image_map["map_Tr"] = (mtex, image) if mtex.use_map_normal: - image_map["map_Bump"] = image + image_map["map_Bump"] = (mtex, image) if mtex.use_map_displacement: - image_map["disp"] = image + image_map["disp"] = (mtex, image) if mtex.use_map_color_diffuse and (mtex.texture_coords == 'REFLECTION'): - image_map["refl"] = image + image_map["refl"] = (mtex, image) if mtex.use_map_emit: - image_map["map_Ke"] = image + image_map["map_Ke"] = (mtex, image) - for key, image in sorted(image_map.items()): + for key, (mtex, image) in sorted(image_map.items()): filepath = bpy_extras.io_utils.path_reference(image.filepath, source_dir, dest_dir, path_mode, "", copy_set, image.library) - fw('%s %s\n' % (key, repr(filepath)[1:-1])) + options = [] + if key == "map_Bump": + if mtex.normal_factor != 1.0: + options.append('-bm %.6f' % mtex.normal_factor) + if mtex.offset != Vector((0.0, 0.0, 0.0)): + options.append('-o %.6f %.6f %.6f' % mtex.offset[:]) + if mtex.scale != Vector((1.0, 1.0, 1.0)): + options.append('-s %.6f %.6f %.6f' % mtex.scale[:]) + fw('%s %s %s\n' % (key, " ".join(options), repr(filepath)[1:-1])) def test_nurbs_compat(ob): @@ -349,18 +360,16 @@ def write_file(filepath, objects, scene, subprogress1.step("Ignoring %s, dupli child..." % ob_main.name) continue - obs = [] + obs = [(ob_main, ob_main.matrix_world)] 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] + 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)] + print(ob_main.name, 'has', len(obs) - 1, 'dupli children') subprogress1.enter_substeps(len(obs)) for ob, ob_mat in obs: @@ -415,9 +424,8 @@ def write_file(filepath, objects, scene, if EXPORT_NORMALS and face_index_pairs: me.calc_normals_split() # No need to call me.free_normals_split later, as this mesh is deleted anyway! - loops = me.loops - else: - loops = [] + + loops = me.loops if (EXPORT_SMOOTH_GROUPS or EXPORT_SMOOTH_GROUPS_BITFLAGS) and face_index_pairs: smooth_groups, smooth_groups_tot = me.calc_smooth_groups(EXPORT_SMOOTH_GROUPS_BITFLAGS) @@ -504,7 +512,13 @@ def write_file(filepath, objects, scene, uv_ls = uv_face_mapping[f_index] = [] for uv_index, l_index in enumerate(f.loop_indices): uv = uv_layer[l_index].uv - uv_key = veckey2d(uv) + # include the vertex index in the key so we don't share UV's between vertices, + # allowed by the OBJ spec but can cause issues for other importers, see: T47010. + + # this works too, shared UV's for all verts + #~ uv_key = veckey2d(uv) + uv_key = loops[l_index].vertex_index, veckey2d(uv) + uv_val = uv_get(uv_key) if uv_val is None: uv_val = uv_dict[uv_key] = uv_unique_count @@ -782,7 +796,9 @@ Currently the exporter lacks these features: """ -def save(operator, context, filepath="", +def save(context, + filepath, + *, use_triangles=False, use_edges=True, use_normals=False, diff --git a/io_scene_obj/import_obj.py b/io_scene_obj/import_obj.py index a9888602..7b065824 100644 --- a/io_scene_obj/import_obj.py +++ b/io_scene_obj/import_obj.py @@ -63,8 +63,8 @@ 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 "_" in imagepath: + image = load_image(imagepath.replace("_", " "), DIR, recursive=recursive, relpath=relpath) if image: return image @@ -81,10 +81,21 @@ def create_materials(filepath, relpath, DIR = os.path.dirname(filepath) context_material_vars = set() - def load_material_image(blender_material, context_material_name, imagepath, type): + def load_material_image(blender_material, context_material_name, img_data, type): """ Set textures defined in .mtl file. """ + imagepath = os.fsdecode(img_data[-1]) + map_options = {} + + curr_token = [] + for token in img_data[:-1]: + if token.startswith(b'-'): + if curr_token: + map_options[curr_token[0]] = curr_token[1:] + curr_token[:] = [] + curr_token.append(token) + texture = bpy.data.textures.new(name=type, type='IMAGE') # Absolute path - c:\.. etc would work here @@ -120,6 +131,14 @@ def create_materials(filepath, relpath, mtex.texture_coords = 'UV' mtex.use_map_color_spec = True + elif type == 'Ke': + mtex = blender_material.texture_slots.add() + mtex.use_map_color_diffuse = False + + mtex.texture = texture + mtex.texture_coords = 'UV' + mtex.use_map_emit = True + elif type == 'Bump': mtex = blender_material.texture_slots.add() mtex.use_map_color_diffuse = False @@ -128,6 +147,10 @@ def create_materials(filepath, relpath, mtex.texture_coords = 'UV' mtex.use_map_normal = True + bump_mult = map_options.get(b'-bm') + if bump_mult: + mtex.normal_factor = bump_mult[0] + elif type == 'D': mtex = blender_material.texture_slots.add() mtex.use_map_color_diffuse = False @@ -156,14 +179,35 @@ def create_materials(filepath, relpath, mtex.texture = texture mtex.texture_coords = 'REFLECTION' mtex.use_map_color_diffuse = True + + map_type = map_options.get(b'-type') + if map_type and map_type != [b'sphere']: + print("WARNING, unsupported reflection type '%s', defaulting to 'sphere'" + "" % ' '.join(i.decode() for i in map_type)) + mtex.mapping = 'SPHERE' else: raise Exception("invalid type %r" % type) + map_offset = map_options.get(b'-o') + map_scale = map_options.get(b'-s') + if map_offset: + mtex.offset.x = float(map_offset[0]) + if len(map_offset) >= 2: + mtex.offset.y = float(map_offset[1]) + if len(map_offset) >= 3: + mtex.offset.z = float(map_offset[2]) + if map_scale: + mtex.scale.x = float(map_scale[0]) + if len(map_scale) >= 2: + mtex.scale.y = float(map_scale[1]) + if len(map_scale) >= 3: + mtex.scale.z = float(map_scale[2]) + # 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' + temp_mtl = os.path.splitext((os.path.basename(filepath)))[0] + ".mtl" - if os.path.exists(os.path.join(DIR, temp_mtl)) and temp_mtl not in material_libs: - material_libs.append(temp_mtl) + if os.path.exists(os.path.join(DIR, temp_mtl)): + material_libs.add(temp_mtl) del temp_mtl # Create new materials @@ -177,7 +221,7 @@ def create_materials(filepath, relpath, #~ unique_materials[None] = None #~ unique_material_images[None] = None - for libname in material_libs: + for libname in sorted(material_libs): # print(libname) mtlpath = os.path.join(DIR, libname) if not os.path.exists(mtlpath): @@ -190,6 +234,7 @@ def create_materials(filepath, relpath, do_glass = False do_fresnel = False do_raytrace = False + emit_colors = [0.0, 0.0, 0.0] # print('\t\tloading mtl: %e' % mtlpath) context_material = None @@ -203,8 +248,14 @@ def create_materials(filepath, relpath, line_id = line_split[0].lower() if line_id == b'newmtl': - # Finalize preview mat, if any. + # Finalize previous mat, if any. if context_material: + emit_value = sum(emit_colors) / 3.0 + if emit_value > 1e-6: + # We have to adapt it to diffuse color too... + emit_value /= sum(context_material.diffuse_color) / 3.0 + context_material.emit = emit_value + if not do_ambient: context_material.ambient = 0.0 @@ -243,6 +294,7 @@ def create_materials(filepath, relpath, context_material = unique_materials.get(context_material_name) context_material_vars.clear() + emit_colors[:] = [0.0, 0.0, 0.0] do_ambient = True do_highlight = False do_reflection = False @@ -267,6 +319,10 @@ def create_materials(filepath, relpath, context_material.specular_color = ( float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])) context_material.specular_intensity = 1.0 + elif line_id == b'ke': + # We cannot set context_material.emit right now, we need final diffuse color as well for this. + emit_colors[:] = [ + 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) + 1) elif line_id == b'ni': # Refraction index (between 1 and 3). @@ -340,35 +396,39 @@ def create_materials(filepath, relpath, pass 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') + img_data = line.split()[1:] + if img_data: + load_material_image(context_material, context_material_name, img_data, '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') + img_data = line.split()[1:] + if img_data: + load_material_image(context_material, context_material_name, img_data, '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') + img_data = line.split()[1:] + if img_data: + load_material_image(context_material, context_material_name, img_data, 'Kd') + elif line_id == b'map_ke': + img_data = line.split()[1:] + if img_data: + load_material_image(context_material, context_material_name, img_data, 'Ke') 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') + img_data = line.split()[1:] + if img_data: + load_material_image(context_material, context_material_name, img_data, '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') + img_data = line.split()[1:] + if img_data: + load_material_image(context_material, context_material_name, img_data, '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') + img_data = line.split()[1:] + if img_data: + load_material_image(context_material, context_material_name, img_data, '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') + img_data = line.split()[1:] + if img_data: + load_material_image(context_material, context_material_name, img_data, 'refl') else: print("\t%r:%r (ignored)" % (filepath, line)) mtl.close() @@ -506,43 +566,46 @@ def create_mesh(new_objects, # 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]], - ] if face_vert_nor_indices else [], - [face_vert_tex_indices[ngon[0]], - face_vert_tex_indices[ngon[1]], - face_vert_tex_indices[ngon[2]], - ] if face_vert_tex_indices else [], - 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] - if vidx == prev_vidx: - continue # broken OBJ... Just skip. - 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) + # ignore triangles with invalid indices + if len(face_vert_loc_indices) > 3: + 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]], + ] if face_vert_nor_indices else [], + [face_vert_tex_indices[ngon[0]], + face_vert_tex_indices[ngon[1]], + face_vert_tex_indices[ngon[2]], + ] if face_vert_tex_indices else [], + context_material, + context_smooth_group, + context_object, + [], + ) + for ngon in ngon_face_indices] + ) + tot_loops += 3 * len(ngon_face_indices) + + # edges to make ngons + if len(ngon_face_indices) > 1: + 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] + if vidx == prev_vidx: + continue # broken OBJ... Just skip. + 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: @@ -799,7 +862,9 @@ def get_float_func(filepath): return float -def load(operator, context, filepath, +def load(context, + filepath, + *, global_clamp_size=0.0, use_smooth_groups=True, use_edges=True, @@ -808,7 +873,7 @@ def load(operator, context, filepath, use_image_search=True, use_groups_as_vgroups=False, relpath=None, - global_matrix=None, + global_matrix=None ): """ Called by the user interface or another script. @@ -856,7 +921,7 @@ def load(operator, context, filepath, verts_nor = [] verts_tex = [] faces = [] # tuples of the faces - material_libs = [] # filanems to material libs this uses + material_libs = set() # filenames to material libs this OBJ 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. @@ -1036,7 +1101,7 @@ def load(operator, context, filepath, 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:])) + material_libs |= {os.fsdecode(f) for f in line.split()[1:]} # Nurbs support elif line_start == b'cstype': @@ -1096,7 +1161,7 @@ def load(operator, context, filepath, progress.step("Done, loading materials and images...") - create_materials(filepath.encode(), relpath, material_libs, unique_materials, + create_materials(filepath, relpath, material_libs, unique_materials, unique_material_images, use_image_search, float_func) progress.step("Done, building geometries (verts:%i faces:%i materials: %i smoothgroups:%i) ..." % |