Skip to content

Commit

Permalink
Add support for references in xml.load_dict()
Browse files Browse the repository at this point in the history
  • Loading branch information
Speierers committed May 6, 2020
1 parent 3f2dc35 commit b8c28c4
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 9 deletions.
46 changes: 46 additions & 0 deletions docs/src/python_interface/parsing_xml.rst
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,49 @@ The following example constructs a Mitsuba scene using :py:func:`mitsuba.core.xm
}
}
})
As in the XML scene description, it is possible to reference other objects in the `dict`, as
long as those a declared before the reference takes place in the dictionary. For this purpose,
you can specify a nested dictionary with ``"type":"ref"`` and an ``"id"`` entry. Objects can be
referenced using their ``key`` in the dictionary. It is also possible to reference an
object using it's ``id`` if one was defined.

.. code-block:: python
{
"type" : "scene",
# this BSDF can be referenced using its key "bsdf_id_0"
"bsdf_key_0" : {
"type" : "roughconductor"
},
"shape_0" : {
"type" : "sphere",
"mybsdf" : {
"type" : "ref",
"id" : "bsdf_key_0"
}
}
# this BSDF can be referenced using its key "bsdf_key_1" or its id "bsdf_id_1"
"bsdf_key_1" : {
"type" : "roughconductor",
"id" : "bsdf_id_1"
},
"shape_2" : {
"type" : "sphere",
"mybsdf" : {
"type" : "ref",
"id" : "bsdf_id_1"
}
},
"shape_3" : {
"type" : "sphere",
"mybsdf" : {
"type" : "ref",
"id" : "bsdf_key_1"
}
}
}
65 changes: 56 additions & 9 deletions src/libcore/python/xml_v.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
#include <mitsuba/core/transform.h>
#include <mitsuba/python/python.h>
#include <pybind11/numpy.h>
#include <map>

using Caster = py::object(*)(mitsuba::Object *);
extern Caster cast_object;

// Forward declaration
MTS_VARIANT ref<Object> load_dict(const py::dict& dict);
MTS_VARIANT ref<Object> load_dict(const py::dict& dict,
std::map<std::string, ref<Object>> &instances);

/// Shorthand notation for accessing the MTS_VARIANT string
#define GET_VARIANT() mitsuba::detail::get_variant<Float, Spectrum>()
Expand Down Expand Up @@ -54,7 +56,8 @@ MTS_PY_EXPORT(xml) {
m.def(
"load_dict",
[](const py::dict dict) {
return cast_object(load_dict<Float, Spectrum>(dict));
std::map<std::string, ref<Object>> instances;
return cast_object(load_dict<Float, Spectrum>(dict, instances));
},
"dict"_a,
R"doc(Load a Mitsuba scene or object from an Python dictionary
Expand All @@ -80,18 +83,19 @@ std::string get_type(const py::dict &dict) {
continue; \
}

MTS_VARIANT ref<Object> load_dict(const py::dict &dict) {
MTS_VARIANT ref<Object> load_dict(const py::dict &dict,
std::map<std::string, ref<Object>> &instances) {

std::string type = get_type(dict);
bool is_scene = (type == "scene");

const Class *class_;
if (type == "scene")
if (is_scene)
class_ = Class::for_name("Scene", GET_VARIANT());
else
class_ = PluginManager::instance()->get_plugin_class(type, GET_VARIANT());

bool within_emitter = (class_->parent()->name() == "Emitter");

bool within_emitter = (class_->parent()->alias() == "emitter");
Properties props(type);

for (auto& [k, value] : dict) {
Expand All @@ -100,6 +104,11 @@ MTS_VARIANT ref<Object> load_dict(const py::dict &dict) {
if (key == "type")
continue;

if (key == "id") {
props.set_id(value.cast<std::string>());
continue;
}

SET_PROPS(py::bool_, bool, set_bool);
SET_PROPS(py::int_, int64_t, set_long);
SET_PROPS(py::float_, Properties::Float, set_float);
Expand Down Expand Up @@ -130,7 +139,7 @@ MTS_VARIANT ref<Object> load_dict(const py::dict &dict) {
if (key2 == "value")
color = value2.cast<Properties::Color3f>();
else if (key2 != "type")
Throw("Unexpected item in rgb dictionary: %s", key2);
Throw("Unexpected key in rgb dictionary: %s", key2);
}
// Update the properties struct
ref<Object> obj = mitsuba::xml::detail::create_texture_from_rgb(
Expand Down Expand Up @@ -163,7 +172,7 @@ MTS_VARIANT ref<Object> load_dict(const py::dict &dict) {
Throw("Unexpected value type in spectrum dictionary: %s", value2);
}
} else if (key2 != "type") {
Throw("Unexpected item in spectrum dictionary: %s", key2);
Throw("Unexpected key in spectrum dictionary: %s", key2);
}
}
// Update the properties struct
Expand All @@ -176,7 +185,45 @@ MTS_VARIANT ref<Object> load_dict(const py::dict &dict) {
continue;
}

auto obj = load_dict<Float, Spectrum>(dict2);
// Nested dict with type == "ref" specify a reference to another
// object previously instanciated
if (type2 == "ref") {
if (is_scene)
Throw("Reference found at the scene level: %s", key);

for (auto& [k2, value2] : value.cast<py::dict>()) {
std::string key2 = k2.cast<std::string>();
if (key2 == "id") {
std::string id = value2.cast<std::string>();
if (instances.count(id) == 1)
props.set_object(key, instances[id]);
else
Throw("Referenced id \"%s\" not found: %s", id, key);
} else if (key2 != "type") {
Throw("Unexpected key in ref dictionary: %s", key2);
}
}
continue;
}

// Load the dictionary recursively
auto obj = load_dict<Float, Spectrum>(dict2, instances);

// Add instanced object to the instance map for later references
if (is_scene) {
// An object can be referenced using its key
if (instances.count(key) != 0)
Throw("%s has duplicate id: %s", key, key);
instances[key] = obj;

// An object can also be referenced using its "id" if it has one
std::string id = obj->id();
if (!id.empty() && id != key) {
if (instances.count(id) != 0)
Throw("%s has duplicate id: %s", key, id);
instances[id] = obj;
}
}
props.set_object(key, obj);
continue;
}
Expand Down
96 changes: 96 additions & 0 deletions src/libcore/tests/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,3 +627,99 @@ def test28_dict_unreferenced_attribute_error(variants_all_rgb):
"foo": 0.44
})
e.match("Unreferenced attribute")



def test29_dict_scene_reference(variants_all_rgb):
from mitsuba.core import xml

scene = xml.load_dict({
"type" : "scene",
# reference using its key
"bsdf1_key" : { "type" : "conductor" },
# reference using its key or its id
"bsdf2_key" : {
"type" : "roughdielectric",
"id" : "bsdf2_id"
},
"texture1_id" : { "type" : "checkerboard"},

"shape0" : {
"type" : "sphere",
"foo" : {
"type" : "ref",
"id" : "bsdf1_key"
}
},

"shape1" : {
"type" : "sphere",
"foo" : {
"type" : "ref",
"id" : "bsdf2_id"
}
},

"shape2" : {
"type" : "sphere",
"foo" : {
"type" : "ref",
"id" : "bsdf2_key"
}
},

"shape3" : {
"type" : "sphere",
"bsdf" : {
"type" : "diffuse",
"reflectance" : {
"type" : "ref",
"id" : "texture1_id"
}
}
},
})

bsdf1 = xml.load_dict({ "type" : "conductor" })
bsdf2 = xml.load_dict({
"type" : "roughdielectric",
"id" : "bsdf2_id"
})
texture = xml.load_dict({ "type" : "checkerboard"})
bsdf3 = xml.load_dict({
"type" : "diffuse",
"reflectance" : texture
})

assert str(scene.shapes()[0].bsdf()) == str(bsdf1)
assert str(scene.shapes()[1].bsdf()) == str(bsdf2)
assert str(scene.shapes()[2].bsdf()) == str(bsdf2)
assert str(scene.shapes()[3].bsdf()) == str(bsdf3)

with pytest.raises(Exception) as e:
scene = xml.load_dict({
"type" : "scene",
"shape0" : {
"type" : "sphere",
"id" : "shape_id",
},
"shape1" : {
"type" : "sphere",
"id" : "shape_id",
},
})
e.match("has duplicate id")

with pytest.raises(Exception) as e:
scene = xml.load_dict({
"type" : "scene",
"bsdf1_id" : { "type" : "conductor" },
"shape1" : {
"type" : "sphere",
"foo" : {
"type" : "ref",
"id" : "bsdf2_id"
}
},
})
e.match("""Referenced id "bsdf2_id" not found""")

0 comments on commit b8c28c4

Please sign in to comment.