Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] v0.5.0 #105

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mitsuba-blender/io/exporter/materials.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
57 changes: 43 additions & 14 deletions mitsuba-blender/io/importer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import time
import os
import xml.etree.ElementTree as ET

if "bpy" in locals():
import importlib
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -148,15 +176,15 @@ 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
return node

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')
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -247,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

Expand Down Expand Up @@ -315,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
Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion mitsuba-blender/io/importer/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'):
'''
Expand Down Expand Up @@ -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
Expand Down
34 changes: 21 additions & 13 deletions mitsuba-blender/io/importer/materials.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
importlib.reload(mi_props_utils)
if "textures" in locals():
importlib.reload(textures)

import bpy

from . import bl_shader_utils
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
8 changes: 4 additions & 4 deletions mitsuba-blender/io/importer/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

##########################
Expand Down Expand Up @@ -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)

##########################
Expand Down Expand Up @@ -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)

#######################
Expand Down Expand Up @@ -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)

###########################
Expand Down
Loading
Loading