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__.py16
-rw-r--r--io_scene_obj/export_obj.py64
-rw-r--r--io_scene_obj/import_obj.py207
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) ..." %