diff --git a/src/libcore/xml.cpp b/src/libcore/xml.cpp index 70a4ea34e..e00cbaae5 100644 --- a/src/libcore/xml.cpp +++ b/src/libcore/xml.cpp @@ -260,14 +260,22 @@ struct XMLParseContext { Transform4f transform; size_t id_counter = 0; bool parallelize; + bool load_permissive = false; + bool upgrade_approximate = false; + bool data_modified = false; ColorMode color_mode; - XMLParseContext(const std::string &variant) : variant(variant) { + XMLParseContext(const std::string &variant, ParameterList const& params) : variant(variant) { color_mode = MTS_INVOKE_VARIANT(variant, variant_to_color_mode); /* Don't load the scene in parallel when running in GPU mode (The Enoki CUDA backend is currently not multi-threaded) */ parallelize = !MTS_INVOKE_VARIANT(variant, check_cuda); + + for (auto& p : params) { + load_permissive |= (p.first == "__load_permissive"); + upgrade_approximate |= (p.first == "__upgrade_approximate"); + } } std::string variant; @@ -340,7 +348,7 @@ Vector3f parse_vector(XMLSource &src, pugi::xml_node &node, Float def_val = 0.f) } } -void upgrade_tree(XMLSource &src, pugi::xml_node &node, const Version &version) { +void upgrade_tree(XMLSource &src, pugi::xml_node &node, const Version &version, bool approximate) { if (version == Version(MTS_VERSION_MAJOR, MTS_VERSION_MINOR, MTS_VERSION_PATCH)) return; @@ -349,8 +357,11 @@ void upgrade_tree(XMLSource &src, pugi::xml_node &node, const Version &version) if (version < Version(2, 0, 0)) { // Upgrade all attribute names from camelCase to underscore_case - for (pugi::xpath_node result: node.select_nodes("//@name")) { - pugi::xml_attribute name_attrib = result.attribute(); + for (pugi::xpath_node result: node.select_nodes("//*[@name]")) { + pugi::xml_node n = result.node(); + if (std::strcmp(n.name(), "default") == 0) + continue; + pugi::xml_attribute name_attrib = n.attribute("name"); std::string name = name_attrib.value(); for (size_t i = 0; i < name.length() - 1; ++i) { if (std::islower(name[i]) && std::isupper(name[i + 1])) { @@ -366,6 +377,159 @@ void upgrade_tree(XMLSource &src, pugi::xml_node &node, const Version &version) } for (pugi::xpath_node result: node.select_nodes("//lookAt")) result.node().set_name("lookat"); + // automatically rename reserved identifiers + for (pugi::xpath_node result: node.select_nodes("//@id")) { + pugi::xml_attribute id_attrib = result.attribute(); + char const* val = id_attrib.value(); + if (val && val[0] == '_') { + std::string new_id = std::string("ID") + val + "__UPGR"; + Log(Warn, "Changing identifier: \"%s\" -> \"%s\"", val, new_id.c_str()); + id_attrib = new_id.c_str(); + } + } + // renamed features + for (pugi::xpath_node result: node.select_nodes("//bsdf[@type='bump']")) + result.node().attribute("type") = "bumpmap"; + // approximate unsupported features + if (approximate) { + for (pugi::xpath_node result: node.select_nodes("//bsdf[@type='phong' or string/@value='phong']")) { + pugi::xml_node n = result.node(); + pugi::xml_attribute at = n.attribute("type"); + if (std::strcmp(at.value(), "phong") == 0) { + Log(Warn, "Changing phong -> rough plastic: \"%s\"", n.attribute("id").value()); + at = "roughplastic"; + } else { + Log(Warn, "Changing %s -> beckmann: \"%s\"", at.value(), n.attribute("id").value()); + for (pugi::xpath_node result: n.select_nodes("string/@value[.='phong']")) + result.attribute() = "beckmann"; + } + for (pugi::xpath_node result: n.select_nodes("float[@name='exponent']")) { + pugi::xml_node nv = result.node(); + Float e = stof(nv.attribute("value").value()); + Float alpha = std::sqrt(2 / (2+e)); + nv.attribute("name") = "alpha"; + nv.attribute("value") = std::to_string(alpha).c_str(); + } + } + for (pugi::xpath_node result: node.select_nodes("//bsdf[@type='mixturebsdf']")) { + pugi::xml_node n = result.node(); + Log(Warn, "Changing mixturebsdf -> blendbsdf: \"%s\"", n.attribute("id").value()); + n.attribute("type") = "blendbsdf"; + for (pugi::xpath_node result: n.select_nodes("string[@name='weights']")) { + pugi::xml_node wn = result.node(); + std::string val = string::trim( wn.attribute("value").value() ); + size_t sp = val.find_last_of(" \t,"); + if (sp != val.npos) + // note: last value ok, load fails implcitly for more than 2 bsdfs + val.erase(val.begin(), val.begin() + (sp + 1)); + wn.attribute("value") = val.c_str(); + wn.attribute("name") = "weight"; + wn.set_name("float"); + } + } + for (pugi::xpath_node result: node.select_nodes("//bsdf[@type='coating' or @type='roughcoating']")) { + pugi::xml_node n = result.node(); + char const* stype = strstr(n.attribute("type").value(), "rough") ? "roughplastic" : "plastic"; + Log(Warn, "Changing coating -> blended %s: \"%s\"", stype, n.attribute("id").value()); + pugi::xml_node pn = n.append_child("bsdf"); + pn.append_attribute("type") = stype; + for (pugi::xpath_node result: n.select_nodes("*[not(self::bsdf)]")) + pn.append_move(result.node()); + n.attribute("type") = "blendbsdf"; + pugi::xml_node wn = n.append_child("float"); + wn.append_attribute("name") = "weight"; + wn.append_attribute("value") = "0.04"; + } + for (pugi::xpath_node result: node.select_nodes("//bsdf[@type='roughdiffuse']")) { + pugi::xml_node n = result.node(); + Log(Warn, "Changing rough diffuse -> diffuse: \"%s\"", n.attribute("id").value()); + n.attribute("type") = "diffuse"; + } + for (pugi::xpath_node result: node.select_nodes("//bsdf[@type='bumpmap' or @type='normalmap']")) { + pugi::xml_node n = result.node(); + char const* id = n.attribute("id").value(); + Log(Warn, "Bump/normalmaps currently unsupported -> bypassing! \"%s\"", id); + for (pugi::xpath_node result: n.select_nodes("bsdf")) { + pugi::xml_node nn = result.node(); + if (id && id[0]) { + nn.remove_attribute("id"); + nn.append_attribute("id") = id; + } + n.parent().insert_move_before(nn, n); + } + n.parent().remove_child(n); + } + for (pugi::xpath_node result: node.select_nodes("//shape[@type='instance']")) { + pugi::xml_node n = result.node(); + Log(Warn, "Unrolling instance -> shapes! \"%s\"", n.attribute("id").value()); + int shapeCtr = 0; + for (pugi::xpath_node result: n.select_nodes("ref/@id")) { + char const* shapegroup_id = result.attribute().value(); + std::string group_selector = std::string("//shape[@id='") + shapegroup_id + "' and @type='shapegroup']"; + pugi::xml_node sg = node.select_node(group_selector.c_str()).node(); + if (!sg) Throw("Unknown shape group \"%s\" referenced", shapegroup_id); + if (!sg.attribute("was_referenced")) + sg.append_attribute("was_referenced") = "1"; + // clone shapes from shape group + for (pugi::xpath_node result: sg.select_nodes("shape")) { + pugi::xml_node sn = n.parent().insert_copy_before(result.node(), n); + ++shapeCtr; + // concat instance transformations (XML parser already chains node effects) + for (pugi::xpath_node result: n.select_nodes("transform")) { + pugi::xml_node itn = result.node(); + char const* transform_name = itn.attribute("name").value(); + std::string transform_selector = std::string("transform[@name='") + transform_name + "']"; + pugi::xml_node stn = sn.select_node(transform_selector.c_str()).node(); + if (!stn) { + stn = sn.append_child("transform"); + stn.append_attribute("name") = transform_name; + } + for (pugi::xml_node t: itn.children()) + stn.append_copy(t); + } + } + } + Log(shapeCtr > 0 ? Info : Warn, "Instantiated %d shapes", shapeCtr); + n.parent().remove_child(n); + } + for (pugi::xpath_node result: node.select_nodes("//shape[@type='shapegroup']")) { + pugi::xml_node n = result.node(); + if (!n.attribute("was_referenced")) + Log(Warn, "Unreferenced shape group: \"%s\"", n.attribute("id").value()); + n.parent().remove_child(n); + } + for (pugi::xpath_node result: node.select_nodes("//srgb")) { + Log(Warn, "Changing -> "); + result.node().set_name("rgb"); + } + for (pugi::xpath_node result: node.select_nodes("//spectrum[@value[not(contains(.,':'))]]")) { + pugi::xml_node n = result.node(); + if (string::tokenize(n.attribute("value").value()).size() > 1) { + Log(Warn, "Changing w/o wavelengths -> "); + n.set_name("rgb"); + } + } + } + // changed parameters + for (pugi::xpath_node result: node.select_nodes("//bsdf[@type='diffuse']/*/@name[.='diffuse_reflectance']")) + result.attribute() = "reflectance"; + for (pugi::xpath_node result: node.select_nodes("//rgb/@value[starts-with(.,'#')]")) { + pugi::xml_attribute a = result.attribute(); + std::string val = string::trim( a.value() ); + Color3f color(0); + if (val.size() == 7) + color = Color3f( (float) std::stoul(val.substr(1, 2), nullptr, 16) / 255.0f, + (float) std::stoul(val.substr(3, 2), nullptr, 16) / 255.0f, + (float) std::stoul(val.substr(5, 2), nullptr, 16) / 255.0f ); + else if (val.size() == 7) + color = Color3f( (float) std::stoul(val.substr(1, 1), nullptr, 16) / 15.0f, + (float) std::stoul(val.substr(2, 1), nullptr, 16) / 15.0f, + (float) std::stoul(val.substr(3, 1), nullptr, 16) / 15.0f ); + else + Throw("Invalid color code \"%s\"", val.c_str()); + val = std::to_string(color.r()) + ' ' + std::to_string(color.g()) + ' ' + std::to_string(color.b()); + a = val.c_str(); + } // Update 'uoffset', 'voffset', 'uscale', 'vscale' to transform block for (pugi::xpath_node result : node.select_nodes( @@ -485,7 +649,8 @@ static std::pair parse_xml(XMLSource &src, XMLParseCon } catch (const std::exception &) { src.throw_error(node, "could not parse version number \"%s\"", version_attr.value()); } - upgrade_tree(src, node, version); + upgrade_tree(src, node, version, ctx.upgrade_approximate); + ctx.data_modified |= src.modified; node.remove_attribute(version_attr); } @@ -643,7 +808,8 @@ static std::pair parse_xml(XMLSource &src, XMLParseCon } catch (const std::exception &) { nested_src.throw_error(*doc.begin(), "could not parse version number \"%s\"", version_attr_incl.value()); } - upgrade_tree(nested_src, *doc.begin(), version); + upgrade_tree(nested_src, *doc.begin(), version, ctx.upgrade_approximate); + ctx.data_modified |= nested_src.modified; doc.begin()->remove_attribute(version_attr_incl); } @@ -1064,9 +1230,10 @@ static ref instantiate_node(XMLParseContext &ctx, const std::string &id) inst.object = PluginManager::instance()->create_object(props, inst.class_); } catch (const std::exception &e) { Throw("Error while loading \"%s\" (near %s): could not instantiate " - "%s plugin of type \"%s\": %s", inst.src_id, inst.offset(inst.location), + "%s plugin of type \"%s\": %s%s", inst.src_id, inst.offset(inst.location), string::to_lower(inst.class_->name()), props.plugin_name(), - e.what()); + e.what(), + ctx.data_modified && !ctx.upgrade_approximate ? " (try -D__upgrade_approximate=1)" : ""); } auto unqueried = props.unqueried(); @@ -1074,20 +1241,24 @@ static ref instantiate_node(XMLParseContext &ctx, const std::string &id) for (auto &v : unqueried) { if (props.type(v) == Properties::Type::Object) { const auto &obj = props.object(v); - Throw("Error while loading \"%s\" (near %s): unreferenced " - "object %s (within %s of type \"%s\")", + Log(ctx.load_permissive ? Warn : Error, + "Error while loading \"%s\" (near %s): unreferenced " + "object %s (within %s of type \"%s\")%s", inst.src_id, inst.offset(inst.location), obj, string::to_lower(inst.class_->name()), - inst.props.plugin_name()); + inst.props.plugin_name(), + !ctx.load_permissive ? " (try -D__load_permissive=1)" : ""); } else { v = "\"" + v + "\""; } } - Throw("Error while loading \"%s\" (near %s): unreferenced %s " - "%s in %s plugin of type \"%s\"", + Log(ctx.load_permissive ? Warn : Error, + "Error while loading \"%s\" (near %s): unreferenced %s " + "%s in %s plugin of type \"%s\"%s", inst.src_id, inst.offset(inst.location), unqueried.size() > 1 ? "properties" : "property", unqueried, - string::to_lower(inst.class_->name()), props.plugin_name()); + string::to_lower(inst.class_->name()), props.plugin_name(), + !ctx.load_permissive ? " (try -D__load_permissive=1)" : ""); } return inst.object; } @@ -1111,7 +1282,7 @@ ref load_string(const std::string &string, const std::string &variant, src.offset(result.offset), result.description()); pugi::xml_node root = doc.document_element(); - detail::XMLParseContext ctx(variant); + detail::XMLParseContext ctx(variant, param); Properties prop; size_t arg_counter; // Unused auto scene_id = detail::parse_xml(src, ctx, root, Tag::Invalid, prop, @@ -1145,7 +1316,7 @@ ref load_file(const fs::path &filename_, const std::string &variant, pugi::xml_node root = doc.document_element(); - detail::XMLParseContext ctx(variant); + detail::XMLParseContext ctx(variant, param); Properties prop; size_t arg_counter = 0; // Unused auto scene_id = detail::parse_xml(src, ctx, root, Tag::Invalid, prop,