// // Copyright Contributors to the MaterialX Project // SPDX-License-Identifier: Apache-2.0 // #include #include #include #include MATERIALX_NAMESPACE_BEGIN const string PortElement::NODE_NAME_ATTRIBUTE = "nodename"; const string PortElement::NODE_GRAPH_ATTRIBUTE = "nodegraph"; const string PortElement::OUTPUT_ATTRIBUTE = "output"; const string InterfaceElement::NODE_DEF_ATTRIBUTE = "nodedef"; const string InterfaceElement::TARGET_ATTRIBUTE = "target"; const string InterfaceElement::VERSION_ATTRIBUTE = "version"; const string InterfaceElement::DEFAULT_VERSION_ATTRIBUTE = "isdefaultversion"; const string Input::DEFAULT_GEOM_PROP_ATTRIBUTE = "defaultgeomprop"; const string Input::HINT_ATTRIBUTE = "hint"; const string Input::TRANSPARENCY_HINT = "transparency"; const string Input::OPACITY_HINT = "opacity"; const string Input::ANISOTROPY_HINT = "anisotropy"; const string Output::DEFAULT_INPUT_ATTRIBUTE = "defaultinput"; // // PortElement methods // void PortElement::setConnectedNode(ConstNodePtr node) { if (node) { setNodeName(node->getName()); removeAttribute(VALUE_ATTRIBUTE); removeAttribute(NODE_GRAPH_ATTRIBUTE); removeAttribute(INTERFACE_NAME_ATTRIBUTE); } else { removeAttribute(NODE_NAME_ATTRIBUTE); } } NodePtr PortElement::getConnectedNode() const { ConstGraphElementPtr graph = getAncestorOfType(); return graph ? graph->getNode(getNodeName()) : nullptr; } void PortElement::setConnectedOutput(ConstOutputPtr output) { if (output) { setOutputString(output->getName()); ConstElementPtr parent = output->getParent(); if (parent->isA()) { setNodeGraphString(parent->getName()); removeAttribute(NODE_NAME_ATTRIBUTE); } else if (parent->isA()) { setNodeName(parent->getName()); removeAttribute(NODE_GRAPH_ATTRIBUTE); } removeAttribute(VALUE_ATTRIBUTE); } else { removeAttribute(OUTPUT_ATTRIBUTE); removeAttribute(NODE_GRAPH_ATTRIBUTE); removeAttribute(NODE_NAME_ATTRIBUTE); } } OutputPtr PortElement::getConnectedOutput() const { const string& outputString = getOutputString(); OutputPtr result = nullptr; // Determine the scope at which the connected output may be found. ConstElementPtr parent = getParent(); ConstElementPtr scope = parent ? parent->getParent() : nullptr; // Look for a nodegraph output. if (hasNodeGraphString()) { NodeGraphPtr nodeGraph = resolveNameReference(getNodeGraphString(), scope); if (!nodeGraph) { nodeGraph = resolveNameReference(getNodeGraphString()); } if (nodeGraph) { std::vector outputs = nodeGraph->getOutputs(); if (!outputs.empty()) { if (outputString.empty()) { result = outputs[0]; } else { result = nodeGraph->getOutput(outputString); } } } } // Look for a node output. else if (hasNodeName()) { const string& nodeName = getNodeName(); NodePtr node = resolveNameReference(nodeName, scope); if (!node) { node = resolveNameReference(nodeName); } if (node) { std::vector outputs = node->getOutputs(); if (!outputs.empty()) { if (outputString.empty()) { result = outputs[0]; } else { result = node->getOutput(outputString); } } } } // Look for an output at document scope. if (!result) { result = getDocument()->getOutput(outputString); } return result; } bool PortElement::validate(string* message) const { bool res = true; NodePtr connectedNode = getConnectedNode(); if (hasNodeName() || hasOutputString()) { NodeGraphPtr nodeGraph = resolveNameReference(getNodeName()); if (!nodeGraph) { validateRequire(connectedNode != nullptr, res, message, "Invalid port connection"); } } if (connectedNode) { const string& outputString = getOutputString(); if (!outputString.empty()) { OutputPtr output; if (hasNodeName()) { NodeDefPtr nodeDef = connectedNode->getNodeDef(); output = nodeDef ? nodeDef->getOutput(outputString) : OutputPtr(); if (output) { validateRequire(connectedNode->getType() == MULTI_OUTPUT_TYPE_STRING, res, message, "Multi-output type expected in port connection"); } } else if (hasNodeGraphString()) { NodeGraphPtr nodeGraph = resolveNameReference(getNodeGraphString()); if (nodeGraph) { output = nodeGraph->getOutput(outputString); if (nodeGraph->getNodeDef()) { validateRequire(nodeGraph->getOutputCount() > 1, res, message, "Multi-output type expected in port connection"); } } } else { // Document has no concept of a multioutput type output = getDocument()->getOutput(outputString); } validateRequire(output != nullptr, res, message, "No output found for port connection"); if (output) { validateRequire(getType() == output->getType(), res, message, "Mismatched types in port connection"); } } else if (connectedNode->getType() != MULTI_OUTPUT_TYPE_STRING) { validateRequire(getType() == connectedNode->getType(), res, message, "Mismatched types in port connection"); } } return ValueElement::validate(message) && res; } // // Input methods // NodePtr Input::getConnectedNode() const { // Handle interface name references. InputPtr graphInput = getInterfaceInput(); if (graphInput && (graphInput->hasNodeName() || graphInput->hasNodeGraphString())) { return graphInput->getConnectedNode(); } // Handle inputs of compound nodegraphs. if (getParent()->isA()) { NodePtr rootNode = getDocument()->getNode(getNodeName()); if (rootNode) { return rootNode; } } // Handle transitive connections via outputs. OutputPtr output = getConnectedOutput(); if (output) { NodePtr node = output->getConnectedNode(); if (node) { return node; } } return PortElement::getConnectedNode(); } void Input::setConnectedInterfaceName(const string& interfaceName) { if (!interfaceName.empty()) { ConstGraphElementPtr graph = getAncestorOfType(); if (graph && graph->getInput(interfaceName)) { setInterfaceName(interfaceName); removeAttribute(VALUE_ATTRIBUTE); removeAttribute(OUTPUT_ATTRIBUTE); removeAttribute(NODE_GRAPH_ATTRIBUTE); removeAttribute(NODE_NAME_ATTRIBUTE); } } else { removeAttribute(INTERFACE_NAME_ATTRIBUTE); } } InputPtr Input::getInterfaceInput() const { if (hasInterfaceName()) { ConstGraphElementPtr graph = getAncestorOfType(); if (graph) { return graph->getInput(getInterfaceName()); } } return nullptr; } GeomPropDefPtr Input::getDefaultGeomProp() const { const string& defaultGeomProp = getAttribute(DEFAULT_GEOM_PROP_ATTRIBUTE); if (!defaultGeomProp.empty()) { ConstDocumentPtr doc = getDocument(); return doc->getChildOfType(defaultGeomProp); } return nullptr; } bool Input::validate(string* message) const { bool res = true; ConstElementPtr parent = getParent(); if (hasDefaultGeomPropString()) { validateRequire(parent->isA() || parent->isA(), res, message, "Invalid defaultgeomprop on non-definition and non-nodegraph input"); validateRequire(getDefaultGeomProp() != nullptr, res, message, "Invalid defaultgeomprop string"); } if (parent->isA()) { int numBindings = 0; if (hasValue()) numBindings++; if (hasNodeName()) numBindings++; if (hasNodeGraphString()) numBindings++; if (hasInterfaceName()) numBindings++; if (hasOutputString() && !(hasNodeName() || hasNodeGraphString())) numBindings++; validateRequire(numBindings, res, message, "Node input binds no value or connection"); validateRequire(numBindings <= 1, res, message, "Node input has too many bindings"); } else if (parent->isA()) { validateRequire(parent->asA()->getNodeDef() == nullptr, res, message, "Input element in a functional nodegraph has no effect"); } return PortElement::validate(message) && res; } // // Output methods // Edge Output::getUpstreamEdge(size_t index) const { if (index < getUpstreamEdgeCount()) { return Edge(getSelfNonConst(), nullptr, getConnectedNode()); } return getNullEdge(); } bool Output::hasUpstreamCycle() const { try { for (Edge edge : traverseGraph()) { } } catch (ExceptionFoundCycle&) { return true; } return false; } bool Output::validate(string* message) const { bool res = true; validateRequire(!hasUpstreamCycle(), res, message, "Cycle in upstream path"); return PortElement::validate(message) && res; } // // InterfaceElement methods // InputPtr InterfaceElement::getActiveInput(const string& name) const { for (ConstElementPtr elem : traverseInheritance()) { InputPtr input = elem->asA()->getInput(name); if (input) { return input; } } return nullptr; } vector InterfaceElement::getActiveInputs() const { vector activeInputs; StringSet activeInputNamesSet; for (ConstElementPtr elem : traverseInheritance()) { vector inputs = elem->asA()->getInputs(); for (const InputPtr& input : inputs) { if (input && activeInputNamesSet.insert(input->getName()).second) { activeInputs.push_back(input); } } } return activeInputs; } OutputPtr InterfaceElement::getActiveOutput(const string& name) const { for (ConstElementPtr elem : traverseInheritance()) { OutputPtr output = elem->asA()->getOutput(name); if (output) { return output; } } return nullptr; } vector InterfaceElement::getActiveOutputs() const { vector activeOutputs; StringSet activeOutputNamesSet; for (ConstElementPtr elem : traverseInheritance()) { vector outputs = elem->asA()->getOutputs(); for (const OutputPtr& output : outputs) { if (output && activeOutputNamesSet.insert(output->getName()).second) { activeOutputs.push_back(output); } } } return activeOutputs; } void InterfaceElement::setConnectedOutput(const string& inputName, OutputPtr output) { InputPtr input = getInput(inputName); if (!input) { input = addInput(inputName); } if (output) { input->setType(output->getType()); } input->setConnectedOutput(output); } OutputPtr InterfaceElement::getConnectedOutput(const string& inputName) const { InputPtr input = getInput(inputName); if (!input) { return OutputPtr(); } return input->getConnectedOutput(); } TokenPtr InterfaceElement::getActiveToken(const string& name) const { for (ConstElementPtr elem : traverseInheritance()) { TokenPtr token = elem->asA()->getToken(name); if (token) { return token; } } return nullptr; } vector InterfaceElement::getActiveTokens() const { vector activeTokens; for (ConstElementPtr elem : traverseInheritance()) { vector tokens = elem->asA()->getTokens(); activeTokens.insert(activeTokens.end(), tokens.begin(), tokens.end()); } return activeTokens; } ValueElementPtr InterfaceElement::getActiveValueElement(const string& name) const { for (ConstElementPtr interface : traverseInheritance()) { ValueElementPtr valueElem = interface->getChildOfType(name); if (valueElem) { return valueElem; } } return nullptr; } vector InterfaceElement::getActiveValueElements() const { vector activeValueElems; StringSet activeValueElemNamesSet; for (ConstElementPtr interface : traverseInheritance()) { vector valueElems = interface->getChildrenOfType(); for (const ValueElementPtr& valueElem : valueElems) { if (valueElem && activeValueElemNamesSet.insert(valueElem->getName()).second) { activeValueElems.push_back(valueElem); } } } return activeValueElems; } ValuePtr InterfaceElement::getInputValue(const string& name, const string& target) const { InputPtr input = getInput(name); if (input) { return input->getValue(); } // Return the value, if any, stored in our declaration. ConstInterfaceElementPtr decl = getDeclaration(target); if (decl) { input = decl->getInput(name); if (input) { return input->getValue(); } } return ValuePtr(); } void InterfaceElement::setVersionIntegers(int majorVersion, int minorVersion) { string versionString = std::to_string(majorVersion) + "." + std::to_string(minorVersion); setVersionString(versionString); } std::pair InterfaceElement::getVersionIntegers() const { const string& versionString = getVersionString(); StringVec splitVersion = splitString(versionString, "."); try { if (splitVersion.size() == 2) { return { std::stoi(splitVersion[0]), std::stoi(splitVersion[1]) }; } else if (splitVersion.size() == 1) { return { std::stoi(splitVersion[0]), 0 }; } } catch (std::invalid_argument&) { } catch (std::out_of_range&) { } return { 0, 0 }; } void InterfaceElement::registerChildElement(ElementPtr child) { TypedElement::registerChildElement(child); if (child->isA()) { _inputCount++; } else if (child->isA()) { _outputCount++; } } void InterfaceElement::unregisterChildElement(ElementPtr child) { TypedElement::unregisterChildElement(child); if (child->isA()) { _inputCount--; } else if (child->isA()) { _outputCount--; } } ConstInterfaceElementPtr InterfaceElement::getDeclaration(const string&) const { return InterfaceElementPtr(); } void InterfaceElement::clearContent() { _inputCount = 0; _outputCount = 0; TypedElement::clearContent(); } bool InterfaceElement::hasExactInputMatch(ConstInterfaceElementPtr declaration, string* message) const { for (InputPtr input : getActiveInputs()) { InputPtr declarationInput = declaration->getActiveInput(input->getName()); if (!declarationInput || declarationInput->getType() != input->getType()) { if (message) { *message += "Input '" + input->getName() + "' doesn't match declaration"; } return false; } } return true; } MATERIALX_NAMESPACE_END