{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
" # Creating Material Graphs\n",
"\n",
"The following topics will be covered in this book:\n",
"1. Creating a node graph container.\n",
"2. Creating container input and output interfaces.\n",
"3. Creating nodes in a graph.\n",
"4. Connecting nodes in a graph.\n",
"5. Creating a material and connecting the graph to the material.\n",
"\n",
"At the end of this book, a simple shader graph will have been created. \n",
"\n",
"The utilities used in this tutorial are available in the `mtlxutils` file: mtlxutls/mxnodegraph.py for reuse."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"The following pre-requisite setup steps need to performed first:\n",
"* Load MaterialX\n",
"* Creating a working document\n",
"* Loading in the standard library definitions\n",
"* Setting up a predicate to filter definitions on write."
]
},
{
"cell_type": "code",
"execution_count": 2384,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"MaterialX version 1.39.0. Loaded 743 standard library definitions\n"
]
}
],
"source": [
"import MaterialX as mx\n",
"\n",
"# Version check\n",
"from mtlxutils.mxbase import *\n",
"haveVersion1387 = haveVersion(1, 38, 7) \n",
"if not haveVersion1387:\n",
" print(\"** Warning: Minimum version is 1.38.7 for tutorials. Have version: \", mx.__version__)\n",
"\n",
"stdlib = mx.createDocument()\n",
"searchPath = mx.getDefaultDataSearchPath()\n",
"libraryFolders = mx.getDefaultDataLibraryFolders()\n",
"try:\n",
" libFiles = mx.loadLibraries(libraryFolders, searchPath, stdlib)\n",
" print('MaterialX version %s. Loaded %d standard library definitions' % (mx.__version__, len(stdlib.getNodeDefs())))\n",
"except mx.Exception as err:\n",
" print('Failed to load standard library definitions: \"', err, '\"')\n",
"\n",
"doc = mx.createDocument()\n",
"doc.importLibrary(stdlib)\n",
"\n",
"# Write predicate\n",
"def skipLibraryElement(elem):\n",
" return not elem.hasSourceUri()\n",
"\n",
"def validateDocument(doc):\n",
" valid, errors = doc.validate()\n",
" if not valid:\n",
" print('> Document is not valid')\n",
" print('> ' + errors)\n",
" else:\n",
" print('> Document is valid')"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"# Creating a Node Graph\n",
"\n",
"## Create `` Container\n",
"The first step to creating a useful node graph is to create the parent container (`NodeGraph`).\n",
"The interface `addNodeGraph()` can be used to do so. \n",
"\n",
"As with documents, all children must be uniquely named. Name generation of child names uses the\n",
"`createValidChildName()` interface which can be used for documents, nodes, and node graphs. \n"
]
},
{
"cell_type": "code",
"execution_count": 2385,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Created nodegraph: \n",
"\n"
]
}
],
"source": [
"def addNodeGraph(parent, name):\n",
" \"\"\"\n",
" Add named nodegraph under parent\n",
" \"\"\"\n",
" # Create a uniquely named node graph container under the parent document\n",
" childName = parent.createValidChildName(name)\n",
" \n",
" # Create the node graph\n",
" nodegraph = parent.addChildOfCategory('nodegraph', childName)\n",
" return nodegraph\n",
"\n",
"nodeGraph = addNodeGraph(doc,\"test_nodegraph\")\n",
"if nodeGraph:\n",
" print('Created nodegraph:', mx.prettyPrint(nodeGraph)) "
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Creating Output Interfaces\n",
"\n",
"A node graph container without any outputs (`Output`) isn't of much use as no data flow can occur.\n",
"Thus, at a minimum a `NodeGraph`s should create at least one child output. \n",
"This can be done using the `addOutput()` interface on a `NodeGraph`. \n",
"\n",
"The same considerations should be given for creating an output for nodes. Namely:\n",
"* a unique name\n",
"* a proper type \n",
"should be used. \n",
"\n",
"In this case we want to create a graph which outputs a `surfaceshader`."
]
},
{
"cell_type": "code",
"execution_count": 2386,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
" \n"
]
}
],
"source": [
"def addNodeGraphOutput(parent, type, name='out'):\n",
" \"\"\"\n",
" Create an output with a unique name and proper type\n",
" \"\"\"\n",
" if not parent.isA(mx.NodeGraph):\n",
" return None\n",
" \n",
" newOutput = None\n",
" childName = parent.createValidChildName(name)\n",
" newOutput = parent.addOutput(childName, type)\n",
" return newOutput\n",
"\n",
"type = 'surfaceshader'\n",
"graphOutput = addNodeGraphOutput(nodeGraph, type)\n",
"\n",
"# Print the graph\n",
"text = mx.prettyPrint(nodeGraph)\n",
"print(text + '')"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that we are using `getNamePath()` to check parent / child relationships. \n",
"\n",
"The path string (`test_nodegraph/out`) indicates that the new output has been correctly added as a child under the node graph container `test_nodegraph`. (where `/` is the parent/child path separator) "
]
},
{
"cell_type": "code",
"execution_count": 2387,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Path to output is: \"test_nodegraph/out\"\n"
]
}
],
"source": [
"# Examine the path to the output\n",
"print('Path to output is: \"%s\"' % graphOutput.getNamePath())"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Creating Graph Nodes\n",
"\n",
"Nodes can now be created to add logic to the graph.\n",
"\n",
"The basics book demonstrates how to create nodes as direct children of a `Document`.\n",
"The same interfaces are reused here, with the key difference being that the\n",
"they are created with respect to a `NodeGraph` instead of the `Document`.\n",
"\n",
"That is, we call `NodeGraph.addNodeInstance()` instead of `Document.addNodeInstance()` to add\n",
"a node under a graph instead of a document.\n",
"\n",
"A utility called `createNode()` is added for reuse. "
]
},
{
"cell_type": "code",
"execution_count": 2388,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"- Create shader node with path: test_nodegraph/test_shader\n",
"- Graph contents:\n",
"\n",
"\n",
" \n"
]
}
],
"source": [
"def createNode(definitionName, parent, name):\n",
" \"Utility to create a node under a given parent using a definition name and desired instance name\"\n",
" nodeName = parent.createValidChildName(name)\n",
" nodedef = doc.getNodeDef(definitionName)\n",
" if nodedef:\n",
" newNode = parent.addNodeInstance(nodedef, nodeName)\n",
" if newNode:\n",
" return newNode\n",
" else:\n",
" print('Cannot find definition:', definitionName)\n",
" return None\n",
"\n",
"shaderNode = createNode('ND_standard_surface_surfaceshader', nodeGraph, 'test_shader')\n",
"if shaderNode:\n",
" print('- Create shader node with path:', shaderNode.getNamePath())\n",
"\n",
"# Print contents of graph\n",
"print('- Graph contents:\\n')\n",
"text = mx.prettyPrint(nodeGraph)\n",
"print(text + '')"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Connecting Nodes To Output Interfaces\n",
"\n",
"To allow output data from the shader node to be accessible the shader node's **output** is connected to the \n",
"graph containers **output**.\n",
"\n",
"A utility called `connectOutputToOutput()` is used to hide the syntactic differences between connecting to an upstream node graph as\n",
"opposed to a node, and to check for \"type compatibility\", where \"compatible\" means both ports are of the exact same type. \n",
"\n",
"Note that only upstream nodes, and graphs can to a downstream output. Inputs cannot be directly connected to an output. A `dot` node\n",
"should be used as a pass-through in this case.\n",
"\n",
"> Unfortunately, adding explicit outputs to nodes is not recommended, otherwise these can be pre-populated on a node to avoid the constant search on the definition if it is not found on the node. Basically a `addOutputFromNodeDef()` utility could be called before\n",
"making any connections."
]
},
{
"cell_type": "code",
"execution_count": 2389,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Connected output \"test_nodegraph/out\" to upstream output: test_nodegraph/test_shader.out\n",
"\n",
"\n",
" \n"
]
}
],
"source": [
"def connectOutputToOutput(outputPort, upstream, upstreamOutputName):\n",
" \"Utility to connect a downstream output to an upstream node / node output\"\n",
" \"If the types differ then no connection is made\"\n",
" if not upstream:\n",
" return False\n",
" \n",
" # Cannot directly connect an input to an output\n",
" if upstream.isA(mx.Input):\n",
" return False\n",
"\n",
" upstreamType = upstream.getType()\n",
"\n",
" # Check for an explicit upstream output on the upstream node\n",
" # or upstream node's definition\n",
" if upstreamOutputName:\n",
" upStreamPort = upstream.getActiveOutput(upstreamOutputName)\n",
" if not upStreamPort:\n",
" upstreamNodeDef = upstream.getNodeDef()\n",
" if upstreamNodeDef:\n",
" upStreamPort = upstreamNodeDef.getActiveOutput(upstreamOutputName)\n",
" else:\n",
" return False\n",
" if upStreamPort:\n",
" upstreamType = upStreamPort.getType()\n",
" \n",
" outputPortType = outputPort.getType() \n",
" if upstreamType != outputPortType:\n",
" return False\n",
" \n",
" upstreamName = upstream.getName()\n",
" attributeName = 'nodename'\n",
" if upstream.isA(mx.NodeGraph):\n",
" attributeName = 'nodegraph'\n",
" outputPort.setAttribute(attributeName, upstreamName)\n",
" \n",
" # If an explicit output is specified on the upstream node/graph then\n",
" # set it.\n",
" if upstreamOutputName and upstream.getType() == 'multioutput':\n",
" outputPort.setOutputString(upstreamOutputName) \n",
" \n",
" return True\n",
"\n",
"# Make the connection\n",
"shaderNodeOutput = \"out\"\n",
"if connectOutputToOutput(graphOutput, shaderNode, shaderNodeOutput):\n",
" print('Connected output \"%s\" to upstream output: %s.%s' % (graphOutput.getNamePath(), shaderNode.getNamePath(), shaderNodeOutput))\n",
"else:\n",
" print('Failed to connected output \"%s\" to upstream output: %s.%s' % (graphOutput.getNamePath(), shaderNode.getNamePath(), shaderNodeOutput))\n",
"\n",
"\n",
"# Check the graph\n",
"text = mx.prettyPrint(nodeGraph)\n",
"print(\"\")\n",
"print(text + '')"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
" ## Making Connections Between Nodes\n",
" \n",
" Connections are formed from a downstream `input` to an upstream `output`. For this a wrapper function is\n",
" used to hide some of the syntactic peculiarities.\n",
"\n",
"Setting a connection can be cumbersome for the same reason that setting a value can be cumbersome\n",
"in that a node instance when created has no inputs instantiated. So a check\n",
"must be made to see if it exists and if its not added. Then if input and outputs types match\n",
"then the input can make the connection.\n",
"\n",
"Additionally it is considered \"invalid\" to have both a `value` and a connection on an input, so\n",
"if a value has been set it must be removed. Conversely when a connection is removed a value must be\n",
"re-assigned. \n",
"\n",
"As with value setting, the interface `addInputFromNodeDef()` is used to add individual inputs\n",
"if they do not exist. A utility called `createNode()` is added for convenience.\n",
"\n",
"Having a `connectNodeToNode()` interface would be a useful to have in the core API to avoid having\n",
"to rewrite this logic.\n",
"\n",
"> Note that is currently considered undesirable to have explicit outputs defined on nodes which\n",
"also adds undue complexity. "
]
},
{
"cell_type": "code",
"execution_count": 2390,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Connected \"test_nodegraph/test_image\" to \"test_nodegraph/test_shader\" in node graph \"test_nodegraph\"\n",
"\n",
"\n",
"\n",
" \n"
]
}
],
"source": [
"def connectNodeToNode(inputNode, inputName, outputNode, outputName):\n",
" \"Connect an input on one node to an output on another node. Existence and type checking are performed.\"\n",
" \"Returns input port with connection set if succesful. Otherwise None is returned.\"\n",
"\n",
" if not inputNode or not outputNode:\n",
" return None\n",
"\n",
"\n",
" # Check for the type.\n",
" outputType = outputNode.getType() \n",
" \n",
" # If there is more than one output then we need to find the output type \n",
" # from the output with the name we are interested in.\n",
" outputPortFound = None\n",
" outputPorts = outputNode.getOutputs()\n",
" if outputPorts:\n",
" # Look for an output with a given name, or the first if not found \n",
" if not outputName:\n",
" outputPortFound = outputPorts[0]\n",
" else:\n",
" outputPortFound = outputNode.getOutput(outputName)\n",
"\n",
" # If the output port is not found on the node instance then\n",
" # look for it the corresponding definition\n",
" if not outputPortFound:\n",
" outputNodedef = outputNode.getNodeDef()\n",
" if outputNodedef:\n",
" outputPorts = outputNodedef.getOutputs()\n",
" \n",
" if outputPorts:\n",
" # Look for an output with a given name, or the first if not found \n",
" if not outputName:\n",
" outputPortFound = outputPorts[0]\n",
" else:\n",
" outputPortFound = outputNodedef.getOutput(outputName)\n",
"\n",
" if outputPortFound:\n",
" outputType = outputPortFound.getType()\n",
" else:\n",
" print('No output port found matching: ', outputName) \n",
"\n",
" # Add an input to the downstream node if it does not exist\n",
" inputPort = inputNode.addInputFromNodeDef(inputName)\n",
" \n",
" if inputPort.getType() != outputType:\n",
" print('Input type (%s) and output type (%s) do not match: ' % (inputPort.getType(), outputType))\n",
" return None\n",
"\n",
" if inputPort:\n",
" # Remove any value, and set a \"connection\" but setting the node name\n",
" inputPort.removeAttribute('value')\n",
" attributeName = 'nodename' if outputNode.isA(mx.Node) else 'nodegraph'\n",
" inputPort.setAttribute(attributeName, outputNode.getName())\n",
" if outputNode.getType() == 'multioutput' and outputName:\n",
" inputPort.setOutputString(outputName)\n",
" return inputPort\n",
" \n",
"# Create a unique child name under the node graph container\n",
"imageNode = createNode(\"ND_image_color3\", nodeGraph, \"test_image\")\n",
"if imageNode and shaderNode:\n",
" inputConnnected = connectNodeToNode(shaderNode, \"base_color\", imageNode, \"\")\n",
" if inputConnnected:\n",
" print('Connected \"%s\" to \"%s\" in node graph \"%s\"' % (imageNode.getNamePath(), shaderNode.getNamePath(), \n",
" nodeGraph.getNamePath()))\n",
" \n",
"# Check the graph\n",
"text = mx.prettyPrint(nodeGraph)\n",
"print('\\n')\n",
"print(text + '')"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Adding Input Interfaces\n",
"\n",
"Just as child outputs can be added to a `NodeGraph`, child inputs (`Input`) can also be added.\n",
"Adding inputs can be thought of as exposing the internal inputs as \"public\" interfaces.\n",
"\n",
"The interface `addInputInterface()` can be used to add one or more inputs. These inputs can then be connected to inputs on node children within the node graph container.\n",
"\n",
"> Note that `NodeGraph.addInterfaceName()` can **only** be used for a graph which represents an implementation of a definition ('functional nodegraph'). An error condition will always be thrown\n",
"otherwise. It would be useful if this interface handled non-functional nodegraphs as well.) "
]
},
{
"cell_type": "code",
"execution_count": 2391,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Added input interfaces: \"input_file\" and \"color_scale\"\n",
"\n",
"\n",
" \n"
]
}
],
"source": [
"def addInputInterface(name, typeString, parent):\n",
" \"Add a type input interface. Will always create a new interface\"\n",
"\n",
" validType = False\n",
" typedefs = parent.getDocument().getTypeDefs()\n",
" for t in typedefs:\n",
" if typeString in t.getName():\n",
" validType = True\n",
" break\n",
"\n",
" if validType:\n",
" validName = parent.createValidChildName(name)\n",
" parent.addInput(validName, typeString)\n",
" \n",
"# Add interfaces\n",
"addInputInterface('input_file', 'filename', nodeGraph)\n",
"addInputInterface('color_scale', 'float', nodeGraph)\n",
"\n",
"# Check the graph\n",
"text = mx.prettyPrint(nodeGraph)\n",
"print('Added input interfaces: \"input_file\" and \"color_scale\"\\n')\n",
"print(text + '')"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"The connection for interfaces is slightly different in that instead of an `Output` an `Input` is being connected to a downstream `Input`.\n",
"\n",
"We will again write a utility to hide some of the syntactic peculiarities."
]
},
{
"cell_type": "code",
"execution_count": 2392,
"metadata": {},
"outputs": [],
"source": [
"def connectInterface(nodegraph, interfaceName, internalInput):\n",
" \"Add an interface input to a nodegraph if it does not already exist.\" \n",
" \"Connect the interface to the internal input. Returns interface input\"\n",
"\n",
" if not nodegraph or not interfaceName or not internalInput:\n",
" return None\n",
"\n",
" interfaceInput = nodegraph.getInput(interfaceName)\n",
"\n",
" # Create a new interface with the desired type\n",
" if not interfaceInput:\n",
" interfaceName = nodeGraph.createValidChildName(interfaceName) \n",
" interfaceInput = nodegraph.addInput(interfaceName, internalInput.getType())\n",
"\n",
" # Copy attributes from internal input to interface. \n",
" # Remove undesired attributes.\n",
" interfaceInput.copyContentFrom(internalInput)\n",
" interfaceInput.removeAttribute('sourceUri')\n",
" interfaceInput.removeAttribute('interfacename')\n",
"\n",
" # Logic transfer any value from the internal input to the interface.\n",
" # If none is found then use the the default value as defined by the definition.\n",
" internalInputType = internalInput.getType()\n",
" if internalInput.getValue():\n",
" internaInputValue = internalInput.getValue() \n",
" if internaInputValue:\n",
" interfaceInput.setValue(internaInputValue, internalInputType)\n",
" else:\n",
" internalNode = internalInput.getParent() \n",
" internalNodeDef = internalNode.getNodeDef() if internalNode else None\n",
" internalNodeDefInput = internalNodeDef.getInput(interfaceName) if internalNodeDef else None\n",
" internaInputValue = internalNodeDefInput.getValue() if internalNodeDefInput else None\n",
" if internaInputValue:\n",
" interfaceInput.setValue(internaInputValue, internalInputType)\n",
"\n",
" # Remove \"value\" from internal input as it's value is via a connection\n",
" internalInput.removeAttribute('value')\n",
"\n",
" # \"Connect\" the internal node's input to the interface. Remove any\n",
" # specified value\n",
" internalInput.setInterfaceName(interfaceName)\n",
"\n",
" return interfaceInput\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First example exposes the 'file' input as an 'input_file' interface to the graph."
]
},
{
"cell_type": "code",
"execution_count": 2393,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
" \n"
]
}
],
"source": [
"# Add a 'file' input to the child node \n",
"imageFileInput = imageNode.addInputFromNodeDef('file')\n",
"imageFileInputType = imageFileInput.getType()\n",
"imageFileInput.setValue(\"checker.png\", imageFileInputType)\n",
"# Connect it to interface intput on \"input_file\" \n",
"connectInterface(nodeGraph, \"input_file\", imageNode.getInput('file'))\n",
"\n",
"print(mx.prettyPrint(nodeGraph) + '')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Second example to expose \"base\" as a \"color_scale\" input on the graph."
]
},
{
"cell_type": "code",
"execution_count": 2394,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
" \n"
]
}
],
"source": [
"# Second example: Publish 'base' as an interface. \"Transfer\"\n",
"# the default value from 'base' on the shader node to the interfce input. \n",
"baseInput = shaderNode.addInputFromNodeDef('base')\n",
"connectInterface(nodeGraph, \"color_scale\", baseInput)\n",
"\n",
"print(mx.prettyPrint(nodeGraph) + '')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Third example to add the 'color_scale' input with the non-default value from 'base_color'"
]
},
{
"cell_type": "code",
"execution_count": 2395,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
" \n"
]
}
],
"source": [
"# Set a non-default value to be added to the published interface\n",
"baseInput.setValue(0.2, baseInput.getType())\n",
"connectInterface(nodeGraph, \"color_scale\", baseInput)\n",
"\n",
"print(mx.prettyPrint(nodeGraph) + '')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As a final step, we check that the document is valid and then write out the entire document to a file."
]
},
{
"cell_type": "code",
"execution_count": 2396,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Wrote document to file: data/sample_nodegraph.mtlx\n",
"\n",
"\n",
"\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
"\n",
"\n"
]
}
],
"source": [
"# Check the entire document\n",
"isValid = doc.validate()\n",
"if not isValid:\n",
" print('Document is not valid')\n",
"else:\n",
" writeOptions = mx.XmlWriteOptions()\n",
" writeOptions.writeXIncludeEnable = False\n",
" writeOptions.elementPredicate = skipLibraryElement\n",
"\n",
" # Save document\n",
" mx.writeToXmlFile(doc, 'data/sample_nodegraph.mtlx', writeOptions)\n",
"\n",
" print('Wrote document to file: data/sample_nodegraph.mtlx\\n')\n",
" documentContents = mx.writeToXmlString(doc, writeOptions)\n",
" print(documentContents)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Connecting Node to a NodeGraph\n",
"\n",
"Now that we have a graph with appropriate interfaces we can create a \"material\" by connecting it to a downstream material node (`material`)."
]
},
{
"cell_type": "code",
"execution_count": 2397,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Create material node: my_material\n",
"\n",
"\n",
" \n",
"\n",
"\n",
" \n"
]
}
],
"source": [
"# Create material node \n",
"materialNode = createNode('ND_surfacematerial', doc, 'my_material')\n",
"if materialNode:\n",
" print('Create material node: %s\\n' % materialNode.getName())\n",
"\n",
"# Connect the material node to the output of the graph\n",
"connectNodeToNode(materialNode, 'surfaceshader', nodeGraph, 'out')\n",
"\n",
"# Check results\n",
"print(mx.prettyPrint(materialNode) + '')\n",
"print(mx.prettyPrint(nodeGraph) + '')"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Material Graph Result\n",
"\n",
"The resulting document is shown in XML, diagram and rendered form. (The render is performed using the `MaterialXView` utility)\n",
"\n",
"\n",
""
]
},
{
"cell_type": "code",
"execution_count": 2398,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"> Document is valid\n",
"Wrote document to file: data/sample_nodegraph.mtlx\n",
"\n",
"\n",
"\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
"\n",
"\n"
]
}
],
"source": [
"# Check the entire document\n",
"validateDocument(doc)\n",
"writeOptions = mx.XmlWriteOptions()\n",
"writeOptions.writeXIncludeEnable = False\n",
"writeOptions.elementPredicate = skipLibraryElement\n",
"\n",
"# Save document\n",
"mx.writeToXmlFile(doc, 'data/sample_nodegraph.mtlx', writeOptions)\n",
"\n",
"print('Wrote document to file: data/sample_nodegraph.mtlx\\n')\n",
"documentContents = mx.writeToXmlString(doc, writeOptions)\n",
"print(documentContents)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Renaming Nodes \n",
"\n",
"If you just rename a node without renaming references to it, then the references will be broken.\n",
"Currently the interface for setting node names is \"unsafe\" in that it does not check for references to the node.\n",
"\n",
"Below a utility is added to rename a node and update all references to it. It uses the existing interface getDownStreamPorts() to find all references to a node\n",
"and updates them."
]
},
{
"cell_type": "code",
"execution_count": 2399,
"metadata": {},
"outputs": [],
"source": [
"def renameNode(node, newName : str, updateReferences : bool = True):\n",
"\n",
" if not node or not newName:\n",
" return\n",
" if not (node.isA(mx.Node) or node.isA(mx.NodeGraph)):\n",
" print('A non-node or non-nodegraph was passed to renameNode()')\n",
" return \n",
" if node.getName() == newName:\n",
" return\n",
"\n",
" parent = node.getParent()\n",
" if not parent:\n",
" return\n",
"\n",
" newName = parent.createValidChildName(newName)\n",
"\n",
" if updateReferences:\n",
" downStreamPorts = node.getDownstreamPorts()\n",
" if downStreamPorts:\n",
" for port in downStreamPorts:\n",
" #if (port.getNodeName() == node.getName()): This is assumed from getDownstreamPorts()\n",
" oldName = port.getNodeName()\n",
" if (port.getAttribute('nodename')):\n",
" port.setNodeName(newName)\n",
" print(' > Update downstream port: \"' + port.getNamePath() + '\" from:\"' + oldName + '\" to \"' + port.getAttribute('nodename') + '\"')\n",
" elif (port.getAttribute('nodegraph')):\n",
" port.setAttribute('nodegraph', newName)\n",
" print(' > Update downstream port: \"' + port.getNamePath() + '\" from:\"' + oldName + '\" to \"' + port.getAttribute('nodegraph') + '\"')\n",
" elif (port.getAttribute('interfacename')):\n",
" port.setAttribute('interfacename', newName)\n",
" print(' > Update downstream port: \"' + port.getNamePath() + '\" from:\"' + oldName + '\" to \"' + port.getAttribute('interfacename') + '\"')\n",
"\n",
" node.setName(newName)\n"
]
},
{
"cell_type": "code",
"execution_count": 2400,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"> Result with renaming to same name:\n",
"\n",
"\n",
" \n"
]
}
],
"source": [
"# Test renaming to the same name. This will be a no-op\n",
"shaderNode = nodeGraph.getNode('test_shader')\n",
"renameNode(shaderNode, 'test_shader') \n",
"print('> Result with renaming to same name:\\n')\n",
"print(mx.prettyPrint(nodeGraph) + '')\n"
]
},
{
"cell_type": "code",
"execution_count": 2401,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"> Rename with new names, but without updating references:\n",
"\n",
" \n"
]
}
],
"source": [
"\n",
"print('> Rename with new names, but without updating references:')\n",
"# Then rename to a new name\n",
"renameNode(shaderNode, 'new_shader', False) \n",
"# Also rename the image node\n",
"imageNode = nodeGraph.getNode('test_image')\n",
"renameNode(imageNode, 'new_image', False)\n",
"\n",
"print(mx.prettyPrint(nodeGraph) + '')\n"
]
},
{
"cell_type": "code",
"execution_count": 2402,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"> Document is not valid\n",
"> Invalid port connection: