1#!/usr/bin/python 2 3# 4# This source file is part of appleseed. 5# Visit https://appleseedhq.net/ for additional information and resources. 6# 7# This software is released under the MIT license. 8# 9# Copyright (c) 2016-2018 Francois Beaune, The appleseedhq Organization 10# 11# Permission is hereby granted, free of charge, to any person obtaining a copy 12# of this software and associated documentation files (the "Software"), to deal 13# in the Software without restriction, including without limitation the rights 14# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15# copies of the Software, and to permit persons to whom the Software is 16# furnished to do so, subject to the following conditions: 17# 18# The above copyright notice and this permission notice shall be included in 19# all copies or substantial portions of the Software. 20# 21# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27# THE SOFTWARE. 28# 29 30from __future__ import division 31from __future__ import print_function 32from xml.etree.ElementTree import ElementTree 33import appleseed as asr 34import argparse 35import math 36import os.path 37import sys 38import traceback 39 40 41# ------------------------------------------------------------------------------------------------- 42# Utility functions. 43# ------------------------------------------------------------------------------------------------- 44 45def info(message): 46 print(message) 47 48 49def progress(message): 50 print(message + "...") 51 52 53def warning(message): 54 print("Warning: {0}.".format(message)) 55 56 57def fatal(message): 58 print("Fatal: {0}. Aborting.".format(message)) 59 # if sys.exc_info()[0]: 60 # print(traceback.format_exc()) 61 sys.exit(1) 62 63 64def square(x): 65 return x * x 66 67 68# ------------------------------------------------------------------------------------------------- 69# Conversion code. 70# ------------------------------------------------------------------------------------------------- 71 72def get_vector(element): 73 return [ 74 float(element.attrib["x"]), 75 float(element.attrib["y"]), 76 float(element.attrib["z"]) 77 ] 78 79 80def get_matrix(element): 81 values = [float(x) for x in element.attrib["value"].split()] 82 if len(values) != 16: 83 fatal("Matrix was expected to contain 16 coefficients but contained {0} instead".format(len(values))) 84 matrix = asr.Matrix4d() 85 for i in range(16): 86 matrix[int(i / 4), i % 4] = values[i] 87 return matrix 88 89 90def get_rgb(element): 91 values = [float(x) for x in element.attrib["value"].split(",")] 92 if len(values) != 3: 93 fatal("RGB color was expected to contain 3 coefficients but contained {0} instead".format(len(values))) 94 return values 95 96 97def get_rgb_and_multiplier(element): 98 values = get_rgb(element) 99 max_value = max(values) 100 if max_value > 1.0: 101 return [x / max_value for x in values], max_value 102 else: 103 return values, 1.0 104 105 106def set_private_param(params, key, value): 107 params.setdefault("mitsuba2appleseed", {})[key] = value 108 109 110def get_private_param(params, key, default): 111 return params.setdefault("mitsuba2appleseed", {}).get(key, default) 112 113 114def clear_private_params(params): 115 params.pop("mitsuba2appleseed", None) 116 117 118def make_unique_name(prefix, entities): 119 names = set(entity.get_name() for entity in entities) 120 121 if prefix not in names: 122 return prefix 123 124 n = 1 125 while True: 126 candidate = "{0}{1}".format(prefix, n) 127 if candidate not in names: 128 return candidate 129 130 131def convert_path_integrator(project, element): 132 interactive_config = project.configurations().get_by_name("interactive") 133 final_config = project.configurations().get_by_name("final") 134 135 interactive_config.insert_path("lighting_engine", "pt") 136 final_config.insert_path("lighting_engine", "pt") 137 138 max_depth_element = element.find("integer[@name='maxDepth']") 139 if max_depth_element is not None: 140 max_depth = int(max_depth_element.attrib["value"]) 141 interactive_config.insert_path("pt.max_path_length", max_depth) 142 final_config.insert_path("pt.max_path_length", max_depth) 143 144 145def convert_sppm_integrator(project, element): 146 interactive_config = project.configurations().get_by_name("interactive") 147 final_config = project.configurations().get_by_name("final") 148 149 interactive_config.insert_path("lighting_engine", "pt") # SPPM is incompatible with interactive rendering 150 final_config.insert_path("lighting_engine", "sppm") 151 152 final_config.insert_path("sppm.photon_type", "poly") 153 154 max_depth_element = element.find("integer[@name='maxDepth']") 155 if max_depth_element is not None: 156 max_depth = int(max_depth_element.attrib["value"]) 157 interactive_config.insert_path("pt.max_path_length", max_depth) 158 final_config.insert_path("sppm.photon_tracing_max_path_length", max_depth) 159 final_config.insert_path("sppm.path_tracing_max_path_length", max_depth) 160 161 photon_count_element = element.find("integer[@name='photonCount']") 162 if photon_count_element is not None: 163 photon_count = int(photon_count_element.attrib["value"]) 164 final_config.insert_path("sppm.light_photons_per_pass", photon_count) 165 final_config.insert_path("sppm.env_photons_per_pass", photon_count) 166 167 initial_radius_element = element.find("float[@name='initialRadius']") 168 if initial_radius_element is not None: 169 initial_radius = float(initial_radius_element.attrib["value"]) 170 # todo: this is mostly incorrect as in appleseed the initial radius is expressed 171 # as a percentage of the scene's radius, while in Mitsuba it is a world space 172 # distance. 173 final_config.insert_path("sppm.initial_radius", initial_radius) 174 175 alpha_element = element.find("float[@name='alpha']") 176 if alpha_element is not None: 177 alpha = float(alpha_element.attrib["value"]) 178 final_config.insert_path("sppm.alpha", alpha) 179 180 pass_count_element = element.find("integer[@name='maxPasses']") 181 if pass_count_element is not None: 182 pass_count = int(pass_count_element.attrib["value"]) 183 project.configurations().get_by_name("final").insert_path("generic_frame_renderer.passes", pass_count) 184 185 186def convert_integrator(project, element): 187 type = element.attrib["type"] 188 if type == "path": 189 convert_path_integrator(project, element) 190 elif type == "sppm": 191 convert_sppm_integrator(project, element) 192 else: 193 warning("Don't know how to convert integrator of type {0}, defaulting to path integrator.".format(type)) 194 convert_path_integrator(project, element) 195 196 197def convert_film(camera_params, frame_params, element): 198 width = int(element.find("integer[@name='width']").attrib["value"]) 199 height = int(element.find("integer[@name='height']").attrib["value"]) 200 dimensions = "{0} {1}".format(width, height) 201 camera_params["film_dimensions"] = dimensions 202 frame_params["resolution"] = dimensions 203 204 205def convert_sampler(project, element): 206 sample_count_element = element.find("integer[@name='sampleCount']") 207 if sample_count_element is not None: 208 sample_count = int(sample_count_element.attrib["value"]) 209 project.configurations().get_by_name("final").insert_path("uniform_pixel_renderer.samples", sample_count) 210 211 212def convert_sensor(project, scene, element): 213 camera_params = {} 214 camera_matrix = None 215 frame_params = { 216 "camera": "camera", 217 "color_space": "srgb", 218 "tile_size": "32 32" 219 } 220 221 for child in element: 222 if child.tag == "float": 223 if child.attrib["name"] == "fov": 224 camera_params["horizontal_fov"] = child.attrib["value"] 225 elif child.tag == "transform": 226 camera_matrix = get_matrix(child.find("matrix")) 227 elif child.tag == "sampler": 228 convert_sampler(project, child) 229 elif child.tag == "film": 230 convert_film(camera_params, frame_params, child) 231 232 camera = asr.Camera("pinhole_camera", "camera", camera_params) 233 if camera_matrix is not None: 234 roty = asr.Matrix4d.make_rotation(asr.Vector3d(0.0, 1.0, 0.0), math.radians(180.0)) 235 camera_matrix = camera_matrix * roty 236 camera.transform_sequence().set_transform(0.0, asr.Transformd(camera_matrix)) 237 scene.cameras().insert(camera) 238 239 project.set_frame(asr.Frame("beauty", frame_params)) 240 241 242def create_linear_rgb_color(parent, color_name, rgb, multiplier): 243 color_params = { 244 "color_space": "linear_rgb", 245 "multiplier": multiplier 246 } 247 parent.colors().insert(asr.ColorEntity(color_name, color_params, rgb)) 248 249 250def is_hdri_file(filepath): 251 return filepath.endswith(".exr") or filepath.endswith(".hdr") or filepath.endswith(".pfm") 252 253 254def create_texture(parent, texture_name, filepath): 255 parent.textures().insert(asr.Texture("disk_texture_2d", texture_name, { 256 "filename": filepath, 257 "color_space": "linear_rgb" if is_hdri_file(filepath) else "srgb" 258 }, [])) 259 260 texture_instance_name = "{0}_inst".format(texture_name) 261 texture_instance = asr.TextureInstance(texture_instance_name, {}, texture_name, asr.Transformf.identity()) 262 parent.texture_instances().insert(texture_instance) 263 return texture_instance_name 264 265 266def convert_texture(parent, texture_name, element): 267 type = element.attrib["type"] 268 if type == "bitmap": 269 filepath = element.find("string[@name='filename']").attrib["value"] 270 return create_texture(parent, texture_name, filepath) 271 else: 272 warning("Don't know how to convert texture of type {0}".format(type)) 273 color_params = { 274 "color_space": "srgb", 275 "multiplier": 1.0 276 } 277 parent.colors().insert(asr.ColorEntity(texture_name, color_params, [0.7, 0.7, 0.7])) 278 return texture_name 279 280 281def convert_colormap(parent, parent_name, element): 282 map_name = element.attrib["name"] 283 if element.tag == "texture": 284 texture_name = "{0}_{1}".format(parent_name, map_name) 285 return convert_texture(parent, texture_name, element) 286 elif element.tag == "rgb": 287 color_name = "{0}_{1}".format(parent_name, map_name) 288 rgb, multiplier = get_rgb_and_multiplier(element) 289 create_linear_rgb_color(parent, color_name, rgb, multiplier) 290 return color_name 291 else: 292 warning("Don't know how to convert color map of type {0}".format(element.tag)) 293 294 295def convert_alpha_to_roughness(element, default_alpha): 296 alpha_element = element.find("float[@name='alpha']") 297 alpha = float(alpha_element.attrib["value"]) if alpha_element is not None else default_alpha 298 return math.sqrt(alpha) 299 300 301def fresnel_conductor_inverse_reparam(n, k): 302 # See artist_friendly_fresnel_conductor_inverse_reparameterization() function 303 # in src/appleseed/foundation/math/fresnel.h. 304 305 normal_reflectance = [] 306 edge_tint = [] 307 308 for ni, ki in zip(n, k): 309 k2 = square(ki) 310 r = (square(ni - 1.0) + k2) / (square(ni + 1.0) + k2) 311 normal_reflectance.append(r) 312 313 sqrt_r = math.sqrt(r) 314 tmp = (1.0 + sqrt_r) / (1.0 - sqrt_r) 315 edge_tint.append(max((tmp - ni) / (tmp - ((1.0 - r) / (1.0 + r))), 0)) 316 317 return normal_reflectance, edge_tint 318 319 320def convert_diffuse_bsdf(assembly, bsdf_name, element): 321 bsdf_params = {} 322 323 reflectance = element.find("*[@name='reflectance']") 324 bsdf_params["reflectance"] = convert_colormap(assembly, bsdf_name, reflectance) 325 326 assembly.bsdfs().insert(asr.BSDF("lambertian_brdf", bsdf_name, bsdf_params)) 327 328 329def convert_roughdiffuse_bsdf(assembly, bsdf_name, element): 330 bsdf_params = {} 331 332 reflectance = element.find("*[@name='reflectance']") 333 bsdf_params["reflectance"] = convert_colormap(assembly, bsdf_name, reflectance) 334 335 bsdf_params["roughness"] = convert_alpha_to_roughness(element, 0.2) 336 337 assembly.bsdfs().insert(asr.BSDF("orennayar_brdf", bsdf_name, bsdf_params)) 338 339 340def convert_plastic_bsdf(assembly, bsdf_name, element, roughness=0.0): 341 bsdf_params = {} 342 343 distribution_element = element.find("string[@name='distribution']") 344 if distribution_element is not None: 345 distribution = distribution_element.attrib["value"] 346 if distribution == "phong": 347 warning("Phong distribution not supported by appleseed's plastic BRDF, defaulting to GGX") 348 distribution = "ggx" 349 bsdf_params["mdf"] = distribution 350 else: 351 bsdf_params["mdf"] = "beckmann" 352 353 specular_reflectance_element = element.find("*[@name='specularReflectance']") 354 bsdf_params["specular_reflectance"] = convert_colormap(assembly, bsdf_name, specular_reflectance_element) \ 355 if specular_reflectance_element is not None else 1.0 356 357 bsdf_params["roughness"] = roughness 358 359 diffuse_reflectance_element = element.find("*[@name='diffuseReflectance']") 360 bsdf_params["diffuse_reflectance"] = convert_colormap(assembly, bsdf_name, diffuse_reflectance_element) \ 361 if diffuse_reflectance_element is not None else 0.5 362 363 ior_element = element.find("float[@name='intIOR']") 364 bsdf_params["ior"] = float(ior_element.attrib["value"]) if ior_element is not None else 1.49 365 366 nonlinear_element = element.find("boolean[@name='nonlinear']") 367 bsdf_params["internal_scattering"] = 1.0 if nonlinear_element is not None and \ 368 nonlinear_element.attrib["value"] == "true" else 0.0 369 370 assembly.bsdfs().insert(asr.BSDF("plastic_brdf", bsdf_name, bsdf_params)) 371 372 373def convert_roughplastic_bsdf(assembly, bsdf_name, element): 374 roughness = convert_alpha_to_roughness(element, 0.1) 375 return convert_plastic_bsdf(assembly, bsdf_name, element, roughness) 376 377 378def convert_conductor_bsdf(assembly, bsdf_name, element, roughness=0.0): 379 bsdf_params = {} 380 381 material_element = element.find("string[@name='material']") 382 if material_element is not None: 383 material = material_element.attrib["value"] 384 if material == "none": 385 bsdf_params["mdf"] = "ggx" 386 bsdf_params["normal_reflectance"] = 1.0 387 bsdf_params["edge_tint"] = 0.0 388 bsdf_params["roughness"] = roughness 389 assembly.bsdfs().insert(asr.BSDF("metal_brdf", bsdf_name, bsdf_params)) 390 return 391 392 eta_element = element.find("rgb[@name='eta']") 393 eta_rgb = get_rgb(eta_element) 394 395 k_element = element.find("rgb[@name='k']") 396 k_rgb = get_rgb(k_element) 397 398 normal_reflectance_rgb, edge_tint_rgb = fresnel_conductor_inverse_reparam(eta_rgb, k_rgb) 399 400 normal_reflectance_color_name = "{0}_normal_reflectance".format(bsdf_name) 401 create_linear_rgb_color(assembly, normal_reflectance_color_name, normal_reflectance_rgb, 1.0) 402 403 edge_tint_color_name = "{0}_edge_tint".format(bsdf_name) 404 create_linear_rgb_color(assembly, edge_tint_color_name, edge_tint_rgb, 1.0) 405 406 bsdf_params["mdf"] = "ggx" 407 bsdf_params["normal_reflectance"] = normal_reflectance_color_name 408 bsdf_params["edge_tint"] = edge_tint_color_name 409 bsdf_params["roughness"] = roughness 410 411 specular_reflectance_element = element.find("*[@name='specularReflectance']") 412 bsdf_params["reflectance_multiplier"] = convert_colormap(assembly, bsdf_name, specular_reflectance_element) \ 413 if specular_reflectance_element is not None else 1.0 414 415 assembly.bsdfs().insert(asr.BSDF("metal_brdf", bsdf_name, bsdf_params)) 416 417 418def convert_roughconductor_bsdf(assembly, bsdf_name, element): 419 roughness = convert_alpha_to_roughness(element, 0.1) 420 return convert_conductor_bsdf(assembly, bsdf_name, element, roughness) 421 422 423def convert_dielectric_bsdf(assembly, bsdf_name, element, roughness=0.0): 424 bsdf_params = {} 425 426 # todo: support textured IOR. 427 bsdf_params["ior"] = float(element.find("float[@name='intIOR']").attrib["value"]) 428 429 bsdf_params["mdf"] = "ggx" 430 bsdf_params["surface_transmittance"] = 1.0 431 bsdf_params["roughness"] = roughness 432 433 specular_reflectance_element = element.find("*[@name='specularReflectance']") 434 bsdf_params["reflection_tint"] = convert_colormap(assembly, bsdf_name, specular_reflectance_element) \ 435 if specular_reflectance_element is not None else 1.0 436 437 specular_transmittance_element = element.find("*[@name='specularTransmittance']") 438 bsdf_params["refraction_tint"] = convert_colormap(assembly, bsdf_name, specular_transmittance_element) \ 439 if specular_transmittance_element is not None else 1.0 440 441 assembly.bsdfs().insert(asr.BSDF("glass_bsdf", bsdf_name, bsdf_params)) 442 443 444def convert_roughdielectric_bsdf(assembly, bsdf_name, element): 445 roughness = convert_alpha_to_roughness(element, 0.1) 446 return convert_dielectric_bsdf(assembly, bsdf_name, element, roughness) 447 448 449def convert_area_emitter(assembly, emitter_name, element): 450 if emitter_name is None: 451 fatal("Area emitters must have a name") 452 453 edf_params = {} 454 455 radiance = element.find("*[@name='radiance']") 456 edf_params["radiance"] = convert_colormap(assembly, emitter_name, radiance) 457 458 assembly.edfs().insert(asr.EDF("diffuse_edf", emitter_name, edf_params)) 459 460 461def convert_constant_emitter(scene, emitter_name, element): 462 radiance = element.find("*[@name='radiance']") 463 464 scene.environment_edfs().insert(asr.EnvironmentEDF("constant_environment_edf", "environment_edf", { 465 "radiance": convert_colormap(scene, emitter_name, radiance) 466 })) 467 468 scene.environment_shaders().insert(asr.EnvironmentShader("edf_environment_shader", "environment_shader", { 469 "environment_edf": 'environment_edf' 470 })) 471 472 scene.set_environment(asr.Environment("environment", { 473 "environment_edf": "environment_edf", 474 "environment_shader": "environment_shader" 475 })) 476 477 478def convert_envmap_emitter(scene, emitter_name, element): 479 filepath = element.find("string[@name='filename']").attrib["value"] 480 481 texture_instance_name = create_texture(scene, "environment_map", filepath) 482 483 env_edf = asr.EnvironmentEDF("latlong_map_environment_edf", "environment_edf", { 484 "radiance": texture_instance_name 485 }) 486 487 matrix_element = element.find("transform[@name='toWorld']/matrix") 488 if matrix_element is not None: 489 matrix = get_matrix(matrix_element) 490 roty = asr.Matrix4d.make_rotation(asr.Vector3d(0.0, 1.0, 0.0), math.radians(-90.0)) 491 matrix = matrix * roty 492 env_edf.transform_sequence().set_transform(0.0, asr.Transformd(matrix)) 493 494 scene.environment_edfs().insert(env_edf) 495 496 scene.environment_shaders().insert(asr.EnvironmentShader("edf_environment_shader", "environment_shader", { 497 "environment_edf": 'environment_edf' 498 })) 499 500 scene.set_environment(asr.Environment("environment", { 501 "environment_edf": "environment_edf", 502 "environment_shader": "environment_shader" 503 })) 504 505 506def convert_sun_emitter(scene, assembly, emitter_name, element): 507 sun_params = {} 508 509 turbidity = element.find("float[@name='turbidity']") 510 if turbidity is not None: 511 sun_params["turbidity"] = float(turbidity.attrib["value"]) - 2.0 512 else: 513 sun_params["turbidity"] = 1.0 514 515 scale = element.find("float[@name='scale']") 516 if scale is not None: 517 sun_params["radiance_multiplier"] = float(scale.attrib["value"]) 518 519 sun = asr.Light("sun_light", "sun_light", sun_params) 520 521 sun_direction = element.find("vector[@name='sunDirection']") 522 if sun_direction is not None: 523 from_direction = asr.Vector3d(0.0, 0.0, 1.0) 524 to_direction = asr.Vector3d(get_vector(sun_direction)) 525 sun.set_transform( 526 asr.Transformd( 527 asr.Matrix4d.make_rotation( 528 asr.Quaterniond.make_rotation(from_direction, to_direction)))) 529 530 assembly.lights().insert(sun) 531 532 533def convert_sunsky_emitter(scene, assembly, emitter_name, element): 534 turbidity_element = element.find("float[@name='turbidity']") 535 if turbidity_element is not None: 536 turbidity = float(turbidity_element.attrib["value"]) - 2.0 537 else: 538 turbidity = 1.0 539 540 # Sky. 541 sun_direction = element.find("vector[@name='sunDirection']") 542 if sun_direction is not None: 543 d = get_vector(sun_direction) 544 sun_theta = math.acos(d[1]) 545 sun_phi = math.atan2(d[2], d[0]) 546 else: 547 sun_theta = 0.0 548 sun_phi = 0.0 549 env_edf = asr.EnvironmentEDF("hosek_environment_edf", "environment_edf", { 550 "sun_theta": math.degrees(sun_theta), 551 "sun_phi": math.degrees(sun_phi), 552 "turbidity": turbidity 553 }) 554 scene.environment_edfs().insert(env_edf) 555 scene.environment_shaders().insert(asr.EnvironmentShader("edf_environment_shader", "environment_shader", { 556 "environment_edf": 'environment_edf' 557 })) 558 scene.set_environment(asr.Environment("environment", { 559 "environment_edf": "environment_edf", 560 "environment_shader": "environment_shader" 561 })) 562 563 # Sun. 564 sun_params = {"environment_edf": "environment_edf", "turbidity": turbidity} 565 sun_scale = element.find("float[@name='sunScale']") 566 if sun_scale is not None: 567 sun_params["radiance_multiplier"] = float(sun_scale.attrib["value"]) 568 sun = asr.Light("sun_light", "sun_light", sun_params) 569 assembly.lights().insert(sun) 570 571 572def convert_emitter(scene, assembly, emitter_name, element): 573 type = element.attrib["type"] 574 if type == "area": 575 convert_area_emitter(assembly, emitter_name, element) 576 elif type == "constant": 577 convert_constant_emitter(scene, emitter_name, element) 578 elif type == "envmap": 579 convert_envmap_emitter(scene, emitter_name, element) 580 elif type == "sun": 581 convert_sun_emitter(scene, assembly, emitter_name, element) 582 elif type == "sunsky": 583 convert_sunsky_emitter(scene, assembly, emitter_name, element) 584 else: 585 warning("Don't know how to convert emitter of type {0}".format(type)) 586 587 588def convert_material(assembly, material_name, material_params, element): 589 if material_name is None and "id" in element.attrib: 590 material_name = element.attrib["id"] 591 592 type = element.attrib["type"] 593 594 # Two-sided adapter. 595 if type == "twosided": 596 set_private_param(material_params, "two_sided", True) 597 return convert_material(assembly, material_name, material_params, element.find("bsdf")) 598 599 # Bump mapping adapter. 600 # todo: add bump mapping support. 601 if type == "bumpmap": 602 return convert_material(assembly, material_name, material_params, element.find("bsdf")) 603 604 # Opacity adapter. 605 if type == "mask": 606 opacity_element = element.find("*[@name='opacity']") 607 opacity = 0.5 608 if opacity_element is not None: 609 if opacity_element.tag == "rgb": 610 opacity_rgb = get_rgb(opacity_element) 611 if opacity_rgb[0] == opacity_rgb[1] and opacity_rgb[0] == opacity_rgb[2]: 612 opacity = opacity_rgb[0] 613 else: 614 warning("Colored opacity not supported, using average opacity") 615 opacity = (opacity_rgb[0] + opacity_rgb[1] + opacity_rgb[2]) / 3 616 else: 617 warning("Textured opacity not supported") 618 material_params["alpha_map"] = opacity 619 return convert_material(assembly, material_name, material_params, element.find("bsdf")) 620 621 # BSDF. 622 bsdf_name = "{0}_bsdf".format(material_name) 623 if type == "diffuse": 624 convert_diffuse_bsdf(assembly, bsdf_name, element) 625 elif type == "roughdiffuse": 626 convert_roughdiffuse_bsdf(assembly, bsdf_name, element) 627 elif type == "plastic": 628 convert_plastic_bsdf(assembly, bsdf_name, element) 629 elif type == "roughplastic": 630 convert_roughplastic_bsdf(assembly, bsdf_name, element) 631 elif type == "conductor": 632 convert_conductor_bsdf(assembly, bsdf_name, element) 633 elif type == "roughconductor": 634 convert_roughconductor_bsdf(assembly, bsdf_name, element) 635 elif type == "dielectric": 636 set_private_param(material_params, "two_sided", True) 637 convert_dielectric_bsdf(assembly, bsdf_name, element) 638 elif type == "roughdielectric": 639 set_private_param(material_params, "two_sided", True) 640 convert_roughdielectric_bsdf(assembly, bsdf_name, element) 641 elif type == "thindielectric": 642 set_private_param(material_params, "two_sided", True) 643 set_private_param(material_params, "thin_dielectric", True) 644 convert_dielectric_bsdf(assembly, bsdf_name, element) 645 else: 646 warning("Don't know how to convert BSDF of type {0}".format(type)) 647 return 648 649 # Hack: force light-emitting materials to be single-sided. 650 if "edf" in material_params: 651 set_private_param(material_params, "two_sided", False) 652 653 # Material. 654 material_params["bsdf"] = bsdf_name 655 material_params["surface_shader"] = "physical_surface_shader" 656 assembly.materials().insert(asr.Material("generic_material", material_name, material_params)) 657 658 659def process_shape_material(scene, assembly, instance_name, element): 660 material = None 661 662 # Material reference. 663 ref_element = element.find("ref") 664 if ref_element is not None: 665 material_name = ref_element.attrib["id"] 666 material = assembly.materials().get_by_name(material_name) 667 668 # Embedded material (has priority over the referenced material). 669 bsdf_element = element.find("bsdf") 670 if bsdf_element is not None: 671 material_name = "{0}_material".format(instance_name) 672 convert_material(assembly, material_name, {}, bsdf_element) 673 material = assembly.materials().get_by_name(material_name) 674 675 # Embedded emitter (we suppose it's an area emitter). 676 emitter_element = element.find("emitter") 677 if emitter_element is not None: 678 edf_name = "{0}_edf".format(instance_name) 679 convert_emitter(scene, assembly, edf_name, emitter_element) 680 681 material_params = material.get_parameters() 682 material_params["edf"] = edf_name 683 684 # Hack: force light-emitting materials to be single-sided. 685 set_private_param(material_params, "two_sided", False) 686 687 material_name = make_unique_name(instance_name + "_material", assembly.materials()) 688 material = asr.Material("generic_material", material_name, material_params) 689 assembly.materials().insert(material) 690 material = assembly.materials().get_by_name(material_name) 691 692 return material.get_name() if material is not None else None 693 694 695def make_object_instance(assembly, object, material_name, transform): 696 instance_name = "{0}_inst".format(object.get_name()) 697 698 front_mappings = {} 699 back_mappings = {} 700 instance_params = {} 701 702 if material_name is not None: 703 material = assembly.materials().get_by_name(material_name) 704 two_sided = get_private_param(material.get_parameters(), "two_sided", False) if material is not None else False 705 thin_dielectric = get_private_param(material.get_parameters(), "thin_dielectric", False) if material is not None else False 706 707 slots = object.material_slots() 708 if len(slots) == 0: 709 slots = ["default"] 710 711 front_mappings = dict([(slot, material_name) for slot in slots]) 712 back_mappings = front_mappings if two_sided else {} 713 714 if thin_dielectric: 715 instance_params = {"visibility": {"shadow": False}} 716 717 return asr.ObjectInstance(instance_name, instance_params, object.get_name(), transform, front_mappings, back_mappings) 718 719 720def make_new_object_name(assembly): 721 return "object_{0}".format(len(assembly.objects())) 722 723 724def convert_obj_shape(project, scene, assembly, element): 725 # Read OBJ file from disk and create objects. 726 object_name = make_new_object_name(assembly) 727 filepath = element.find("string[@name='filename']").attrib["value"] 728 objects = asr.MeshObjectReader.read(project.get_search_paths(), object_name, {"filename": filepath}) 729 730 # Instance transform. 731 matrix = get_matrix(element.find("transform[@name='toWorld']/matrix")) 732 transform = asr.Transformd(matrix) 733 734 # Instance material. 735 material_name = process_shape_material(scene, assembly, object_name, element) 736 737 for object in objects: 738 instance = make_object_instance(assembly, object, material_name, transform) 739 assembly.object_instances().insert(instance) 740 assembly.objects().insert(object) 741 742 743def convert_rectangle_shape(scene, assembly, element): 744 # Object. 745 object_name = make_new_object_name(assembly) 746 object = asr.create_primitive_mesh(object_name, { 747 "primitive": "grid", 748 "resolution_u": 1, 749 "resolution_v": 1, 750 "width": 2.0, 751 "height": 2.0 752 }) 753 754 # Instance transform. 755 matrix = get_matrix(element.find("transform[@name='toWorld']/matrix")) 756 rotx = asr.Matrix4d.make_rotation(asr.Vector3d(1.0, 0.0, 0.0), math.radians(90.0)) 757 matrix = matrix * rotx 758 transform = asr.Transformd(matrix) 759 760 # Instance material. 761 material_name = process_shape_material(scene, assembly, object_name, element) 762 763 instance = make_object_instance(assembly, object, material_name, transform) 764 assembly.object_instances().insert(instance) 765 assembly.objects().insert(object) 766 767 768def convert_disk_shape(scene, assembly, element): 769 # Radius. 770 radius_element = element.find("float[@name='radius']") 771 radius = float(radius_element.attrib["value"]) if radius_element is not None else 1.0 772 773 # Object. 774 object_name = make_new_object_name(assembly) 775 object = asr.create_primitive_mesh(object_name, { 776 "primitive": "disk", 777 "resolution_u": 1, 778 "resolution_v": 32, 779 "radius": radius 780 }) 781 782 # Instance transform. 783 matrix = get_matrix(element.find("transform[@name='toWorld']/matrix")) 784 rotx = asr.Matrix4d.make_rotation(asr.Vector3d(1.0, 0.0, 0.0), math.radians(90.0)) 785 matrix = matrix * rotx 786 transform = asr.Transformd(matrix) 787 788 # Instance material. 789 material_name = process_shape_material(scene, assembly, object_name, element) 790 791 instance = make_object_instance(assembly, object, material_name, transform) 792 assembly.object_instances().insert(instance) 793 assembly.objects().insert(object) 794 795 796def convert_sphere_shape(scene, assembly, element): 797 # Radius. 798 radius_element = element.find("float[@name='radius']") 799 radius = float(radius_element.attrib["value"]) if radius_element is not None else 1.0 800 801 # Center. 802 center_element = element.find("point[@name='center']") 803 center = asr.Vector3d(get_vector(center_element)) if center_element is not None else asr.Vector3d(0.0) 804 805 # Object. 806 object_name = make_new_object_name(assembly) 807 object = asr.create_primitive_mesh(object_name, { 808 "primitive": "sphere", 809 "resolution_u": 32, 810 "resolution_v": 16, 811 "radius": radius 812 }) 813 814 # Instance transform. 815 matrix = asr.Matrix4d.make_translation(center) 816 matrix_element = element.find("transform[@name='toWorld']/matrix") 817 if matrix_element is not None: 818 # todo: no idea what is the right multiplication order, untested. 819 matrix = matrix * get_matrix(matrix_element) 820 transform = asr.Transformd(matrix) 821 822 # Instance material. 823 material_name = process_shape_material(scene, assembly, object_name, element) 824 825 instance = make_object_instance(assembly, object, material_name, transform) 826 assembly.object_instances().insert(instance) 827 assembly.objects().insert(object) 828 829 830def convert_cube_shape(scene, assembly, element): 831 # Object. 832 object_name = make_new_object_name(assembly) 833 object = asr.create_primitive_mesh(object_name, {"primitive": "cube"}) 834 835 # Instance transform. 836 matrix_element = element.find("transform[@name='toWorld']/matrix") 837 matrix = get_matrix(matrix_element) if matrix_element is not None else asr.Matrix4d.identity() 838 transform = asr.Transformd(matrix) 839 840 # Instance material. 841 material_name = process_shape_material(scene, assembly, object_name, element) 842 843 instance = make_object_instance(assembly, object, material_name, transform) 844 assembly.object_instances().insert(instance) 845 assembly.objects().insert(object) 846 847 848def convert_shape(project, scene, assembly, element): 849 type = element.attrib["type"] 850 if type == "obj": 851 convert_obj_shape(project, scene, assembly, element) 852 elif type == "rectangle": 853 convert_rectangle_shape(scene, assembly, element) 854 elif type == "disk": 855 convert_disk_shape(scene, assembly, element) 856 elif type == "sphere": 857 convert_sphere_shape(scene, assembly, element) 858 elif type == "cube": 859 convert_cube_shape(scene, assembly, element) 860 else: 861 warning("Don't know how to convert shape of type {0}".format(type)) 862 863 864def convert_scene(project, scene, assembly, element): 865 for child in element: 866 if child.tag == "integrator": 867 convert_integrator(project, child) 868 elif child.tag == "sensor": 869 convert_sensor(project, scene, child) 870 elif child.tag == "bsdf": 871 convert_material(assembly, None, {}, child) 872 elif child.tag == "shape": 873 convert_shape(project, scene, assembly, child) 874 elif child.tag == "emitter": 875 convert_emitter(scene, assembly, None, child) 876 877 878def convert(tree): 879 project = asr.Project("project") 880 881 # Search paths. 882 paths = project.get_search_paths() 883 paths.append("models") 884 paths.append("textures") 885 project.set_search_paths(paths) 886 887 # Add default configurations to the project. 888 project.add_default_configurations() 889 890 # Enable caustics. 891 project.configurations().get_by_name("final").insert_path("pt.enable_caustics", True) 892 project.configurations().get_by_name("interactive").insert_path("pt.enable_caustics", True) 893 894 # Create a scene. 895 scene = asr.Scene() 896 897 # Create an assembly. 898 assembly = asr.Assembly("assembly") 899 assembly.surface_shaders().insert(asr.SurfaceShader("physical_surface_shader", "physical_surface_shader")) 900 901 # Convert the Mitsuba scene. 902 convert_scene(project, scene, assembly, tree.getroot()) 903 904 # Create an instance of the assembly. 905 assembly_inst = asr.AssemblyInstance("assembly_inst", {}, assembly.get_name()) 906 assembly_inst.transform_sequence().set_transform(0.0, asr.Transformd(asr.Matrix4d.identity())) 907 scene.assembly_instances().insert(assembly_inst) 908 909 # Insert the assembly into the scene. 910 scene.assemblies().insert(assembly) 911 912 # Bind the scene to the project. 913 project.set_scene(scene) 914 915 return project 916 917 918# ------------------------------------------------------------------------------------------------- 919# Entry point. 920# ------------------------------------------------------------------------------------------------- 921 922def main(): 923 parser = argparse.ArgumentParser(description="convert Mitsuba scenes to appleseed format.") 924 parser.add_argument("input_file", metavar="input-file", help="Mitsuba scene (*.xml)") 925 parser.add_argument("output_file", metavar="output-file", help="appleseed scene (*.appleseed)") 926 args = parser.parse_args() 927 928 # Create a log target that outputs to stderr, and binds it to the renderer's global logger. 929 # Eventually you will want to redirect log messages to your own target. 930 # For this you will need to subclass appleseed.ILogTarget. 931 log_target = asr.ConsoleLogTarget(sys.stderr) 932 933 # It is important to keep log_target alive, as the global logger does not 934 # take ownership of it. In this example, we do that by removing the log target 935 # when no longer needed, at the end of this function. 936 asr.global_logger().add_target(log_target) 937 asr.global_logger().set_verbosity_level(asr.LogMessageCategory.Warning) 938 939 tree = ElementTree() 940 try: 941 tree.parse(args.input_file) 942 except IOError: 943 fatal("Failed to load {0}".format(args.input_file)) 944 945 # Make asset paths in the Mitsuba file relative to the Mitsuba file itself. 946 for child in tree.getroot(): 947 filepath = child.find("string[@name='filename']") 948 if filepath is not None: 949 filepath.attrib["value"] = os.path.join(os.path.dirname(args.input_file), filepath.attrib["value"]) 950 951 project = convert(tree) 952 953 asr.ProjectFileWriter().write(project, args.output_file, 954 asr.ProjectFileWriterOptions.OmitHandlingAssetFiles) 955 956if __name__ == '__main__': 957 main() 958