// // Copyright Contributors to the MaterialX Project // SPDX-License-Identifier: Apache-2.0 // #include #include #include #include #include #include namespace mx = MaterialX; bool isTopologicalOrder(const std::vector& elems) { std::set prevElems; for (mx::ElementPtr elem : elems) { for (size_t i = 0; i < elem->getUpstreamEdgeCount(); i++) { mx::ElementPtr upstreamElem = elem->getUpstreamElement(i); if (upstreamElem && !prevElems.count(upstreamElem)) { return false; } } prevElems.insert(elem); } return true; } TEST_CASE("Interface Input Validation", "[node]") { std::string validationErrors; mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); mx::DocumentPtr doc = mx::createDocument(); mx::loadLibraries({ "libraries" }, searchPath, doc); // Test inside nodegraph mx::GraphElementPtr nodegraph = doc->addNodeGraph("graph1"); std::vector graphs = { doc, nodegraph }; for (auto graph : graphs) { mx::InputPtr graphInput = graph->addInput(mx::EMPTY_STRING, "color3"); mx::NodePtr addNode = graph->addNode("add", mx::EMPTY_STRING, "color3"); mx::InputPtr addInput = addNode->addInput("in1"); addInput->setValueString("3, 3, 3"); addInput->setInterfaceName(graphInput->getName()); bool valid = doc->validate(&validationErrors); if (!valid) { INFO(validationErrors); } REQUIRE(!valid); addInput->setConnectedInterfaceName(graphInput->getName()); mx::InputPtr interfaceInput = addInput->getInterfaceInput(); REQUIRE((interfaceInput && interfaceInput->getNamePath() == graphInput->getNamePath())); REQUIRE(!addInput->getValue()); valid = doc->validate(&validationErrors); if (!valid) { INFO(validationErrors); } REQUIRE(valid); addInput->setConnectedInterfaceName(mx::EMPTY_STRING); addInput->setValueString("2, 2, 2"); interfaceInput = addInput->getInterfaceInput(); REQUIRE(!interfaceInput); valid = doc->validate(&validationErrors); if (!valid) { INFO(validationErrors); } REQUIRE(valid); } } TEST_CASE("Node Type Multioutput Validation", "[Node]") { // Create a document mx::DocumentPtr doc = mx::createDocument(); // Create a graph and add two outputs, types of the outputs are not important mx::NodeGraphPtr graph = doc->addNodeGraph("NG_custom_node"); graph->addOutput("output1", "float"); graph->addOutput("output2", "float"); // Create a nodeDef based on the graph and make sure it has two outputs mx::NodeDefPtr nodeDef = doc->addNodeDefFromGraph(graph, "nodeDefName", "Category", "ND_custom_node"); REQUIRE(nodeDef->getOutputCount() == 2); // Create a node based on the nodeDef and make sure it is of type multioutput and has no errors mx::NodePtr node = doc->addNodeInstance(nodeDef); REQUIRE(node->getType() == "multioutput"); REQUIRE(node->validate()); // Change the type of the node so that it does not match the nodeDef node->setType("float"); // Make sure the validation fails as the node no longer has multioutput as type REQUIRE(!node->validate()); } TEST_CASE("Node Type Validation", "[Node]") { // Create a document mx::DocumentPtr doc = mx::createDocument(); // Create a graph and add a single output mx::NodeGraphPtr graph = doc->addNodeGraph("NG_custom_node"); graph->addOutput("output1", "float"); // Create a nodeDef based on the graph and make sure it has a single output mx::NodeDefPtr nodeDef = doc->addNodeDefFromGraph(graph, "nodeDefName", "Category", "ND_custom_node"); REQUIRE(nodeDef->getOutputCount() == 1); // Create a node based on the nodeDef and make sure it has no errors mx::NodePtr node = doc->addNodeInstance(nodeDef); REQUIRE(node->validate()); // Change the type of the node so that it does not match the nodeDef node->setType("int"); // Make sure the validation fails as the node has a different type than the single output of the nodedef REQUIRE(!node->validate()); } TEST_CASE("Node", "[node]") { // Create a document. mx::DocumentPtr doc = mx::createDocument(); // Create a graph with two source nodes. mx::NodePtr constant = doc->addNode("constant"); mx::NodePtr image = doc->addNode("image"); REQUIRE(doc->getNodes().size() == 2); REQUIRE(doc->getNodes("constant").size() == 1); REQUIRE(doc->getNodes("image").size() == 1); // Set constant node color. mx::Color3 color(0.1f, 0.2f, 0.3f); constant->setInputValue("value", color); REQUIRE(constant->getInputValue("value")->isA()); REQUIRE(constant->getInputValue("value")->asA() == color); // Set image node file. std::string file("image1.tif"); image->setInputValue("file", file, mx::FILENAME_TYPE_STRING); REQUIRE(image->getInputValue("file")->isA()); REQUIRE(image->getInputValue("file")->asA() == file); // Create connected outputs. mx::OutputPtr output1 = doc->addOutput(); mx::OutputPtr output2 = doc->addOutput(); output1->setConnectedNode(constant); output2->setConnectedNode(image); REQUIRE(output1->getUpstreamElement() == constant); REQUIRE(output2->getUpstreamElement() == image); REQUIRE(constant->getDownstreamPorts()[0] == output1); REQUIRE(image->getDownstreamPorts()[0] == output2); // Create a custom nodedef. mx::NodeDefPtr customNodeDef = doc->addNodeDef("ND_turbulence3d", "float", "turbulence3d"); customNodeDef->setNodeGroup(mx::NodeDef::PROCEDURAL_NODE_GROUP); customNodeDef->setInputValue("octaves", 3); customNodeDef->setInputValue("lacunarity", 2.0f); customNodeDef->setInputValue("gain", 0.5f); // Reference the custom nodedef. mx::NodePtr custom = doc->addNodeInstance(customNodeDef); REQUIRE(custom->getNodeDefString() == customNodeDef->getName()); REQUIRE(custom->getNodeDef()->getNodeGroup() == mx::NodeDef::PROCEDURAL_NODE_GROUP); REQUIRE(custom->getInputValue("octaves")->isA()); REQUIRE(custom->getInputValue("octaves")->asA() == 3); custom->setInputValue("octaves", 5); REQUIRE(custom->getInputValue("octaves")->asA() == 5); // Remove the nodedef attribute from the node, requiring that it fall back // to type and version matching. custom->removeAttribute(mx::NodeDef::NODE_DEF_ATTRIBUTE); REQUIRE(custom->getNodeDef() == customNodeDef); // Set nodedef and node version strings. customNodeDef->setVersionString("2.0"); REQUIRE(custom->getNodeDef() == nullptr); customNodeDef->setDefaultVersion(true); REQUIRE(custom->getNodeDef() == customNodeDef); custom->setVersionString("1"); REQUIRE(custom->getNodeDef() == nullptr); custom->removeAttribute(mx::InterfaceElement::VERSION_ATTRIBUTE); REQUIRE(custom->getNodeDef() == customNodeDef); // Define a custom type. mx::TypeDefPtr typeDef = doc->addTypeDef("spectrum"); const int scalarCount = 10; for (int i = 0; i < scalarCount; i++) { mx::MemberPtr scalar = typeDef->addMember(); scalar->setType("float"); } REQUIRE(typeDef->getMembers().size() == scalarCount); // Reference the custom type. std::string d65("{400;82.75;500;109.35;600;90.01;700;71.61;800;59.45}"); constant->setInputValue("value", d65, "spectrum"); REQUIRE(constant->getInput("value")->getType() == "spectrum"); REQUIRE(constant->getInput("value")->getValueString() == d65); REQUIRE(constant->getInputValue("value")->isA()); REQUIRE(constant->getInputValue("value")->asA().getValueString() == d65); // Validate the document. REQUIRE(doc->validate()); // Disconnect outputs from sources. output1->setConnectedNode(nullptr); output2->setConnectedNode(nullptr); REQUIRE(output1->getUpstreamElement() == nullptr); REQUIRE(output2->getUpstreamElement() == nullptr); REQUIRE(constant->getDownstreamPorts().empty()); REQUIRE(image->getDownstreamPorts().empty()); // Remove nodes and outputs. doc->removeNode(image->getName()); doc->removeNode(constant->getName()); doc->removeNode(custom->getName()); doc->removeOutput(output1->getName()); doc->removeOutput(output2->getName()); REQUIRE(doc->getNodes().empty()); REQUIRE(doc->getOutputs().empty()); } TEST_CASE("Node inputCount repro", "[node]") { // Create a document. mx::DocumentPtr doc = mx::createDocument(); mx::NodePtr constant = doc->addNode("constant"); constant->setInputValue("value", 0.5f); // Check that input count is correct after clearContent constant->clearContent(); CHECK(constant->getInputCount() == 0); // Check that validate succeeds after clear and rebuild constant->setType("float"); mx::OutputPtr output = doc->addOutput(mx::EMPTY_STRING, "float"); output->setConnectedNode(constant); CHECK(doc->validate()); } TEST_CASE("Flatten", "[nodegraph]") { // Read an example containing graph-based custom nodes. mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); mx::DocumentPtr doc = mx::createDocument(); mx::readFromXmlFile(doc, "resources/Materials/TestSuite/stdlib/shader/surface.mtlx", searchPath); REQUIRE(doc->validate()); // Count root-level, nested, and custom nodes. size_t origRootNodes = doc->getNodes().size(); size_t origNestedNodes = 0; size_t origCustomNodes = 0; for (mx::NodeGraphPtr graph : doc->getNodeGraphs()) { origNestedNodes += graph->getNodes().size(); } for (mx::NodePtr node : doc->getNodes()) { if (node->getImplementation()) { origCustomNodes++; } } REQUIRE(origRootNodes > 0); REQUIRE(origNestedNodes > 0); REQUIRE(origCustomNodes > 0); // Flatten all root-level nodes. doc->flattenSubgraphs(); REQUIRE(doc->validate()); // Recount root-level nodes. size_t newRootNodes = doc->getNodes().size(); size_t expectedRootNodes = (origRootNodes - origCustomNodes) + (origNestedNodes * origCustomNodes); REQUIRE(newRootNodes == expectedRootNodes); } TEST_CASE("Inheritance", "[nodedef]") { mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); mx::DocumentPtr doc = mx::createDocument(); mx::loadLibraries({ "libraries" }, searchPath, doc); REQUIRE(doc->validate()); auto nodedef = doc->getNodeDef("ND_standard_surface_surfaceshader"); REQUIRE(nodedef); mx::NodePtr surfaceNode = doc->addNodeInstance(nodedef); REQUIRE(surfaceNode); mx::InputPtr nodedefSpecularInput = nodedef->getActiveInput("specular"); REQUIRE(nodedefSpecularInput); mx::InputPtr specularInput = surfaceNode->addInputFromNodeDef("specular"); REQUIRE(specularInput); REQUIRE(specularInput->getAttribute(mx::ValueElement::TYPE_ATTRIBUTE) == nodedefSpecularInput->getAttribute(mx::ValueElement::TYPE_ATTRIBUTE)); REQUIRE(specularInput->getAttribute(mx::ValueElement::VALUE_ATTRIBUTE) == nodedefSpecularInput->getAttribute(mx::ValueElement::VALUE_ATTRIBUTE)); } TEST_CASE("Topological sort", "[nodegraph]") { // Create a document. mx::DocumentPtr doc = mx::createDocument(); // Create a node graph with the following structure: // // [constant1] [constant2] [image2] // \ / \ / // [image1] [add1] [add2] // \ / \______ | // [multiply] \__ [add3] [noise3d] // \____________ | ____________/ // [mix] // | // [output] // mx::NodeGraphPtr nodeGraph = doc->addNodeGraph(); mx::NodePtr image1 = nodeGraph->addNode("image"); mx::NodePtr image2 = nodeGraph->addNode("image"); mx::NodePtr multiply = nodeGraph->addNode("multiply"); mx::NodePtr constant1 = nodeGraph->addNode("constant"); mx::NodePtr constant2 = nodeGraph->addNode("constant"); mx::NodePtr add1 = nodeGraph->addNode("add"); mx::NodePtr add2 = nodeGraph->addNode("add"); mx::NodePtr add3 = nodeGraph->addNode("add"); mx::NodePtr noise3d = nodeGraph->addNode("noise3d"); mx::NodePtr mix = nodeGraph->addNode("mix"); mx::OutputPtr output = nodeGraph->addOutput(); add1->setConnectedNode("in1", constant1); add1->setConnectedNode("in2", constant2); add2->setConnectedNode("in1", constant2); add2->setConnectedNode("in2", image2); add3->setConnectedNode("in1", add1); add3->setConnectedNode("in2", add2); multiply->setConnectedNode("in1", image1); multiply->setConnectedNode("in2", add1); mix->setConnectedNode("fg", multiply); mix->setConnectedNode("bg", add3); mix->setConnectedNode("mask", noise3d); output->setConnectedNode(mix); // Validate the document. REQUIRE(doc->validate()); // Create a topological order and validate the results. std::vector elemOrder = nodeGraph->topologicalSort(); REQUIRE(elemOrder.size() == nodeGraph->getChildren().size()); REQUIRE(isTopologicalOrder(elemOrder)); } TEST_CASE("New nodegraph from output", "[nodegraph]") { // Create a document. mx::DocumentPtr doc = mx::createDocument(); // Create a node graph with the following structure: // // [constant1] [constant2] [image2] // \ / \ / // [image1] [add1] [add2] // \ / \______ | // [multiply1] \__ [add3] [noise3d] [constant3] // \____________ | ____________/ \ / // [mix] \_ [multiply2]_/ // | | // [out1] [out2] // mx::NodeGraphPtr nodeGraph = doc->addNodeGraph(); mx::NodePtr image1 = nodeGraph->addNode("image"); mx::NodePtr image2 = nodeGraph->addNode("image"); mx::NodePtr multiply1 = nodeGraph->addNode("multiply"); mx::NodePtr multiply2 = nodeGraph->addNode("multiply"); mx::NodePtr constant1 = nodeGraph->addNode("constant"); mx::NodePtr constant2 = nodeGraph->addNode("constant"); mx::NodePtr constant3 = nodeGraph->addNode("constant"); mx::NodePtr add1 = nodeGraph->addNode("add"); mx::NodePtr add2 = nodeGraph->addNode("add"); mx::NodePtr add3 = nodeGraph->addNode("add"); mx::NodePtr noise3d = nodeGraph->addNode("noise3d"); mx::NodePtr mix = nodeGraph->addNode("mix"); mx::OutputPtr out1 = nodeGraph->addOutput("out1"); mx::OutputPtr out2 = nodeGraph->addOutput("out2"); add1->setConnectedNode("in1", constant1); add1->setConnectedNode("in2", constant2); add2->setConnectedNode("in1", constant2); add2->setConnectedNode("in2", image2); add3->setConnectedNode("in1", add1); add3->setConnectedNode("in2", add2); multiply1->setConnectedNode("in1", image1); multiply1->setConnectedNode("in2", add1); multiply2->setConnectedNode("in1", noise3d); multiply2->setConnectedNode("in2", constant3); mix->setConnectedNode("fg", multiply1); mix->setConnectedNode("bg", add3); mix->setConnectedNode("mask", noise3d); out1->setConnectedNode(mix); out2->setConnectedNode(multiply2); // Generate a new graph from each output. std::vector outputs = {out1, out2}; for (size_t i = 0; i < outputs.size(); ++i) { const mx::OutputPtr output = outputs[i]; // Create a new graph with this output. mx::NodeGraphPtr nodeGraph2 = doc->addNodeGraph(); nodeGraph2->addOutput(output->getName()); // Keep track of processed nodes to avoid duplication // of nodes with multiple downstream connections. std::set processedNodes; for (mx::Edge edge : output->traverseGraph()) { mx::NodePtr upstreamNode = edge.getUpstreamElement()->asA(); if (processedNodes.count(upstreamNode)) { // Node is already processed continue; } // Create this node in the new graph. mx::NodePtr newNode = nodeGraph2->addNode(upstreamNode->getCategory(), upstreamNode->getName()); newNode->copyContentFrom(upstreamNode); // Connect the node to downstream element in the new graph. mx::ElementPtr downstreamElement = edge.getDownstreamElement(); mx::ElementPtr connectingElement = edge.getConnectingElement(); if (downstreamElement->isA()) { mx::OutputPtr downstream = nodeGraph2->getOutput(downstreamElement->getName()); downstream->setConnectedNode(newNode); } else if (connectingElement) { mx::NodePtr downstream = nodeGraph2->getNode(downstreamElement->getName()); downstream->setConnectedNode(connectingElement->getName(), newNode); } // Mark node as processed. processedNodes.insert(upstreamNode); } // Create a topological order and validate the results. std::vector elemOrder = nodeGraph2->topologicalSort(); REQUIRE(elemOrder.size() == nodeGraph2->getChildren().size()); REQUIRE(isTopologicalOrder(elemOrder)); } // Validate the document. REQUIRE(doc->validate()); } TEST_CASE("Prune nodes", "[nodegraph]") { // Create a document. mx::DocumentPtr doc = mx::createDocument(); // Create a node graph with the following structure: // // [constant1] [constant2] [image2] // \ / \ / // [image1] [add1] [add2] // \ / \______ | // [multiply] \__ [add3] [noise3d] // \____________ | ____________/ // [mix] // | // [output] // mx::NodeGraphPtr nodeGraph = doc->addNodeGraph(); mx::NodePtr image1 = nodeGraph->addNode("image"); mx::NodePtr image2 = nodeGraph->addNode("image"); mx::NodePtr multiply = nodeGraph->addNode("multiply"); mx::NodePtr constant1 = nodeGraph->addNode("constant"); mx::NodePtr constant2 = nodeGraph->addNode("constant"); mx::NodePtr add1 = nodeGraph->addNode("add"); mx::NodePtr add2 = nodeGraph->addNode("add"); mx::NodePtr add3 = nodeGraph->addNode("add"); mx::NodePtr noise3d = nodeGraph->addNode("noise3d"); mx::NodePtr mix = nodeGraph->addNode("mix"); mx::OutputPtr output = nodeGraph->addOutput(); add1->setConnectedNode("in1", constant1); add1->setConnectedNode("in2", constant2); add2->setConnectedNode("in1", constant2); add2->setConnectedNode("in2", image2); add3->setConnectedNode("in1", add1); add3->setConnectedNode("in2", add2); multiply->setConnectedNode("in1", image1); multiply->setConnectedNode("in2", add1); mix->setConnectedNode("fg", multiply); mix->setConnectedNode("bg", add3); mix->setConnectedNode("mask", noise3d); output->setConnectedNode(mix); // Set the node names we want to prune from the graph // and which corresponding input to use for the bypass. std::unordered_map nodesToPrune = { { "add1","in1" }, { "add2","in1" }, { "add3","in1" } }; // Keep track of processed nodes to avoid duplication // of nodes with multiple downstream connections. std::set processedNodes; // Create the new graph with this output and traverse the // original graph upstream to find which nodes to copy. mx::NodeGraphPtr nodeGraph2 = doc->addNodeGraph(); nodeGraph2->addOutput(output->getName()); for (mx::Edge edge : output->traverseGraph()) { mx::NodePtr upstreamNode = edge.getUpstreamElement()->asA(); if (processedNodes.count(upstreamNode)) { // Node is already processed. continue; } // Find the downstream element in the new graph. mx::ElementPtr downstreamElement = edge.getDownstreamElement(); mx::ElementPtr downstreamElement2 = nodeGraph2->getChild(downstreamElement->getName()); if (!downstreamElement2) { // Downstream element has been pruned // so ignore this edge. continue; } // Check if this node should be pruned. // If so we travers upstream using the bypass inputs // until a non-prune node is found. mx::ValuePtr value; while (upstreamNode) { if (!nodesToPrune.count(upstreamNode->getName())) { break; } const std::string& inputName = nodesToPrune[upstreamNode->getName()]; upstreamNode = upstreamNode->getConnectedNode(inputName); } if (upstreamNode) { // Get (or create) the node in the new graph. mx::NodePtr upstreamNode2 = nodeGraph2->getNode(upstreamNode->getName()); if (!upstreamNode2) { upstreamNode2 = nodeGraph2->addNode(upstreamNode->getCategory(), upstreamNode->getName()); upstreamNode2->copyContentFrom(upstreamNode); } mx::ElementPtr connectingElement = edge.getConnectingElement(); // Connect it to downstream. // The downstream element could be a node or an output. mx::NodePtr downstreamNode2 = downstreamElement2->asA(); mx::OutputPtr downstreamOutput2 = downstreamElement2->asA(); if (downstreamOutput2) { downstreamOutput2->setConnectedNode(upstreamNode2); } else if (downstreamNode2 && connectingElement) { downstreamNode2->setConnectedNode(connectingElement->getName(), upstreamNode2); } } // Mark node as processed. processedNodes.insert(upstreamNode); } // Validate the document. REQUIRE(doc->validate()); // Create a topological order and validate the results. std::vector elemOrder = nodeGraph2->topologicalSort(); REQUIRE(elemOrder.size() == nodeGraph2->getChildren().size()); REQUIRE(isTopologicalOrder(elemOrder)); } TEST_CASE("Organization", "[nodegraph]") { // Create a document. mx::DocumentPtr doc = mx::createDocument(); // Create a node graph with the following structure: // // [constant1] [constant2] [image2] // \ / \ / // [image1] [add1] [add2] // \ / \______ | // [multiply] \__ [add3] [noise3d] // \____________ | ____________/ // [mix] // | // [output] // mx::NodeGraphPtr nodeGraph = doc->addNodeGraph(); mx::NodePtr image1 = nodeGraph->addNode("image"); mx::NodePtr image2 = nodeGraph->addNode("image"); mx::NodePtr multiply = nodeGraph->addNode("multiply"); mx::NodePtr constant1 = nodeGraph->addNode("constant"); mx::NodePtr constant2 = nodeGraph->addNode("constant"); mx::NodePtr add1 = nodeGraph->addNode("add"); mx::NodePtr add2 = nodeGraph->addNode("add"); mx::NodePtr add3 = nodeGraph->addNode("add"); mx::NodePtr noise3d = nodeGraph->addNode("noise3d"); mx::NodePtr mix = nodeGraph->addNode("mix"); mx::OutputPtr output = nodeGraph->addOutput(); add1->setConnectedNode("in1", constant1); add1->setConnectedNode("in2", constant2); add2->setConnectedNode("in1", constant2); add2->setConnectedNode("in2", image2); add3->setConnectedNode("in1", add1); add3->setConnectedNode("in2", add2); multiply->setConnectedNode("in1", image1); multiply->setConnectedNode("in2", add1); mix->setConnectedNode("fg", multiply); mix->setConnectedNode("bg", add3); mix->setConnectedNode("mask", noise3d); output->setConnectedNode(mix); // Create a backdrop element. mx::BackdropPtr backdrop1 = nodeGraph->addBackdrop(); backdrop1->setContainsElements({ constant1, constant2, add1 }); backdrop1->setDocString("Group 1"); backdrop1->setWidth(10.0f); backdrop1->setHeight(20.0f); CHECK(backdrop1->getContainsElements().size() == 3); CHECK(backdrop1->getContainsElements()[0] == constant1); CHECK(backdrop1->getDocString() == "Group 1"); CHECK(backdrop1->getWidth() == 10.0f); CHECK(backdrop1->getHeight() == 20.0f); // Create a second backdrop element. mx::BackdropPtr backdrop2 = nodeGraph->addBackdrop(); backdrop2->setContainsElements({ multiply, noise3d, mix, output }); backdrop2->setDocString("Group 2"); backdrop2->setWidth(30.0f); backdrop2->setHeight(40.0f); CHECK(backdrop2->getContainsElements().size() == 4); CHECK(backdrop2->getContainsElements()[0] == multiply); CHECK(backdrop2->getDocString() == "Group 2"); CHECK(backdrop2->getWidth() == 30.0f); CHECK(backdrop2->getHeight() == 40.0f); // Validate the document. REQUIRE(doc->validate()); // Create and test an invalid contains element. backdrop2->setContainsElements({ nodeGraph }); REQUIRE(!doc->validate()); // Remove backdrops. nodeGraph->removeBackdrop(backdrop1->getName()); nodeGraph->removeBackdrop(backdrop2->getName()); CHECK(nodeGraph->getBackdrops().empty()); } TEST_CASE("Node Definition Creation", "[nodedef]") { mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); mx::DocumentPtr stdlib = mx::createDocument(); mx::loadLibraries({ "libraries" }, searchPath, stdlib); mx::DocumentPtr doc = mx::createDocument(); mx::readFromXmlFile(doc, "resources/Materials/TestSuite/stdlib/definition/definition_from_nodegraph.mtlx", searchPath); doc->setDataLibrary(stdlib); mx::NodeGraphPtr graph = doc->getNodeGraph("test_colorcorrect"); REQUIRE(graph); if (graph) { // Add some input interfaces to the graph for (auto node : graph->getNodes()) { for (mx::InputPtr input : node->getInputs()) { if (!input->getConnectedNode()) { const std::string relativePath = node->getName() + "/" + input->getName(); const std::string interfaceName = graph->createValidChildName(relativePath); mx::InputPtr interfaceInput = graph->addInterfaceName(relativePath, interfaceName); REQUIRE(interfaceInput); if (interfaceInput) { interfaceInput->setAttribute(mx::PortElement::UI_NAME_ATTRIBUTE, node->getName() + " " + input->getName()); interfaceInput->setAttribute(mx::PortElement::UI_FOLDER_ATTRIBUTE, "Common"); } } } } const std::string VERSION1 = "1.0"; const std::string GROUP = "adjustment"; bool isDefaultVersion = false; const std::string NODENAME = graph->getName(); // Create a new functional graph and definition from a compound graph std::string newNodeDefName = doc->createValidChildName("ND_" + graph->getName()); std::string newGraphName = doc->createValidChildName("NG_" + graph->getName()); mx::NodeDefPtr nodeDef = doc->addNodeDefFromGraph(graph, newNodeDefName, NODENAME, newGraphName); REQUIRE(nodeDef != nullptr); nodeDef->setVersionString(VERSION1); nodeDef->setDefaultVersion(isDefaultVersion); nodeDef->setNodeGroup(GROUP); nodeDef->setAttribute(mx::PortElement::UI_NAME_ATTRIBUTE, NODENAME + " Version: " + VERSION1); nodeDef->setDocString("This is version 1 of the definition for the graph: " + newGraphName); // Check validity of new definition REQUIRE(nodeDef->getNodeGroup() == "adjustment"); REQUIRE(nodeDef->getVersionString() == VERSION1); REQUIRE_FALSE(nodeDef->getDefaultVersion()); for (mx::InputPtr origInput : graph->getInputs()) { mx::InputPtr nodeDefInput = nodeDef->getInput(origInput->getName()); REQUIRE(nodeDefInput); REQUIRE(*origInput == *nodeDefInput); } mx::StringSet connectionAttributes = { mx::PortElement::NODE_GRAPH_ATTRIBUTE, mx::PortElement::NODE_NAME_ATTRIBUTE, mx::PortElement::INTERFACE_NAME_ATTRIBUTE, mx::Element::XPOS_ATTRIBUTE, mx::Element::YPOS_ATTRIBUTE }; for (mx::OutputPtr origOutput : graph->getOutputs()) { mx::OutputPtr nodeDefOutput = nodeDef->getOutput(origOutput->getName()); REQUIRE(nodeDefOutput); for (const std::string& attribName : origOutput->getAttributeNames()) { if (connectionAttributes.count(attribName)) { REQUIRE(!nodeDefOutput->hasAttribute(attribName)); continue; } REQUIRE(origOutput->getAttribute(attribName) == nodeDefOutput->getAttribute(attribName)); } } // Check validity of new functional nodegraph mx::NodeGraphPtr newGraph = doc->getNodeGraph(newGraphName); REQUIRE(newGraph != nullptr); REQUIRE(newGraph->getNodeDefString() == newNodeDefName); mx::ConstInterfaceElementPtr decl = newGraph->getDeclaration(); REQUIRE(decl->getName() == nodeDef->getName()); REQUIRE(doc->validate()); // Create the new node mx::NodePtr newInstance = doc->addNode(NODENAME, mx::EMPTY_STRING, mx::MULTI_OUTPUT_TYPE_STRING); REQUIRE(newInstance); // Remove default version attribute from previous definitions for (mx::NodeDefPtr prevNodeDef : doc->getMatchingNodeDefs(NODENAME)) { prevNodeDef->setDefaultVersion(false); } // Add new version const std::string VERSION2 = "2.0"; newGraphName = mx::EMPTY_STRING; newNodeDefName = doc->createValidChildName("ND_" + graph->getName() + "_2"); newGraphName = doc->createValidChildName("NG_" + graph->getName() + "_2"); // Create new default version nodeDef = doc->addNodeDefFromGraph(graph, newNodeDefName + "2", NODENAME, newGraphName); nodeDef->setVersionString(VERSION2); nodeDef->setNodeGroup(GROUP); nodeDef->setDefaultVersion(true); REQUIRE(nodeDef != nullptr); nodeDef->setAttribute(mx::PortElement::UI_NAME_ATTRIBUTE, NODENAME + " Version: " + VERSION2); nodeDef->setDocString("This is version 2 of the definition for the graph: " + newGraphName); // Check that we create the version by default mx::NodePtr newDefault = doc->addNode(NODENAME, mx::EMPTY_STRING, mx::MULTI_OUTPUT_TYPE_STRING); if (newDefault) { nodeDef = newDefault->getNodeDef(); if (nodeDef) REQUIRE(nodeDef->getVersionString() == VERSION2); } std::vector matchingNodeDefs; for (auto docNodeDef : doc->getNodeDefs()) { if (docNodeDef->getNodeString() == NODENAME) { matchingNodeDefs.push_back(docNodeDef); } } bool findDefault = false; for (auto matchingDef : matchingNodeDefs) { if (matchingDef->getDefaultVersion()) { findDefault = true; REQUIRE(matchingDef->getVersionString() == VERSION2); break; } } REQUIRE(findDefault); doc->removeChild(graph->getName()); } REQUIRE(doc->validate()); } TEST_CASE("Set Name Global", "[node, nodegraph]") { mx::DocumentPtr doc = mx::createDocument(); const std::string type = "float"; const std::string new_name = "new_name"; SECTION("node") { // Upstream -> Downstream SECTION("Node within NodeGraph -> NodeGraph output") { mx::NodeGraphPtr parentGraph = doc->addNodeGraph(); mx::NodePtr upstreamNode = parentGraph->addNode("constant", "upstreamNode", type); upstreamNode->addOutput("output", type); mx::PortElementPtr downstreamOutput = parentGraph->addOutput("out", type); downstreamOutput->setNodeName(upstreamNode->getName()); upstreamNode->setNameGlobal(new_name); REQUIRE(upstreamNode->getName() == new_name); REQUIRE(downstreamOutput->getNodeName() == new_name); } SECTION("Free node") { SECTION("Free Node -> Free Node") { mx::NodePtr upstreamNode = doc->addNode("constant", "upstreamNode", type); upstreamNode->addOutput("output", type); mx::NodePtr downStreamNode = doc->addNode("downStreamNode"); mx::InputPtr downstreamInput = downStreamNode->addInput("in", type); downstreamInput->setNodeName(upstreamNode->getName()); SECTION("Update references") { upstreamNode->setNameGlobal(new_name); REQUIRE(upstreamNode->getName() == new_name); REQUIRE(downstreamInput->getNodeName() == new_name); } } SECTION("Free Node -> NodeGraph input") { mx::NodePtr upstreamNode = doc->addNode("constant", "upstreamNode", type); upstreamNode->addOutput("output", type); mx::NodeGraphPtr downstreamGraph = doc->addNodeGraph(); mx::InputPtr downstreamInput = downstreamGraph->addInput("input", type); downstreamInput->setNodeName(upstreamNode->getName()); upstreamNode->setNameGlobal(new_name); REQUIRE(upstreamNode->getName() == new_name); REQUIRE(downstreamInput->getNodeName() == new_name); } } SECTION("Node -> NodeDef output") { mx::NodeGraphPtr compoundNodeGraph = doc->addNodeGraph(); mx::OutputPtr compoundNodeGraphOutput = compoundNodeGraph->addOutput("output", type); mx::NodePtr compoundNodeGraphNode = compoundNodeGraph->addNode("constant", "upstreamNode", type); compoundNodeGraphNode->addOutput("output", type); compoundNodeGraphOutput->setNodeName(compoundNodeGraphNode->getName()); std::string newNodeDefName = doc->createValidChildName("ND_" + compoundNodeGraph->getName()); std::string newGraphName = doc->createValidChildName("NG_" + compoundNodeGraph->getName()); mx::NodeDefPtr nodeDef = doc->addNodeDefFromGraph(compoundNodeGraph, newNodeDefName, "NODENAME", newGraphName); mx::NodeGraphPtr functionalNodeGraph = nodeDef->getImplementation()->asA(); mx::NodePtr upstreamNode = functionalNodeGraph->getChild(compoundNodeGraphNode->getName())->asA(); REQUIRE(upstreamNode); mx::OutputPtr downstreamOutput = functionalNodeGraph->getOutput(compoundNodeGraphOutput->getName()); REQUIRE(downstreamOutput); upstreamNode->setNameGlobal(new_name); REQUIRE(upstreamNode->getName() == new_name); REQUIRE(downstreamOutput->getNodeName() == new_name); } } SECTION("nodegraph") { mx::NodeGraphPtr upstreamGraph = doc->addNodeGraph(); mx::NodePtr upstreamGraphNode = upstreamGraph->addNode("constant", "upstreamNode", type); mx::OutputPtr upstreamGraphOutput = upstreamGraph->addOutput("output", type); upstreamGraphOutput->setNodeName(upstreamGraphNode->getName()); SECTION("Node Graph -> Node") { mx::NodePtr downstreamNode = doc->addNode("constant", "downstreamNode", type); mx::InputPtr downstreamInput = downstreamNode->addInput("input", type); downstreamInput->setNodeGraphString(upstreamGraph->getName()); downstreamInput->setOutputString(upstreamGraphOutput->getName()); upstreamGraph->setNameGlobal(new_name); REQUIRE(upstreamGraph->getName() == new_name); REQUIRE(downstreamInput->getNodeGraphString() == new_name); } SECTION("Node Graph -> Node Graph") { mx::NodeGraphPtr downstreamGraph = doc->addNodeGraph(); mx::InputPtr downstreamInput = downstreamGraph->addInput("input", type); downstreamInput->setNodeGraphString(upstreamGraph->getName()); downstreamInput->setOutputString(upstreamGraphOutput->getName()); upstreamGraph->setNameGlobal(new_name); REQUIRE(upstreamGraph->getName() == new_name); REQUIRE(downstreamInput->getNodeGraphString() == new_name); } } }