From 3ed399f0c70ee0af6479cba5fdc6de29f34346ee Mon Sep 17 00:00:00 2001 From: Baptiste Nicolet Date: Mon, 27 May 2024 13:25:07 +0200 Subject: [PATCH 1/6] Remove 'SHARP' distribution metnions in I/O to comply with blender 4.0 API changes --- mitsuba-blender/io/exporter/materials.py | 2 +- mitsuba-blender/io/importer/materials.py | 34 +++++++++++++++--------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/mitsuba-blender/io/exporter/materials.py b/mitsuba-blender/io/exporter/materials.py index 7ca698e..e4513eb 100644 --- a/mitsuba-blender/io/exporter/materials.py +++ b/mitsuba-blender/io/exporter/materials.py @@ -105,7 +105,7 @@ def convert_glossy_materials_cycles(export_ctx, current_node): roughness = convert_float_texture_node(export_ctx, current_node.inputs['Roughness']) - if roughness and current_node.distribution != 'SHARP': + if roughness > 0: params.update({ 'type': 'roughconductor', 'alpha': roughness, diff --git a/mitsuba-blender/io/importer/materials.py b/mitsuba-blender/io/importer/materials.py index eedf480..d252438 100644 --- a/mitsuba-blender/io/importer/materials.py +++ b/mitsuba-blender/io/importer/materials.py @@ -10,7 +10,7 @@ importlib.reload(mi_props_utils) if "textures" in locals(): importlib.reload(textures) - + import bpy from . import bl_shader_utils @@ -325,7 +325,7 @@ def write_mi_bump_and_normal_maps(mi_context, bl_mat_wrap, out_socket_id, mi_bum def write_mi_emitter_bsdf(mi_context, bl_mat_wrap, out_socket_id, mi_emitter): bl_add = bl_mat_wrap.ensure_node_type([out_socket_id], 'ShaderNodeAddShader', 'Shader') bl_add_wrap = bl_shader_utils.NodeMaterialWrapper(bl_mat_wrap.bl_mat, out_node=bl_add) - + bl_emissive = bl_add_wrap.ensure_node_type(['Shader'], 'ShaderNodeEmission', 'Emission') radiance, strength = mi_spectra_utils.convert_mi_srgb_emitter_spectrum(mi_emitter.get('radiance'), [1.0, 1.0, 1.0]) bl_emissive.inputs['Color'].default_value = bl_shader_utils.rgb_to_rgba(radiance) @@ -407,8 +407,10 @@ def write_mi_twosided_bsdf(mi_context, mi_mat, bl_mat_wrap, out_socket_id, mi_bu def write_mi_dielectric_bsdf(mi_context, mi_mat, bl_mat_wrap, out_socket_id, mi_bump=None, mi_normal=None): bl_glass = bl_mat_wrap.ensure_node_type([out_socket_id], 'ShaderNodeBsdfGlass', 'BSDF') bl_glass_wrap = bl_shader_utils.NodeMaterialWrapper(bl_mat_wrap.bl_mat, out_node=bl_glass) - # FIXME: Is this the correct distribution ? - bl_glass.distribution = 'SHARP' + if bpy.app.version < (4, 0, 0): + bl_glass.distribution = 'SHARP' + else: + write_mi_roughness_property(mi_context, mi_mat, 'alpha', bl_glass_wrap, 'Roughness', 0.) write_mi_ior_property(mi_context, mi_mat, 'int_ior', bl_glass_wrap, 'IOR', 1.5046) write_mi_rgb_property(mi_context, mi_mat, 'specular_transmittance', bl_glass_wrap, 'Color', [1.0, 1.0, 1.0]) # Write normal and bump maps @@ -429,7 +431,10 @@ def write_mi_roughdielectric_bsdf(mi_context, mi_mat, bl_mat_wrap, out_socket_id def write_mi_thindielectric_bsdf(mi_context, mi_mat, bl_mat_wrap, out_socket_id, mi_bump=None, mi_normal=None): bl_glass = bl_mat_wrap.ensure_node_type([out_socket_id], 'ShaderNodeBsdfGlass', 'BSDF') bl_glass_wrap = bl_shader_utils.NodeMaterialWrapper(bl_mat_wrap.bl_mat, out_node=bl_glass) - bl_glass.distribution = 'SHARP' + if bpy.app.version < (4, 0, 0): + bl_glass.distribution = 'SHARP' + else: + write_mi_roughness_property(mi_context, mi_mat, 'alpha', bl_glass_wrap, 'Roughness', 0.) bl_glass.inputs['IOR'].default_value = 1.0 write_mi_rgb_property(mi_context, mi_mat, 'specular_transmittance', bl_glass_wrap, 'Color', [1.0, 1.0, 1.0]) # Write normal and bump maps @@ -453,7 +458,10 @@ def write_mi_blend_bsdf(mi_context, mi_mat, bl_mat_wrap, out_socket_id, mi_bump= def write_mi_conductor_bsdf(mi_context, mi_mat, bl_mat_wrap, out_socket_id, mi_bump=None, mi_normal=None): bl_glossy = bl_mat_wrap.ensure_node_type([out_socket_id], 'ShaderNodeBsdfGlossy', 'BSDF') bl_glossy_wrap = bl_shader_utils.NodeMaterialWrapper(bl_mat_wrap.bl_mat, out_node=bl_glossy) - bl_glossy.distribution = 'SHARP' + if bpy.app.version < (4, 0, 0): + bl_glossy.distribution = 'SHARP' + else: + write_mi_roughness_property(mi_context, mi_mat, 'alpha', bl_glossy_wrap, 'Roughness', 0.0) reflectance = _eval_mi_bsdf_retro_reflection(mi_context, mi_mat, [1.0, 1.0, 1.0]) write_mi_rgb_value(mi_context, reflectance, bl_glossy_wrap, 'Color') # Write normal and bump maps @@ -487,7 +495,7 @@ def write_mi_mask_bsdf(mi_context, mi_mat, bl_mat_wrap, out_socket_id, mi_bump=N write_mi_material_to_node_graph(mi_context, mi_child_mats[0], bl_mix_wrap, 'Shader_001', mi_bump=mi_bump, mi_normal=mi_normal) return True -# FIXME: The plastic and roughplastic don't have simple equivalent in Blender. We rely on a +# FIXME: The plastic and roughplastic don't have simple equivalent in Blender. We rely on a # crude approximation using a Disney principled shader. def write_mi_plastic_bsdf(mi_context, mi_mat, bl_mat_wrap, out_socket_id, mi_bump=None, mi_normal=None): bl_principled = bl_mat_wrap.ensure_node_type([out_socket_id], 'ShaderNodeBsdfPrincipled', 'BSDF') @@ -524,7 +532,7 @@ def write_mi_bumpmap_bsdf(mi_context, mi_mat, bl_mat_wrap, out_socket_id, mi_bum return False child_mats = mi_props_utils.named_references_with_class(mi_context, mi_mat, 'BSDF') assert len(child_mats) == 1 - + write_mi_material_to_node_graph(mi_context, child_mats[0], bl_mat_wrap, out_socket_id, mi_bump=mi_mat, mi_normal=mi_normal) return True @@ -608,11 +616,11 @@ def write_mi_material_to_node_graph(mi_context, mi_mat, bl_mat_wrap, out_socket_ mi_context.log(f'Mitsuba BSDF type "{mat_type}" not supported. Skipping.', 'WARN') write_bl_error_material(bl_mat_wrap, out_socket_id) return - + if is_within_twosided and mat_type == 'twosided': mi_context.log('Cannot have nested twosided materials.', 'ERROR') return - + if not is_within_twosided and mat_type != 'twosided' and mat_type not in _always_twosided_bsdfs: # Write one-sided material write_twosided_material(mi_context, bl_mat_wrap, out_socket_id, mi_front_mat=mi_mat, mi_back_mat=None, mi_bump=mi_bump, mi_normal=mi_normal) @@ -622,7 +630,7 @@ def write_mi_material_to_node_graph(mi_context, mi_mat, bl_mat_wrap, out_socket_ def mi_material_to_bl_material(mi_context, mi_mat, mi_emitter=None): ''' Create a Blender node tree representing a given Mitsuba material - + Params ------ mi_context : Mitsuba import context @@ -639,7 +647,7 @@ def mi_material_to_bl_material(mi_context, mi_mat, mi_emitter=None): bl_mat = bpy.data.materials.new(name=mi_mat.id()) bl_mat_wrap = bl_shader_utils.NodeMaterialWrapper(bl_mat, init_empty=True) out_socket_id = 'Surface' - + # If the material is emissive, write the emission shader if mi_emitter is not None: old_bl_mat_wrap = bl_mat_wrap @@ -654,5 +662,5 @@ def mi_material_to_bl_material(mi_context, mi_mat, mi_emitter=None): # Format the shader node graph bl_mat_wrap.format_node_tree() - + return bl_mat From 503bd53cb94921b9001540e6b04776ea6ad97d03 Mon Sep 17 00:00:00 2001 From: Baptiste Nicolet Date: Mon, 27 May 2024 13:48:37 +0200 Subject: [PATCH 2/6] Use correct Spectrum converter name for world emitter --- mitsuba-blender/io/importer/world.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mitsuba-blender/io/importer/world.py b/mitsuba-blender/io/importer/world.py index b4efc5d..673130d 100644 --- a/mitsuba-blender/io/importer/world.py +++ b/mitsuba-blender/io/importer/world.py @@ -6,7 +6,7 @@ importlib.reload(mi_spectra_utils) if "bl_image_utils" in locals(): importlib.reload(bl_image_utils) - + import bpy from mathutils import Color @@ -25,7 +25,7 @@ def write_mi_srgb_emitter_spectrum(mi_context, mi_obj, bl_world_wrap, radiance_s bl_world_wrap.out_node.inputs[strength_socket_id].default_value = strength _emitter_spectrum_object_writers = { - 'SRGBEmitterSpectrum': write_mi_srgb_emitter_spectrum + 'SRGBReflectanceSpectrum': write_mi_srgb_emitter_spectrum } def write_mi_emitter_spectrum_object(mi_context, mi_obj, bl_world_wrap, radiance_socket_id, strength_socket_id, default=None): @@ -113,7 +113,7 @@ def write_mi_emitter_to_node_graph(mi_context, mi_emitter, bl_world_wrap, out_so def mi_emitter_to_bl_world(mi_context, mi_emitter): ''' Create a Blender node tree representing a given Mitsuba emitter - + Params ------ mi_context : Mitsuba import context From 735197c2fae5cb838dea34812c606b4f6e9805ea Mon Sep 17 00:00:00 2001 From: Baptiste Nicolet Date: Mon, 27 May 2024 19:31:25 +0200 Subject: [PATCH 3/6] Support importing scene files with complex directory structures --- mitsuba-blender/io/importer/__init__.py | 51 +++++++++++++++++++------ mitsuba-blender/io/importer/common.py | 5 ++- mitsuba-blender/io/importer/renderer.py | 8 ++-- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/mitsuba-blender/io/importer/__init__.py b/mitsuba-blender/io/importer/__init__.py index db55a16..31c38f6 100644 --- a/mitsuba-blender/io/importer/__init__.py +++ b/mitsuba-blender/io/importer/__init__.py @@ -1,4 +1,6 @@ import time +import os +import xml.etree.ElementTree as ET if "bpy" in locals(): import importlib @@ -50,8 +52,34 @@ def _convert_named_references(mi_context, mi_props, parent_node, type_filter=[]) if child_node is not None: parent_node.add_child(child_node) +def resolve_paths(filename, defaults={}): + if filename.startswith('$'): + filename = defaults[filename[1:]] + import mitsuba + fs = mitsuba.Thread.thread().file_resolver() + filename = str(fs.resolve(filename)) + if os.path.dirname(filename) not in fs: + fs.prepend(os.path.dirname(filename)) + + tree = ET.parse(str(fs.resolve(filename))) + for child in tree.getroot(): + if child.tag == 'default': + defaults[child.attrib['name']] = child.attrib['value'] + elif child.tag == 'path': + new_path = child.attrib['value'] + if not os.path.isabs(new_path): + new_path = os.path.abspath(os.path.join(os.path.dirname(filename), new_path)) + if not os.path.exists(new_path): + new_path = fs.resolve(child.attrib['value']) + if not os.path.exists(new_path): + raise ValueError(f'Path "{new_path}" does not exist.') + fs.prepend(new_path) + elif child.tag == 'include': + resolve_paths(str(fs.resolve(child.attrib['filename'])), defaults) + + ######################## -## Scene convertion ## +## Scene conversion ## ######################## def mi_scene_to_bl_node(mi_context, mi_props): @@ -116,7 +144,7 @@ def mi_bsdf_to_bl_node(mi_context, mi_props, mi_emitter=None): node = common.create_blender_node(common.BlenderNodeType.MATERIAL, id=mi_props.id()) # Parse referenced textures to ensure they are loaded before we parse the material _convert_named_references(mi_context, mi_props, node, type_filter=['Texture']) - + if mi_emitter is None: # If the BSDF is not emissive, we can look for it in the cache. bl_material = mi_context.get_bl_material(mi_props.id()) @@ -148,7 +176,7 @@ def mi_emitter_to_bl_node(mi_context, mi_props): _convert_named_references(mi_context, mi_props, node) bl_light, world_matrix = emitters.mi_emitter_to_bl_light(mi_context, mi_props) - + node.obj_type = common.BlenderObjectNodeType.LIGHT node.bl_data = bl_light node.world_matrix = world_matrix @@ -156,7 +184,7 @@ def mi_emitter_to_bl_node(mi_context, mi_props): def mi_shape_to_bl_node(mi_context, mi_props): node = common.create_blender_node(common.BlenderNodeType.OBJECT, id=mi_props.id()) - + mi_mats = mi_props_utils.named_references_with_class(mi_context, mi_props, 'BSDF') assert len(mi_mats) == 1 mi_emitters = mi_props_utils.named_references_with_class(mi_context, mi_props, 'Emitter') @@ -167,7 +195,7 @@ def mi_shape_to_bl_node(mi_context, mi_props): # Convert the shape bl_shape, world_matrix = shapes.mi_shape_to_bl_shape(mi_context, mi_props) - + node.obj_type = common.BlenderObjectNodeType.SHAPE node.bl_data = bl_shape node.world_matrix = world_matrix @@ -188,7 +216,7 @@ def mi_texture_to_bl_node(mi_context, mi_props): if bl_image is None: return None mi_context.register_bl_image(mi_props.id(), bl_image) - + node.bl_image = bl_image return node @@ -228,7 +256,7 @@ def instantiate_bl_scene_node(mi_context, bl_node): def instantiate_bl_shape_object_node(mi_context, bl_node): bl_obj = bpy.data.objects.new(bl_node.id, bl_node.bl_data) bl_obj.matrix_world = bl_node.world_matrix - + shape_has_material = False for child_node in bl_node.children: # NOTE: Mitsuba shapes support only one BSDF @@ -365,7 +393,7 @@ def instantiate_bl_data_node(mi_context, bl_node): def load_mitsuba_scene(bl_context, bl_scene, bl_collection, filepath, global_mat): ''' Load a Mitsuba scene from an XML file into a Blender scene. - + Params ------ bl_context: Blender context @@ -376,8 +404,9 @@ def load_mitsuba_scene(bl_context, bl_scene, bl_collection, filepath, global_mat ''' start_time = time.time() # Load the Mitsuba XML and extract the objects' properties - from mitsuba import xml_to_props + from mitsuba import xml_to_props, Thread raw_props = xml_to_props(filepath) + resolve_paths(filepath) mi_scene_props = common.MitsubaSceneProperties(raw_props) mi_context = common.MitsubaSceneImportContext(bl_context, bl_scene, bl_collection, filepath, mi_scene_props, global_mat) @@ -386,7 +415,7 @@ def load_mitsuba_scene(bl_context, bl_scene, bl_collection, filepath, global_mat if bl_scene_data_node is None: mi_context.log('Failed to load Mitsuba scene', 'ERROR') return - + # Initialize the Mitsuba renderer inside of Blender renderer.init_mitsuba_renderer(mi_context) @@ -397,7 +426,7 @@ def load_mitsuba_scene(bl_context, bl_scene, bl_collection, filepath, global_mat # Instantiate a default Blender world if none was created if mi_context.bl_scene.world is None: mi_context.bl_scene.world = world.create_default_bl_world() - + # Check that every property was accessed at least once as a sanity check for cls, prop in mi_scene_props: _check_unqueried_props(mi_context, cls, prop) diff --git a/mitsuba-blender/io/importer/common.py b/mitsuba-blender/io/importer/common.py index aea715d..52804f4 100644 --- a/mitsuba-blender/io/importer/common.py +++ b/mitsuba-blender/io/importer/common.py @@ -202,6 +202,8 @@ def get_first_of_class(self, cls): class MitsubaSceneImportContext: ''' Define a context for the Mitsuba scene importer ''' def __init__(self, bl_context, bl_scene, bl_collection, filepath, mi_scene_props, axis_matrix): + from mitsuba import Thread + self.bl_context = bl_context self.bl_scene = bl_scene self.bl_collection = bl_collection @@ -212,6 +214,7 @@ def __init__(self, bl_context, bl_scene, bl_collection, filepath, mi_scene_props self.axis_matrix_inv = axis_matrix.inverted() self.bl_material_cache = {} self.bl_image_cache = {} + self.fs = Thread.thread().file_resolver() def log(self, message, level='INFO'): ''' @@ -242,7 +245,7 @@ def mi_space_to_bl_space(self, matrix): return self.axis_matrix_inv @ matrix def resolve_scene_relative_path(self, path): - abs_path = os.path.join(self.directory, path) + abs_path = str(self.fs.resolve(path)) if not os.path.exists(abs_path): self.log(f'Cannot resolve scene relative path "{path}".', 'ERROR') return None diff --git a/mitsuba-blender/io/importer/renderer.py b/mitsuba-blender/io/importer/renderer.py index ffa0c87..49b4592 100644 --- a/mitsuba-blender/io/importer/renderer.py +++ b/mitsuba-blender/io/importer/renderer.py @@ -111,7 +111,7 @@ def apply_mi_integrator_properties(mi_context, mi_props, bl_integrator_props=Non if mi_integrator_type not in _mi_integrator_properties_converters: mi_context.log(f'Mitsuba Integrator "{mi_integrator_type}" is not supported.', 'ERROR') return False - + return _mi_integrator_properties_converters[mi_integrator_type](mi_context, mi_props, bl_integrator_props) ########################## @@ -171,7 +171,7 @@ def apply_mi_rfilter_properties(mi_context, mi_props): if mi_rfilter_type not in _mi_rfilter_properties_converters: mi_context.log(f'Mitsuba Reconstruction Filter "{mi_rfilter_type}" is not supported.', 'ERROR') return False - + return _mi_rfilter_properties_converters[mi_rfilter_type](mi_context, mi_props) ########################## @@ -249,7 +249,7 @@ def apply_mi_sampler_properties(mi_context, mi_props): if mi_sampler_type not in _mi_sampler_properties_converters: mi_context.log(f'Mitsuba Sampler "{mi_sampler_type}" is not supported.', 'ERROR') return False - + return _mi_sampler_properties_converters[mi_sampler_type](mi_context, mi_props) ####################### @@ -287,7 +287,7 @@ def apply_mi_film_properties(mi_context, mi_props): if mi_film_type not in _mi_film_properties_converters: mi_context.log(f'Mitsuba Film "{mi_film_type}" is not supported.', 'ERROR') return False - + return _mi_film_properties_converters[mi_film_type](mi_context, mi_props) ########################### From f75a2ce08c85ecbe9ceb40f5d30537475e0253bf Mon Sep 17 00:00:00 2001 From: Baptiste Nicolet Date: Wed, 29 May 2024 11:52:20 +0200 Subject: [PATCH 4/6] Ignore sensor child plugins defined at the top level, as they are also defined by the reference to them --- mitsuba-blender/io/importer/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mitsuba-blender/io/importer/__init__.py b/mitsuba-blender/io/importer/__init__.py index 31c38f6..446bc08 100644 --- a/mitsuba-blender/io/importer/__init__.py +++ b/mitsuba-blender/io/importer/__init__.py @@ -275,9 +275,6 @@ def instantiate_bl_shape_object_node(mi_context, bl_node): return True def instantiate_bl_camera_object_node(mi_context, bl_node): - # FIXME: Move this for delayed instantiation as the whole scene needs to - # be created in order to support multiple camera settings. - # FIXME: Handle child nodes bl_obj = bpy.data.objects.new(bl_node.id, bl_node.bl_data) bl_obj.matrix_world = bl_node.world_matrix @@ -343,7 +340,10 @@ def instantiate_sampler_properties_node(mi_context, bl_node): } def instantiate_bl_properties_node(mi_context, bl_node): + node_prop_type = bl_node.prop_type + if node_prop_type != common.BlenderPropertiesNodeType.INTEGRATOR and type(bl_node.parent) == common.BlenderSceneNode: + return True if node_prop_type not in _bl_properties_node_instantiators: mi_context.log(f'Unknown Blender property node type "{node_prop_type}".', 'ERROR') return False From f1f22d2980eb4348b510a7c966a40fb83867d5c5 Mon Sep 17 00:00:00 2001 From: Baptiste Nicolet Date: Wed, 29 May 2024 12:06:43 +0200 Subject: [PATCH 5/6] Mesh import: dont recompute normals for versions > 4.0 --- mitsuba-blender/io/importer/shapes.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mitsuba-blender/io/importer/shapes.py b/mitsuba-blender/io/importer/shapes.py index 01d68c9..925151f 100644 --- a/mitsuba-blender/io/importer/shapes.py +++ b/mitsuba-blender/io/importer/shapes.py @@ -35,7 +35,8 @@ def _set_bl_mesh_shading(bl_mesh, flat_shading=True, flip_normals=False): if flat_shading: bl_mesh.polygons.foreach_set('use_smooth', [False] * len(bl_mesh.polygons)) else: - bl_mesh.calc_normals() + if bpy.app.version < (4, 0, 0): + bl_mesh.calc_normals() bl_mesh.polygons.foreach_set('use_smooth', [True] * len(bl_mesh.polygons)) if flip_normals: bl_mesh.flip_normals() @@ -56,7 +57,7 @@ def mi_ply_to_bl_shape(mi_context, mi_shape): if not bl_mesh: mi_context.log(f'Cannot load PLY mesh file "{filename}".', 'ERROR') return None - + # Set face normals if requested _set_bl_mesh_shading(bl_mesh, mi_shape.get('face_normals', False)) @@ -141,7 +142,7 @@ def mi_rectangle_to_bl_shape(mi_context, mi_shape): bl_bmesh.free() _set_bl_mesh_shading(bl_mesh, flip_normals=mi_shape.get('flip_normals', False)) - + # FIXME: The world matrix seems off world_matrix = bl_transform_utils.mi_transform_to_bl_transform(mi_shape.get('to_world', None)) @@ -181,7 +182,7 @@ def mi_shape_to_bl_shape(mi_context, mi_shape): if shape_type not in _shape_converters: mi_context.log(f'Mitsuba Shape type "{shape_type}" not supported.', 'ERROR') return None - + # Create the Blender object bl_mesh, world_matrix = _shape_converters[shape_type](mi_context, mi_shape) From 8ff9c7edd61a1d53d1f04004e7eebdcea02ca196 Mon Sep 17 00:00:00 2001 From: Baptiste Nicolet Date: Wed, 29 May 2024 12:41:59 +0200 Subject: [PATCH 6/6] Support loading serialized meshes by converting them to temporary PLY files --- mitsuba-blender/io/importer/shapes.py | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/mitsuba-blender/io/importer/shapes.py b/mitsuba-blender/io/importer/shapes.py index 925151f..505a751 100644 --- a/mitsuba-blender/io/importer/shapes.py +++ b/mitsuba-blender/io/importer/shapes.py @@ -65,6 +65,34 @@ def mi_ply_to_bl_shape(mi_context, mi_shape): return bl_mesh, mi_context.mi_space_to_bl_space(world_matrix) +def mi_serialized_to_bl_shape(mi_context, mi_shape): + from mitsuba import load_dict + import tempfile + import os + assert mi_shape.has_property('filename') + + filename = mi_shape['filename'] + abs_path = mi_context.resolve_scene_relative_path(filename) + + # Save as a temporary .PLY file + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_mesh = load_dict({'type': 'serialized', 'filename': abs_path, 'shape_index': mi_shape['shape_index']}) + tmp_filename = os.path.join(tmp_dir, 'shape.ply') + tmp_mesh.write_ply(tmp_filename) + # Load .PLY mesh from temporary file + bl_mesh = bl_import_ply.load_ply_mesh(tmp_filename, mi_shape.id()) + + if not bl_mesh: + mi_context.log(f'Cannot load serialized mesh file "{filename}".', 'ERROR') + return None + + # Set face normals if requested + _set_bl_mesh_shading(bl_mesh, mi_shape.get('face_normals', False)) + + world_matrix = bl_transform_utils.mi_transform_to_bl_transform(mi_shape.get('to_world', None)) + + return bl_mesh, mi_context.mi_space_to_bl_space(world_matrix) + def mi_obj_to_bl_shape(mi_context, mi_shape): start_time = time.time() @@ -170,6 +198,7 @@ def mi_cube_to_bl_shape(mi_context, mi_shape): _shape_converters = { 'ply': mi_ply_to_bl_shape, + 'serialized': mi_serialized_to_bl_shape, 'obj': mi_obj_to_bl_shape, 'sphere': mi_sphere_to_bl_shape, 'disk': mi_disk_to_bl_shape,