Initial porting of OBJ (wavefront) IO add-on to 2.8.

As with FBX, major changes are no support for exporting materials
anymore (since all are nodals), and no more texface at all.
This commit is contained in:
Bastien Montagne 2018-09-21 20:02:41 +02:00
parent 41987f476c
commit 4101cb5d5a
3 changed files with 49 additions and 138 deletions

View File

@ -21,8 +21,8 @@
bl_info = {
"name": "Wavefront OBJ format",
"author": "Campbell Barton, Bastien Montagne",
"version": (2, 3, 6),
"blender": (2, 78, 0),
"version": (3, 3, 6),
"blender": (2, 80, 0),
"location": "File > Import-Export",
"description": "Import-Export OBJ, Import OBJ mesh, UV's, materials and textures",
"warning": "",
@ -48,70 +48,68 @@ from bpy.props import (
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
orientation_helper_factory,
orientation_helper,
path_reference_mode,
axis_conversion,
)
IOOBJOrientationHelper = orientation_helper_factory("IOOBJOrientationHelper", axis_forward='-Z', axis_up='Y')
class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper):
@orientation_helper(axis_forward='-Z', axis_up='Y')
class ImportOBJ(bpy.types.Operator, ImportHelper):
"""Load a Wavefront OBJ File"""
bl_idname = "import_scene.obj"
bl_label = "Import OBJ"
bl_options = {'PRESET', 'UNDO'}
filename_ext = ".obj"
filter_glob = StringProperty(
filter_glob: StringProperty(
default="*.obj;*.mtl",
options={'HIDDEN'},
)
use_edges = BoolProperty(
use_edges: BoolProperty(
name="Lines",
description="Import lines and faces with 2 verts as edge",
default=True,
)
use_smooth_groups = BoolProperty(
use_smooth_groups: BoolProperty(
name="Smooth Groups",
description="Surround smooth groups by sharp edges",
default=True,
)
use_split_objects = BoolProperty(
use_split_objects: BoolProperty(
name="Object",
description="Import OBJ Objects into Blender Objects",
default=True,
)
use_split_groups = BoolProperty(
use_split_groups: BoolProperty(
name="Group",
description="Import OBJ Groups into Blender Objects",
default=True,
)
use_groups_as_vgroups = BoolProperty(
use_groups_as_vgroups: BoolProperty(
name="Poly Groups",
description="Import OBJ groups as vertex groups",
default=False,
)
use_image_search = BoolProperty(
use_image_search: BoolProperty(
name="Image Search",
description="Search subdirs for any associated images "
"(Warning, may be slow)",
default=True,
)
split_mode = EnumProperty(
split_mode: EnumProperty(
name="Split",
items=(('ON', "Split", "Split geometry, omits unused verts"),
('OFF', "Keep Vert Order", "Keep vertex order from file"),
),
)
global_clight_size = FloatProperty(
global_clight_size: FloatProperty(
name="Clamp Size",
description="Clamp bounds under this value (zero to disable)",
min=0.0, max=1000.0,
@ -139,7 +137,7 @@ class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper):
from_up=self.axis_up,
).to_4x4()
keywords["global_matrix"] = global_matrix
keywords["use_cycles"] = (context.scene.view_render.engine == 'CYCLES')
keywords["use_cycles"] = True # (context.scene.view_render.engine == 'CYCLES')
if bpy.data.is_saved and context.user_preferences.filepaths.use_relative_paths:
import os
@ -166,7 +164,7 @@ class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper):
else:
row.prop(self, "use_groups_as_vgroups")
row = layout.split(percentage=0.67)
row = layout.split(factor=0.67)
row.prop(self, "global_clight_size")
layout.prop(self, "axis_forward")
layout.prop(self, "axis_up")
@ -174,7 +172,8 @@ class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper):
layout.prop(self, "use_image_search")
class ExportOBJ(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper):
@orientation_helper(axis_forward='-Z', axis_up='Y')
class ExportOBJ(bpy.types.Operator, ExportHelper):
"""Save a Wavefront OBJ File"""
bl_idname = "export_scene.obj"
@ -182,113 +181,113 @@ class ExportOBJ(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper):
bl_options = {'PRESET'}
filename_ext = ".obj"
filter_glob = StringProperty(
filter_glob: StringProperty(
default="*.obj;*.mtl",
options={'HIDDEN'},
)
# context group
use_selection = BoolProperty(
use_selection: BoolProperty(
name="Selection Only",
description="Export selected objects only",
default=False,
)
use_animation = BoolProperty(
use_animation: BoolProperty(
name="Animation",
description="Write out an OBJ for each frame",
default=False,
)
# object group
use_mesh_modifiers = BoolProperty(
use_mesh_modifiers: BoolProperty(
name="Apply Modifiers",
description="Apply modifiers",
default=True,
)
use_mesh_modifiers_render = BoolProperty(
use_mesh_modifiers_render: BoolProperty(
name="Use Modifiers Render Settings",
description="Use render settings when applying modifiers to mesh objects",
default=False,
)
# extra data group
use_edges = BoolProperty(
use_edges: BoolProperty(
name="Include Edges",
description="",
default=True,
)
use_smooth_groups = BoolProperty(
use_smooth_groups: BoolProperty(
name="Smooth Groups",
description="Write sharp edges as smooth groups",
default=False,
)
use_smooth_groups_bitflags = BoolProperty(
use_smooth_groups_bitflags: BoolProperty(
name="Bitflag Smooth Groups",
description="Same as 'Smooth Groups', but generate smooth groups IDs as bitflags "
"(produces at most 32 different smooth groups, usually much less)",
default=False,
)
use_normals = BoolProperty(
use_normals: BoolProperty(
name="Write Normals",
description="Export one normal per vertex and per face, to represent flat faces and sharp edges",
default=True,
)
use_uvs = BoolProperty(
use_uvs: BoolProperty(
name="Include UVs",
description="Write out the active UV coordinates",
default=True,
)
use_materials = BoolProperty(
use_materials: BoolProperty(
name="Write Materials",
description="Write out the MTL file",
default=True,
)
use_triangles = BoolProperty(
use_triangles: BoolProperty(
name="Triangulate Faces",
description="Convert all faces to triangles",
default=False,
)
use_nurbs = BoolProperty(
use_nurbs: BoolProperty(
name="Write Nurbs",
description="Write nurbs curves as OBJ nurbs rather than "
"converting to geometry",
default=False,
)
use_vertex_groups = BoolProperty(
use_vertex_groups: BoolProperty(
name="Polygroups",
description="",
default=False,
)
# grouping group
use_blen_objects = BoolProperty(
use_blen_objects: BoolProperty(
name="Objects as OBJ Objects",
description="",
default=True,
)
group_by_object = BoolProperty(
group_by_object: BoolProperty(
name="Objects as OBJ Groups ",
description="",
default=False,
)
group_by_material = BoolProperty(
group_by_material: BoolProperty(
name="Material Groups",
description="",
default=False,
)
keep_vertex_order = BoolProperty(
keep_vertex_order: BoolProperty(
name="Keep Vertex Order",
description="",
default=False,
)
global_scale = FloatProperty(
global_scale: FloatProperty(
name="Scale",
min=0.01, max=1000.0,
default=1.0,
)
path_mode = path_reference_mode
path_mode: path_reference_mode
check_extension = True
@ -303,7 +302,7 @@ class ExportOBJ(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper):
"filter_glob",
))
global_matrix = (Matrix.Scale(self.global_scale, 4) *
global_matrix = (Matrix.Scale(self.global_scale, 4) @
axis_conversion(to_forward=self.axis_forward,
to_up=self.axis_up,
).to_4x4())

View File

@ -69,7 +69,7 @@ def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict):
fw('\nnewmtl %s\n' % mtl_mat_name) # Define a new material: matname_imgname
if mat:
if False and mat: # XXX TODO Support nodal materials.
use_mirror = mat.raytrace_mirror.use and mat.raytrace_mirror.reflect_factor != 0.0
# convert from blenders spec to 0 - 1000 range.
@ -139,7 +139,7 @@ def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict):
# so we write the materials image.
face_img = None
if mat: # No face image. if we havea material search for MTex image.
if False and mat: # XXX TODO support nodal materials. If we have a material search for MTex image.
image_map = {}
# backwards so topmost are highest priority
for mtex in reversed(mat.texture_slots):
@ -230,7 +230,7 @@ def write_nurb(fw, ob, ob_mat):
do_endpoints = (do_closed == 0) and nu.use_endpoint_u
for pt in nu.points:
fw('v %.6f %.6f %.6f\n' % (ob_mat * pt.co.to_3d())[:])
fw('v %.6f %.6f %.6f\n' % (ob_mat @ pt.co.to_3d())[:])
pt_num += 1
tot_verts += pt_num
@ -379,7 +379,7 @@ def write_file(filepath, objects, depsgraph, scene,
# Nurbs curve support
if EXPORT_CURVE_AS_NURBS and test_nurbs_compat(ob):
ob_mat = EXPORT_GLOBAL_MATRIX * ob_mat
ob_mat = EXPORT_GLOBAL_MATRIX @ ob_mat
totverts += write_nurb(fw, ob, ob_mat)
continue
# END NURBS
@ -397,7 +397,7 @@ def write_file(filepath, objects, depsgraph, scene,
# _must_ do this first since it re-allocs arrays
mesh_triangulate(me)
me.transform(EXPORT_GLOBAL_MATRIX * ob_mat)
me.transform(EXPORT_GLOBAL_MATRIX @ ob_mat)
# If negative scaling, we have to invert the normals...
if ob_mat.determinant() < 0.0:
me.flip_normals()
@ -740,7 +740,7 @@ def _write(context, filepath,
if EXPORT_ANIMATION: # Add frame to the filepath.
context_name[2] = '_%.6d' % frame
scene.frame_set(frame, 0.0)
scene.frame_set(frame, subframe=0.0)
if EXPORT_SEL_ONLY:
objects = context.selected_objects
else:
@ -773,7 +773,7 @@ def _write(context, filepath,
)
progress.leave_substeps()
scene.frame_set(orig_frame, 0.0)
scene.frame_set(orig_frame, subframe=0.0)
progress.leave_substeps()

View File

@ -87,7 +87,7 @@ def obj_image_load(context_imagepath_map, line, DIR, recursive, relpath):
def create_materials(filepath, relpath,
material_libs, unique_materials, unique_material_images,
material_libs, unique_materials,
use_image_search, use_cycles, float_func):
"""
Create all the used materials in this obj,
@ -133,48 +133,21 @@ def create_materials(filepath, relpath,
mat_wrap.diffuse_image_set(image)
mat_wrap.diffuse_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
mtex = blender_material.texture_slots.add()
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_color_diffuse = True
# adds textures to faces (Textured/Alt-Z mode)
# Only apply the diffuse texture to the face if the image has not been set with the inline usemat func.
unique_material_images[context_material_name] = image # set the texface image
elif type == 'Ka':
if use_cycles:
# XXX Not supported?
print("WARNING, currently unsupported ambient texture, skipped.")
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':
if use_cycles:
mat_wrap.specular_image_set(image)
mat_wrap.specular_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_color_spec = True
elif type == 'Ke':
if use_cycles:
# XXX Not supported?
print("WARNING, currently unsupported emit texture, skipped.")
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':
bump_mult = map_options.get(b'-bm')
bump_mult = float(bump_mult[0]) if (bump_mult is not None and len(bump_mult) > 1) else 1.0
@ -185,41 +158,16 @@ def create_materials(filepath, relpath,
if bump_mult:
mat_wrap.normal_factor_set(bump_mult)
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_normal = True
if bump_mult:
mtex.normal_factor = bump_mult
elif type == 'D':
if use_cycles:
mat_wrap.alpha_image_set(image)
mat_wrap.alpha_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_alpha = True
blender_material.use_transparency = True
blender_material.transparency_method = 'Z_TRANSPARENCY'
if "alpha" not in context_material_vars:
blender_material.alpha = 0.0
# Todo, unset diffuse material alpha if it has an alpha channel
elif type == 'disp':
if use_cycles:
mat_wrap.bump_image_set(image)
mat_wrap.bump_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'UV'
mtex.use_map_displacement = True
elif type == 'refl':
map_type = map_options.get(b'-type')
if map_type and map_type != [b'sphere']:
@ -230,12 +178,6 @@ def create_materials(filepath, relpath,
mat_wrap.diffuse_image_set(image, projection='SPHERE')
mat_wrap.diffuse_mapping_set(coords='Reflection', translation=map_offset, scale=map_scale)
mtex = blender_material.texture_slots.add()
mtex.use_map_color_diffuse = False
mtex.texture = texture
mtex.texture_coords = 'REFLECTION'
mtex.use_map_color_diffuse = True
mtex.mapping = 'SPHERE'
else:
raise Exception("invalid type %r" % type)
@ -263,18 +205,11 @@ def create_materials(filepath, relpath,
for name in unique_materials: # .keys()
if name is not None:
ma = unique_materials[name] = bpy.data.materials.new(name.decode('utf-8', "replace"))
unique_material_images[name] = None # assign None to all material images to start with, add to later.
if use_cycles:
from modules import cycles_shader_compat
ma_wrap = cycles_shader_compat.CyclesShaderWrapper(ma)
cycles_material_wrap_map[ma] = ma_wrap
# XXX Why was this needed? Cannot find any good reason, and adds stupid empty matslot in case we do not separate
# mesh (see T44947).
#~ unique_materials[None] = None
#~ unique_material_images[None] = None
for libname in sorted(material_libs):
# print(libname)
mtlpath = os.path.join(DIR, libname)
@ -382,22 +317,15 @@ def create_materials(filepath, relpath,
col = (float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3]))
if use_cycles:
context_mat_wrap.reflect_color_set(col)
context_material.mirror_color = col
# This is highly approximated, but let's try to stick as close from exporter as possible... :/
context_material.ambient = sum(context_material.mirror_color) / 3
elif line_id == b'kd':
col = (float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3]))
if use_cycles:
context_mat_wrap.diffuse_color_set(col)
context_material.diffuse_color = col
context_material.diffuse_intensity = 1.0
elif line_id == b'ks':
col = (float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3]))
if use_cycles:
context_mat_wrap.specular_color_set(col)
context_mat_wrap.hardness_value_set(1.0)
context_material.specular_color = col
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[:] = [
@ -405,23 +333,15 @@ def create_materials(filepath, relpath,
elif line_id == b'ns':
if use_cycles:
context_mat_wrap.hardness_value_set(((float_func(line_split[1]) + 3.0) / 50.0) - 0.65)
context_material.specular_hardness = int((float_func(line_split[1]) * 0.51) + 1)
elif line_id == b'ni': # Refraction index (between 1 and 3).
if use_cycles:
print("WARNING, currently unsupported glass material, skipped.")
context_material.raytrace_transparency.ior = max(1, min(float_func(line_split[1]), 3))
context_material_vars.add("ior")
elif line_id == b'd': # dissolve (transparency)
if use_cycles:
context_mat_wrap.alpha_value_set(float_func(line_split[1]))
context_material.alpha = float_func(line_split[1])
context_material.use_transparency = True
context_material.transparency_method = 'Z_TRANSPARENCY'
context_material_vars.add("alpha")
elif line_id == b'tr': # translucency
if use_cycles:
print("WARNING, currently unsupported translucency option, skipped.")
context_material.translucency = float_func(line_split[1])
elif line_id == b'tf':
# rgb, filter color, blender has no support for this.
pass
@ -601,7 +521,6 @@ def create_mesh(new_objects,
verts_tex,
faces,
unique_materials,
unique_material_images,
unique_smooth_groups,
vertex_groups,
dataname,
@ -756,7 +675,7 @@ def create_mesh(new_objects,
me.create_normals_split()
if verts_tex and me.polygons:
me.uv_textures.new()
me.uv_layers.new()
context_material_old = -1 # avoid a dict lookup
mat = 0 # rare case it may be un-initialized.
@ -788,11 +707,6 @@ def create_mesh(new_objects,
me.loops[lidx].normal[:] = verts_nor[0 if (face_noidx is ...) else face_noidx]
if verts_tex and face_vert_tex_indices:
if context_material:
image = unique_material_images[context_material]
if image: # Can be none if the material dosnt have an image.
me.uv_textures[0].data[i].image = image
blen_uvs = me.uv_layers[0]
for face_uvidx, lidx in zip(face_vert_tex_indices, blen_poly.loop_indices):
blen_uvs.data[lidx].uv = verts_tex[0 if (face_uvidx is ...) else face_uvidx]
@ -1036,7 +950,6 @@ def load(context,
# 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.
@ -1258,7 +1171,7 @@ def load(context,
progress.step("Done, loading materials and images...")
create_materials(filepath, relpath, material_libs, unique_materials,
unique_material_images, use_image_search, use_cycles, float_func)
use_image_search, use_cycles, float_func)
progress.step("Done, building geometries (verts:%i faces:%i materials: %i smoothgroups:%i) ..." %
(len(verts_loc), len(faces), len(unique_materials), len(unique_smooth_groups)))
@ -1284,7 +1197,6 @@ def load(context,
verts_tex if use_vtex else [],
faces_split,
unique_materials_split,
unique_material_images,
unique_smooth_groups,
vertex_groups,
dataname,