diff --git a/resources/Materials/TestSuite/locale/numericformat.mtlx b/resources/Materials/TestSuite/locale/numericformat.mtlx new file mode 100644 index 0000000000..b9400e270c --- /dev/null +++ b/resources/Materials/TestSuite/locale/numericformat.mtlx @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/resources/Materials/TestSuite/locale/utf8.mtlx b/resources/Materials/TestSuite/locale/utf8.mtlx new file mode 100644 index 0000000000..a6a30f02d2 --- /dev/null +++ b/resources/Materials/TestSuite/locale/utf8.mtlx @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/source/MaterialXCore/Unit.cpp b/source/MaterialXCore/Unit.cpp index f55cdc108a..959f697b24 100644 --- a/source/MaterialXCore/Unit.cpp +++ b/source/MaterialXCore/Unit.cpp @@ -241,4 +241,70 @@ void UnitConverterRegistry::write(DocumentPtr doc) const } } +bool UnitConverterRegistry::convertToUnit(DocumentPtr doc, const string& unitType, const string& targetUnit) +{ + UnitTypeDefPtr unitTypeDef = doc->getUnitTypeDef(unitType); + UnitConverterPtr unitConverter = getUnitConverter(unitTypeDef); + + if (!unitTypeDef || !unitConverter) + { + return false; + } + + bool convertedUnits = false; + for (ElementPtr elem : doc->traverseTree()) + { + NodePtr pNode = elem->asA(); + if (!pNode || !pNode->getInputCount()) + { + continue; + } + for (InputPtr input : pNode->getInputs()) + { + const std::string type = input->getType(); + const ValuePtr value = input->getValue(); + if (value && input->hasUnit() && (input->getUnitType() == unitType) && value) + { + if (type == getTypeString()) + { + float originalval = value->asA(); + float convertedValue = unitConverter->convert(originalval, input->getUnit(), targetUnit); + input->setValue(convertedValue); + input->removeAttribute(ValueElement::UNIT_ATTRIBUTE); + input->removeAttribute(ValueElement::UNITTYPE_ATTRIBUTE); + convertedUnits = true; + } + else if (type == getTypeString()) + { + Vector2 originalval = value->asA(); + Vector2 convertedValue = unitConverter->convert(originalval, input->getUnit(), targetUnit); + input->setValue(convertedValue); + input->removeAttribute(ValueElement::UNIT_ATTRIBUTE); + input->removeAttribute(ValueElement::UNITTYPE_ATTRIBUTE); + convertedUnits = true; + } + else if (type == getTypeString()) + { + Vector3 originalval = value->asA(); + Vector3 convertedValue = unitConverter->convert(originalval, input->getUnit(), targetUnit); + input->setValue(convertedValue); + input->removeAttribute(ValueElement::UNIT_ATTRIBUTE); + input->removeAttribute(ValueElement::UNITTYPE_ATTRIBUTE); + convertedUnits = true; + } + else if (type == getTypeString()) + { + Vector4 originalval = value->asA(); + Vector4 convertedValue = unitConverter->convert(originalval, input->getUnit(), targetUnit); + input->setValue(convertedValue); + input->removeAttribute(ValueElement::UNIT_ATTRIBUTE); + input->removeAttribute(ValueElement::UNITTYPE_ATTRIBUTE); + convertedUnits = true; + } + } + } + } + return convertedUnits; +} + MATERIALX_NAMESPACE_END diff --git a/source/MaterialXCore/Unit.h b/source/MaterialXCore/Unit.h index f50101a1bf..26b2b0df58 100644 --- a/source/MaterialXCore/Unit.h +++ b/source/MaterialXCore/Unit.h @@ -195,6 +195,10 @@ class MX_CORE_API UnitConverterRegistry /// Create unit definitions in a document based on registered converters void write(DocumentPtr doc) const; + /// Convert input values which have a source unit to a given target unit. + /// Returns if any unit conversion occured. + bool convertToUnit(DocumentPtr doc, const string& unitType, const string& targetUnit); + private: UnitConverterRegistry(const UnitConverterRegistry&) = delete; UnitConverterRegistry() { } diff --git a/source/MaterialXCore/Util.cpp b/source/MaterialXCore/Util.cpp index a0fb1ff1f2..9805f4a7c9 100644 --- a/source/MaterialXCore/Util.cpp +++ b/source/MaterialXCore/Util.cpp @@ -91,6 +91,16 @@ StringVec splitString(const string& str, const string& sep) return split; } +string joinStrings(const StringVec& stringVec, const string& sep) +{ + string res; + for (const string& name : stringVec) + { + res = res.empty() ? name : res + sep + name; + } + return res; +} + string replaceSubstrings(string str, const StringMap& stringMap) { for (const auto& pair : stringMap) @@ -145,11 +155,7 @@ StringVec splitNamePath(const string& namePath) string createNamePath(const StringVec& nameVec) { - string res; - for (const string& name : nameVec) - { - res = res.empty() ? name : res + NAME_PATH_SEPARATOR + name; - } + string res = joinStrings(nameVec, NAME_PATH_SEPARATOR); return res; } diff --git a/source/MaterialXCore/Util.h b/source/MaterialXCore/Util.h index 8e201b37a3..a3f2e8a64c 100644 --- a/source/MaterialXCore/Util.h +++ b/source/MaterialXCore/Util.h @@ -35,6 +35,10 @@ MX_CORE_API string incrementName(const string& name); /// separator characters. MX_CORE_API StringVec splitString(const string& str, const string& sep); +/// Join a vector of substrings into a single string, placing the given +/// separator between each substring. +MX_CORE_API string joinStrings(const StringVec& strVec, const string& sep); + /// Apply the given substring substitutions to the input string. MX_CORE_API string replaceSubstrings(string str, const StringMap& stringMap); diff --git a/source/MaterialXCore/Value.cpp b/source/MaterialXCore/Value.cpp index f83fb81e63..ebf1346f8d 100644 --- a/source/MaterialXCore/Value.cpp +++ b/source/MaterialXCore/Value.cpp @@ -31,6 +31,7 @@ template using enable_if_std_vector_t = template void stringToData(const string& str, T& data) { std::stringstream ss(str); + ss.imbue(std::locale::classic()); if (!(ss >> data)) { throw ExceptionTypeError("Type mismatch in generic stringToData: " + str); @@ -94,6 +95,7 @@ template void stringToData(const string& str, enable_if_std_vector_t void dataToString(const T& data, string& str) { std::stringstream ss; + ss.imbue(std::locale::classic()); // Set float format and precision for the stream const Value::FloatFormat fmt = Value::getFloatFormat(); diff --git a/source/MaterialXTest/MaterialXFormat/XmlIo.cpp b/source/MaterialXTest/MaterialXFormat/XmlIo.cpp index d59e8d930d..fb2df02acf 100644 --- a/source/MaterialXTest/MaterialXFormat/XmlIo.cpp +++ b/source/MaterialXTest/MaterialXFormat/XmlIo.cpp @@ -227,3 +227,75 @@ TEST_CASE("Load content", "[xmlio]") mx::DocumentPtr nonExistentDoc = mx::createDocument(); REQUIRE_THROWS_AS(mx::readFromXmlFile(nonExistentDoc, "NonExistent.mtlx", mx::FileSearchPath(), &readOptions), mx::ExceptionFileMissing&); } + +TEST_CASE("Locale region testing", "[xmlio]") +{ + /// In the United States, the thousands separator is a comma, while in Germany it is a period. + /// Thus, one thousand twenty five is displayed as 1,025 in the United States and 1.025 in Germany. + /// + /// In a MaterialX document, a vector3 value of "1,1.5,2.0" should be interpreted as (1.0f, 1.5f, 2.0f). + + try + { + // Set locale to de + std::locale deLocale("de_DE"); + std::locale::global(deLocale); + } + catch (const std::runtime_error& e) + { + WARN("Unable to change locale " << e.what()); + return; + } + + mx::FilePath libraryPath("libraries/stdlib"); + mx::FilePath testPath("resources/Materials/TestSuite/locale"); + mx::FileSearchPath searchPath = libraryPath.asString() + + mx::PATH_LIST_SEPARATOR + + testPath.asString(); + + // Read the standard library. + std::vector libs; + for (const mx::FilePath& filename : libraryPath.getFilesInDirectory(mx::MTLX_EXTENSION)) + { + mx::DocumentPtr lib = mx::createDocument(); + mx::readFromXmlFile(lib, filename, searchPath); + libs.push_back(lib); + } + + // Read and validate each example document. + for (const mx::FilePath& filename : testPath.getFilesInDirectory(mx::MTLX_EXTENSION)) + { + mx::DocumentPtr doc = mx::createDocument(); + mx::readFromXmlFile(doc, filename, searchPath); + for (mx::DocumentPtr lib : libs) + { + doc->importLibrary(lib); + } + std::string message; + + bool docValid = doc->validate(&message); + if (!docValid) + { + WARN("[" + filename.asString() + "] " + message); + } + REQUIRE(docValid); + + // Traverse the document tree + int valueElementCount = 0; + int uiAttributeCount = 0; + for (mx::ElementPtr elem : doc->traverseTree()) + { + if (elem->isA()) + { + valueElementCount++; + if (elem->hasAttribute("uiname")) + { + REQUIRE(!elem->getAttribute("uiname").empty()); + uiAttributeCount++; + } + } + } + REQUIRE(valueElementCount > 0); + REQUIRE(uiAttributeCount > 0); + } +} diff --git a/source/PyMaterialX/PyMaterialXCore/PyUtil.cpp b/source/PyMaterialX/PyMaterialXCore/PyUtil.cpp index a646a5ecf1..190668a608 100644 --- a/source/PyMaterialX/PyMaterialXCore/PyUtil.cpp +++ b/source/PyMaterialX/PyMaterialXCore/PyUtil.cpp @@ -21,6 +21,7 @@ void bindPyUtil(py::module& mod) mod.def("isValidName", &mx::isValidName); mod.def("incrementName", &mx::incrementName); mod.def("splitString", &mx::splitString); + mod.def("joinStrings", &mx::joinStrings); mod.def("replaceSubstrings", &mx::replaceSubstrings); mod.def("stringEndsWith", &mx::stringEndsWith); mod.def("splitNamePath", &mx::splitNamePath); diff --git a/source/PyMaterialX/PyMaterialXFormat/PyXmlIo.cpp b/source/PyMaterialX/PyMaterialXFormat/PyXmlIo.cpp index 683a487931..1ac0c5ca7c 100644 --- a/source/PyMaterialX/PyMaterialXFormat/PyXmlIo.cpp +++ b/source/PyMaterialX/PyMaterialXFormat/PyXmlIo.cpp @@ -35,6 +35,12 @@ void bindPyXmlIo(py::module& mod) py::arg("doc"), py::arg("writeOptions") = nullptr); mod.def("prependXInclude", mx::prependXInclude); + mod.def("getEnvironmentPath", &mx::getEnvironmentPath, + py::arg("sep") = mx::PATH_LIST_SEPARATOR); + + mod.attr("PATH_LIST_SEPARATOR") = mx::PATH_LIST_SEPARATOR; + mod.attr("MATERIALX_SEARCH_PATH_ENV_VAR") = mx::MATERIALX_SEARCH_PATH_ENV_VAR; + py::register_exception(mod, "ExceptionParseError"); py::register_exception(mod, "ExceptionFileMissing"); }