Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions documents/Specification/MaterialX.Specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -581,14 +581,14 @@ Nodes are individual data generation or processing "blocks". Node functionality
Individual node elements have the form:

```xml
<nodecategory name="nodename" type="outputdatatype" [version="version"]
<node name="nodename" nodetype="nodecategory" type="outputdatatype" [version="version"]
[nodedef="nodedef_name"]>
<input name="inputname" type="type" [nodename="nodename"] [value="value"]/>
...additional input or token elements...
</nodecategory>
</node>
```

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, color<em>N</em>, or vector<em>N</em>) 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 &lt;_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, color<em>N</em>, or vector<em>N</em>) 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 &lt;_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.

Expand Down
11 changes: 9 additions & 2 deletions python/Scripts/mxformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -70,17 +71,23 @@ 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}')
if validate:
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]))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0"?>
<materialx version="1.39" colorspace="lin_rec709">
<nodegraph name="NG_marble1" xpos="-1.521739" ypos="-1.724138">
<input name="base_color_1" type="color3" value="0.8, 0.8, 0.8" uiname="Color 1" uifolder="Marble Color" />
<input name="base_color_2" type="color3" value="0.1, 0.1, 0.3" uiname="Color 2" uifolder="Marble Color" />
<input name="noise_scale_1" type="float" value="6.0" uisoftmin="1.0" uisoftmax="10.0" uiname="Scale 1" uifolder="Marble Noise" />
<input name="noise_scale_2" type="float" value="4.0" uisoftmin="1.0" uisoftmax="10.0" uiname="Scale 2" uifolder="Marble Noise" />
<input name="noise_power" type="float" value="3.0" uisoftmin="1.0" uisoftmax="10.0" uiname="Power" uifolder="Marble Noise" />
<input name="noise_octaves" type="integer" value="3" uisoftmin="1" uisoftmax="8" uiname="Octaves" uifolder="Marble Noise" />
<node name="obj_pos" nodetype="position" type="vector3" />
<node name="add_xyz" nodetype="dotproduct" type="float">
<input name="in1" type="vector3" nodename="obj_pos" />
<input name="in2" type="vector3" value="1, 1, 1" />
</node>
<node name="scale_xyz" nodetype="multiply" type="float">
<input name="in1" type="float" nodename="add_xyz" />
<input name="in2" type="float" interfacename="noise_scale_1" />
</node>
<node name="scale_pos" nodetype="multiply" type="vector3">
<input name="in1" type="vector3" nodename="obj_pos" />
<input name="in2" type="float" interfacename="noise_scale_2" />
</node>
<node name="noise" nodetype="fractal3d" type="float">
<input name="octaves" type="integer" interfacename="noise_octaves" />
<input name="position" type="vector3" nodename="scale_pos" />
</node>
<node name="scale_noise" nodetype="multiply" type="float">
<input name="in1" type="float" nodename="noise" />
<input name="in2" type="float" value="3.0" />
</node>
<node name="sum" nodetype="add" type="float">
<input name="in1" type="float" nodename="scale_xyz" />
<input name="in2" type="float" nodename="scale_noise" />
</node>
<node name="sin" nodetype="sin" type="float">
<input name="in" type="float" nodename="sum" />
</node>
<node name="scale" nodetype="multiply" type="float">
<input name="in1" type="float" nodename="sin" />
<input name="in2" type="float" value="0.5" />
</node>
<node name="bias" nodetype="add" type="float">
<input name="in1" type="float" nodename="scale" />
<input name="in2" type="float" value="0.5" />
</node>
<node name="power" nodetype="power" type="float">
<input name="in1" type="float" nodename="bias" />
<input name="in2" type="float" interfacename="noise_power" />
</node>
<node name="color_mix" nodetype="mix" type="color3">
<input name="bg" type="color3" interfacename="base_color_1" />
<input name="fg" type="color3" interfacename="base_color_2" />
<input name="mix" type="float" nodename="power" />
</node>
<output name="out" type="color3" nodename="color_mix" />
</nodegraph>
<node name="SR_marble1" nodetype="standard_surface" type="surfaceshader" xpos="6.724638" ypos="-1.379310">
<input name="base" type="float" value="1" />
<input name="base_color" type="color3" nodegraph="NG_marble1" output="out" />
<input name="specular_roughness" type="float" value="0.1" />
<input name="subsurface" type="float" value="0.4" />
<input name="subsurface_color" type="color3" nodegraph="NG_marble1" output="out" />
</node>
<node name="Marble_3D" nodetype="surfacematerial" type="material" xpos="17.391304" ypos="0.000000">
<input name="surfaceshader" type="surfaceshader" nodename="SR_marble1" />
</node>
</materialx>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0"?>
<materialx version="1.39" colorspace="lin_rec709">
<nodegraph name="NG_OnyxHextiled" fileprefix="../../../Images/" xpos="-3.826087" ypos="-1.724138">
<input name="tiling" type="float" value="4.0" uiname="Tiling" uifolder="Hex Tiling" />
<input name="rotation" type="float" value="1.0" uiname="Rotation" uifolder="Hex Tiling" />
<input name="scale" type="float" value="1.0" uiname="Scale" uifolder="Hex Tiling" />
<input name="offset" type="float" value="1.0" uiname="Offset" uifolder="Hex Tiling" />
<input name="falloff" type="float" value="0.75" uiname="Falloff" uifolder="Hex Tiling" />
<node name="tiling_vector2" nodetype="convert" type="vector2">
<input name="in" type="float" interfacename="tiling" />
</node>
<node name="image_color" nodetype="hextiledimage" type="color3">
<input name="file" type="filename" value="onyx_color.jpg" colorspace="srgb_texture" />
<input name="tiling" type="vector2" nodename="tiling_vector2" />
<input name="rotation" type="float" interfacename="rotation" />
<input name="scale" type="float" interfacename="scale" />
<input name="offset" type="float" interfacename="offset" />
<input name="falloff" type="float" interfacename="falloff" />
</node>
<node name="image_roughness" nodetype="hextiledimage" type="color3">
<input name="file" type="filename" value="onyx_roughness.jpg" colorspace="none" />
<input name="tiling" type="vector2" nodename="tiling_vector2" />
<input name="rotation" type="float" interfacename="rotation" />
<input name="scale" type="float" interfacename="scale" />
<input name="offset" type="float" interfacename="offset" />
<input name="falloff" type="float" interfacename="falloff" />
</node>
<node name="image_roughness_float" nodetype="extract" type="float">
<input name="in" type="color3" nodename="image_roughness" />
<input name="index" type="integer" value="0" />
</node>
<output name="out_color" type="color3" nodename="image_color" />
<output name="out_roughness" type="float" nodename="image_roughness_float" />
</nodegraph>
<node name="N_StandardSurface" nodetype="standard_surface" type="surfaceshader" xpos="6.289855" ypos="-0.344828">
<input name="base_color" type="color3" nodegraph="NG_OnyxHextiled" output="out_color" />
<input name="specular_roughness" type="float" nodegraph="NG_OnyxHextiled" output="out_roughness" />
</node>
<node name="M_OnyxHextiled" nodetype="surfacematerial" type="material" xpos="17.391304" ypos="0.000000">
<input name="surfaceshader" type="surfaceshader" nodename="N_StandardSurface" />
</node>
</materialx>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<materialx version="1.39" xmlns:xi="http://www.w3.org/2001/XInclude">
<nodegraph name="NG_multi" nodedef="ND_multi">
<node name="burley_bsdf" nodetype="burley_diffuse_bsdf" type="BSDF">
<input name="color" type="color3" interfacename="diffuse_color" />
</node>
<node name="dielectric_bsdf" nodetype="dielectric_bsdf" type="BSDF">
<input name="tint" type="color3" interfacename="diffuse_color" />
</node>
<node name="burley_surface" nodetype="surface" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="burley_bsdf" />
</node>
<node name="dielectric_surface" nodetype="surface" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="dielectric_bsdf" />
</node>
<output name="burley_out" type="surfaceshader" nodename="burley_surface" />
<output name="dielectric_out" type="surfaceshader" nodename="dielectric_surface" />
</nodegraph>
<nodedef name="ND_multi" node="customtype" version="1.0" isdefaultversion="true">
<input name="diffuse_color" type="color3" value="0.25, 0.50, 0.75" />
<output name="burley_out" type="surfaceshader" />
<output name="dielectric_out" type="surfaceshader" />
</nodedef>
<node name="customtype_1" nodetype="customtype" type="multioutput" xpos="8.797101" ypos="1.025862">
<input name="diffuse_color" type="color3" value="0.25, 0.50, 0.75" />
<output name="burley_out" type="surfaceshader" value="" />
<output name="dielectric_out" type="surfaceshader" value="" />
</node>
<output name="burley_out2" nodename="customtype_1" output="burley_out" type="surfaceshader" xpos="17.391304" ypos="0.000000" />
<output name="dielectric_out2" nodename="customtype_1" output="dielectric_out" type="surfaceshader" xpos="17.391304" ypos="2.741379" />
</materialx>
64 changes: 49 additions & 15 deletions source/MaterialXFormat/XmlIo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,24 @@ 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.
if (!elem->getName().empty())
{
xmlNode.append_attribute(Element::NAME_ATTRIBUTE.c_str()) = elem->getName().c_str();
}

bool isNode = writeNodeInstanceAsNode && elem->isA<Node>();
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());
Expand Down Expand Up @@ -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<Node>();
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());
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -280,7 +313,8 @@ XmlReadOptions::XmlReadOptions() :
//

XmlWriteOptions::XmlWriteOptions() :
writeXIncludeEnable(true)
writeXIncludeEnable(true),
writeNodeInstanceAsNode(false)
{
}

Expand Down
4 changes: 4 additions & 0 deletions source/MaterialXFormat/XmlIo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions source/MaterialXGraphEditor/Graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4973,6 +4973,7 @@ void Graph::saveDocument(mx::FilePath filePath)
}

mx::XmlWriteOptions writeOptions;
writeOptions.writeNodeInstanceAsNode = true;
writeOptions.elementPredicate = getElementPredicate();
mx::writeToXmlFile(writeDoc, filePath, &writeOptions);
}
1 change: 1 addition & 0 deletions source/PyMaterialX/PyMaterialXFormat/PyXmlIo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ void bindPyXmlIo(py::module& mod)
py::class_<mx::XmlWriteOptions>(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,
Expand Down
Loading