diff --git a/documents/Specification/MaterialX.Specification.md b/documents/Specification/MaterialX.Specification.md
index 29049205c2..afa69b006b 100644
--- a/documents/Specification/MaterialX.Specification.md
+++ b/documents/Specification/MaterialX.Specification.md
@@ -581,14 +581,14 @@ Nodes are individual data generation or processing "blocks". Node functionality
Individual node elements have the form:
```xml
-
...additional input or token elements...
-
+
```
-where _nodecategory_ is the general "category" of the node (e.g. "image", "add" or "mix"), `name` (string, required) defines the name of this instance of the node, which must be unique within the scope it appears in, and `type` (string, required) specifies the MaterialX type (typically float, colorN, or vectorN) of the output of that node. If the application uses a different name for this instance of the node in the user interface, a `uiname` attribute may be added to the <_nodecategory_> element to indicate the name of the node as it appears to the user.
+where _nodecategory_ is the general "category" of the node (e.g. "image", "add" or "mix"), `name` (string, required) defines the name of this instance of the node, which must be unique within the scope it appears in, and `type` (string, required) specifies the MaterialX type (typically float, colorN, or vectorN) of the output of that node. If there are more than outputs for the node the output type is `multioutput`. If the application uses a different name for this instance of the node in the user interface, a `uiname` attribute may be added to the <_node_> element to indicate the name of the node as it appears to the user.
Node elements may optionally specify a `version` string attribute in "_major_[._minor_]" format, requesting that a specific version of that node's definition be used instead of the default version. Normally, the types of a node's inputs and outputs are sufficient to disambiguate which signature of the applicable version of a node is intended, but if necessary, a node instantiation may also declare a specific nodedef name to precisely define exactly which node signature is desired. Please refer to the [Custom Node Declaration NodeDef Elements](#custom-node-declaration-nodedef-elements) section below for further details.
diff --git a/python/Scripts/mxformat.py b/python/Scripts/mxformat.py
index c9c036769c..b5ad9d5017 100644
--- a/python/Scripts/mxformat.py
+++ b/python/Scripts/mxformat.py
@@ -24,6 +24,7 @@ def main():
parser.add_argument('-u', '--upgrade', dest='upgrade', action="store_true", help='Upgrade documents to the latest version of the standard.')
parser.add_argument('-v', '--validate', dest='validate', action="store_true", help='Perform MaterialX validation on documents after reformatting.')
parser.add_argument('-x', '--xml_syntax', dest='xml_syntax', action="store_true", help='Check XML syntax after reformatting.')
+ parser.add_argument('-n', '--node_types', dest='node_types', action="store_true", help='Write nodes instances using "node" type.')
parser.add_argument(dest="inputFolder", help="An input folder to scan for MaterialX documents.")
opts = parser.parse_args()
@@ -70,9 +71,14 @@ def main():
xml_syntax = opts.xml_syntax
if xml_syntax:
print(f'- Check XML syntax')
+ node_types = opts.node_types
+ writeOptions = mx.XmlWriteOptions()
+ if node_types:
+ writeOptions.writeNodeInstanceAsNode = True
+ print(f'- Write nodes using "node" type')
for (filename, doc) in validDocs.items():
if xml_syntax:
- xml_string = mx.writeToXmlString(doc)
+ xml_string = mx.writeToXmlString(doc, writeOptions)
errors = is_well_formed(xml_string)
if errors:
print(f'- Warning: Document {filename} is not well-formed XML: {errors}')
@@ -80,7 +86,8 @@ def main():
is_valid, errors = doc.validate()
if not is_valid:
print(f'- Warning: Document {filename} is invalid. Errors {errors}.')
- mx.writeToXmlFile(doc, filename)
+
+ mx.writeToXmlFile(doc, filename, writeOptions)
if opts.upgrade:
print('Upgraded %i documents to MaterialX v%i.%i' % (len(validDocs), mxVersion[0], mxVersion[1]))
diff --git a/resources/Materials/Examples/StandardSurface/standard_surface_marble_solid_node.mtlx b/resources/Materials/Examples/StandardSurface/standard_surface_marble_solid_node.mtlx
new file mode 100644
index 0000000000..4a26b39be2
--- /dev/null
+++ b/resources/Materials/Examples/StandardSurface/standard_surface_marble_solid_node.mtlx
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/Examples/StandardSurface/standard_surface_onyx_hextiled_node.mtlx b/resources/Materials/Examples/StandardSurface/standard_surface_onyx_hextiled_node.mtlx
new file mode 100644
index 0000000000..af36a04471
--- /dev/null
+++ b/resources/Materials/Examples/StandardSurface/standard_surface_onyx_hextiled_node.mtlx
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/Materials/TestSuite/pbrlib/multioutput/multishaderoutput_node.mtlx b/resources/Materials/TestSuite/pbrlib/multioutput/multishaderoutput_node.mtlx
new file mode 100644
index 0000000000..ef9f4878b9
--- /dev/null
+++ b/resources/Materials/TestSuite/pbrlib/multioutput/multishaderoutput_node.mtlx
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/MaterialXFormat/XmlIo.cpp b/source/MaterialXFormat/XmlIo.cpp
index 83e4721dc1..63839eec13 100644
--- a/source/MaterialXFormat/XmlIo.cpp
+++ b/source/MaterialXFormat/XmlIo.cpp
@@ -42,6 +42,7 @@ void documentToXml(DocumentPtr doc, xml_node& xmlRoot, const XmlWriteOptions* wr
elemStack.pop_back();
bool writeXIncludeEnable = writeOptions ? writeOptions->writeXIncludeEnable : true;
+ bool writeNodeInstanceAsNode = writeOptions ? writeOptions->writeNodeInstanceAsNode : false;
ElementPredicate elementPredicate = writeOptions ? writeOptions->elementPredicate : nullptr;
// Store attributes in XML.
@@ -49,6 +50,16 @@ void documentToXml(DocumentPtr doc, xml_node& xmlRoot, const XmlWriteOptions* wr
{
xmlNode.append_attribute(Element::NAME_ATTRIBUTE.c_str()) = elem->getName().c_str();
}
+
+ bool isNode = writeNodeInstanceAsNode && elem->isA();
+ if (isNode)
+ {
+ const string& category = elem->getCategory();
+ const string NODE_TYPE_ATTRIBUTE = "nodetype";
+ xml_attribute xmlAttr = xmlNode.append_attribute(NODE_TYPE_ATTRIBUTE.c_str());
+ xmlAttr.set_value(category.c_str());
+ }
+
for (const string& attrName : elem->getAttributeNames())
{
xml_attribute xmlAttr = xmlNode.append_attribute(attrName.c_str());
@@ -91,33 +102,49 @@ void documentToXml(DocumentPtr doc, xml_node& xmlRoot, const XmlWriteOptions* wr
}
}
- // Handle the interpretation of comments and newlines.
- if (child->getCategory() == CommentElement::CATEGORY)
+ isNode = writeNodeInstanceAsNode && child->isA();
+ if (isNode)
{
- xml_node xmlChild = xmlNode.append_child(node_comment);
- xmlChild.set_value(child->getAttribute(Element::DOC_ATTRIBUTE).c_str());
- continue;
+ // Push child data onto the stack.
+ const string NODE_CATEGORY = "node";
+ xml_node xmlChild = xmlNode.append_child(NODE_CATEGORY.c_str());
+ elemStack.emplace_back(child, xmlChild);
}
- if (child->getCategory() == NewlineElement::CATEGORY)
+ else
{
- xml_node xmlChild = xmlNode.append_child(node_newline);
- xmlChild.set_value("\n");
- continue;
- }
+ string category = child->getCategory();
- // Push child data onto the stack.
- xml_node xmlChild = xmlNode.append_child(child->getCategory().c_str());
- elemStack.emplace_back(child, xmlChild);
+ // Handle the interpretation of comments and newlines.
+ if (category == CommentElement::CATEGORY)
+ {
+ xml_node xmlChild = xmlNode.append_child(node_comment);
+ xmlChild.set_value(child->getAttribute(Element::DOC_ATTRIBUTE).c_str());
+ }
+ else if (category == NewlineElement::CATEGORY)
+ {
+ xml_node xmlChild = xmlNode.append_child(node_newline);
+ xmlChild.set_value("\n");
+ }
+ else
+ {
+ // Push child data onto the stack.
+ xml_node xmlChild = xmlNode.append_child(category.c_str());
+ elemStack.emplace_back(child, xmlChild);
+ }
+ }
}
}
}
void elementFromXml(const xml_node& xmlNode, ElementPtr elem, const XmlReadOptions* readOptions, int depth = 1)
{
+ const string NODETYPE_ATTRIBUTE = "nodetype";
+
// Store attributes in element.
for (const xml_attribute& xmlAttr : xmlNode.attributes())
{
- if (xmlAttr.name() != Element::NAME_ATTRIBUTE)
+ if (xmlAttr.name() != Element::NAME_ATTRIBUTE &&
+ xmlAttr.name() != NODETYPE_ATTRIBUTE)
{
elem->setAttribute(xmlAttr.name(), xmlAttr.value());
}
@@ -147,6 +174,12 @@ void elementFromXml(const xml_node& xmlNode, ElementPtr elem, const XmlReadOptio
throw ExceptionParseError("Maximum tree depth exceeded.");
}
+ // If name is "node" get category from attribute.
+ if (category == "node")
+ {
+ category = xmlChild.attribute(NODETYPE_ATTRIBUTE.c_str()).value();
+ }
+
// Create the child element.
ElementPtr child = elem->addChildOfCategory(category, name);
elementFromXml(xmlChild, child, readOptions, depth + 1);
@@ -280,7 +313,8 @@ XmlReadOptions::XmlReadOptions() :
//
XmlWriteOptions::XmlWriteOptions() :
- writeXIncludeEnable(true)
+ writeXIncludeEnable(true),
+ writeNodeInstanceAsNode(false)
{
}
diff --git a/source/MaterialXFormat/XmlIo.h b/source/MaterialXFormat/XmlIo.h
index 5c80ae8005..575b81383c 100644
--- a/source/MaterialXFormat/XmlIo.h
+++ b/source/MaterialXFormat/XmlIo.h
@@ -70,6 +70,10 @@ class MX_FORMAT_API XmlWriteOptions
/// XIncludes rather than explicit data. Defaults to true.
bool writeXIncludeEnable;
+ /// If true, node instances are written as "node" elements rather than
+ /// elements based on category. Defaults to false.
+ bool writeNodeInstanceAsNode;
+
/// If provided, this function will be used to exclude specific elements
/// (those returning false) from the write operation. Defaults to nullptr.
ElementPredicate elementPredicate;
diff --git a/source/MaterialXGraphEditor/Graph.cpp b/source/MaterialXGraphEditor/Graph.cpp
index 9f505375fa..9249bcfe51 100644
--- a/source/MaterialXGraphEditor/Graph.cpp
+++ b/source/MaterialXGraphEditor/Graph.cpp
@@ -4973,6 +4973,7 @@ void Graph::saveDocument(mx::FilePath filePath)
}
mx::XmlWriteOptions writeOptions;
+ writeOptions.writeNodeInstanceAsNode = true;
writeOptions.elementPredicate = getElementPredicate();
mx::writeToXmlFile(writeDoc, filePath, &writeOptions);
}
diff --git a/source/PyMaterialX/PyMaterialXFormat/PyXmlIo.cpp b/source/PyMaterialX/PyMaterialXFormat/PyXmlIo.cpp
index 9b4b32cf6a..b6bb671b13 100644
--- a/source/PyMaterialX/PyMaterialXFormat/PyXmlIo.cpp
+++ b/source/PyMaterialX/PyMaterialXFormat/PyXmlIo.cpp
@@ -24,6 +24,7 @@ void bindPyXmlIo(py::module& mod)
py::class_(mod, "XmlWriteOptions")
.def(py::init())
.def_readwrite("writeXIncludeEnable", &mx::XmlWriteOptions::writeXIncludeEnable)
+ .def_readwrite("writeNodeInstanceAsNode", &mx::XmlWriteOptions::writeNodeInstanceAsNode)
.def_readwrite("elementPredicate", &mx::XmlWriteOptions::elementPredicate);
mod.def("readFromXmlFileBase", &mx::readFromXmlFile,