// // Copyright Contributors to the MaterialX Project // SPDX-License-Identifier: Apache-2.0 // #include #include #include MATERIALX_NAMESPACE_BEGIN const string Document::CMS_ATTRIBUTE = "cms"; const string Document::CMS_CONFIG_ATTRIBUTE = "cmsconfig"; // // Document factory function // DocumentPtr createDocument() { return Document::createDocument(); } // // Document cache // class Document::Cache { public: Cache() : _valid(false) { } ~Cache() = default; void setDocument(weak_ptr document) { std::unique_lock lock(_mutex); _doc = document; _valid = false; } void invalidate() { std::unique_lock lock(_mutex); _valid = false; } vector getMatchingPorts(const string& nodeName) { auto lock = refreshWithLock(); auto it = _portElementMap.find(nodeName); return (it != _portElementMap.end()) ? it->second : vector(); } vector getMatchingNodeDefs(const string& nodeName) { auto lock = refreshWithLock(); auto it = _nodeDefMap.find(nodeName); return (it != _nodeDefMap.end()) ? it->second : vector(); } vector getMatchingImplementations(const string& nodeDef) { auto lock = refreshWithLock(); auto it = _implementationMap.find(nodeDef); return (it != _implementationMap.end()) ? it->second : vector(); } private: std::shared_lock refreshWithLock() { std::shared_lock lock(_mutex); if (_valid) { return lock; } lock.unlock(); { std::unique_lock writeLock(_mutex); if (!_valid) { auto doc = _doc.lock(); if (doc) { rebuild(doc); } } } lock.lock(); return lock; } void rebuild(DocumentPtr doc) { // Clear the existing cache. _portElementMap.clear(); _nodeDefMap.clear(); _implementationMap.clear(); // Traverse the document to build a new cache. for (ElementPtr elem : doc->traverseTree()) { const string& nodeName = elem->getAttribute(PortElement::NODE_NAME_ATTRIBUTE); const string& nodeGraphName = elem->getAttribute(PortElement::NODE_GRAPH_ATTRIBUTE); const string& nodeString = elem->getAttribute(NodeDef::NODE_ATTRIBUTE); const string& nodeDefString = elem->getAttribute(InterfaceElement::NODE_DEF_ATTRIBUTE); const string& portKey = !nodeName.empty() ? nodeName : nodeGraphName; if (!portKey.empty()) { PortElementPtr portElem = elem->asA(); if (portElem) { _portElementMap[portElem->getQualifiedName(portKey)].push_back(portElem); } } if (!nodeString.empty()) { NodeDefPtr nodeDef = elem->asA(); if (nodeDef) { _nodeDefMap[nodeDef->getQualifiedName(nodeString)].push_back(nodeDef); } } if (!nodeDefString.empty()) { InterfaceElementPtr interface = elem->asA(); if (interface) { if (interface->isA() || interface->isA()) { _implementationMap[interface->getQualifiedName(nodeDefString)].push_back(interface); } } } } _valid = true; } private: weak_ptr _doc; mutable std::shared_mutex _mutex; bool _valid; std::unordered_map> _portElementMap; std::unordered_map> _nodeDefMap; std::unordered_map> _implementationMap; }; // // Document methods // Document::Document(ElementPtr parent, const string& name) : GraphElement(parent, CATEGORY, name), _cache(std::make_unique()) { } Document::~Document() { } void Document::initialize() { _root = getSelf(); _cache->setDocument(getDocument()); clearContent(); setVersionIntegers(MATERIALX_MAJOR_VERSION, MATERIALX_MINOR_VERSION); } NodeDefPtr Document::addNodeDefFromGraph(NodeGraphPtr nodeGraph, const string& nodeDefName, const string& category, const string& newGraphName) { if (category.empty()) { throw Exception("Cannot create a nodedef without a category identifier"); } if (getNodeDef(nodeDefName)) { throw Exception("Cannot create duplicate nodedef: " + nodeDefName); } if (getNodeGraph(newGraphName)) { throw Exception("Cannot create duplicate nodegraph: " + newGraphName); } // Create a new functional nodegraph, and copy over the // contents from the compound nodegraph NodeGraphPtr graph = addNodeGraph(newGraphName); graph->copyContentFrom(nodeGraph); for (auto graphChild : graph->getChildren()) { graphChild->removeAttribute(Element::XPOS_ATTRIBUTE); graphChild->removeAttribute(Element::YPOS_ATTRIBUTE); } graph->setNodeDefString(nodeDefName); // Create a new nodedef and set its category NodeDefPtr nodeDef = addNodeDef(nodeDefName, EMPTY_STRING); nodeDef->setNodeString(category); // Expose any existing interfaces from the graph. // Any connection attributes ("nodegraph", "nodename", "interfacename") on the // existing interface should be removed from the definition as well as any source URI. // Attributes which should not be copied over StringSet filterAttributes = { PortElement::NODE_GRAPH_ATTRIBUTE, PortElement::NODE_NAME_ATTRIBUTE, PortElement::INTERFACE_NAME_ATTRIBUTE, Element::XPOS_ATTRIBUTE, Element::YPOS_ATTRIBUTE }; // Transfer input interface from the graph to the nodedef for (InputPtr input : graph->getInputs()) { InputPtr nodeDefInput = nodeDef->addInput(input->getName(), input->getType()); if (nodeDefInput) { nodeDefInput->copyContentFrom(input); for (const string& filterAttribute : filterAttributes) { nodeDefInput->removeAttribute(filterAttribute); } nodeDefInput->setSourceUri(EMPTY_STRING); input->setInterfaceName(nodeDefInput->getName()); } } // Remove interfaces from the nodegraph for (InputPtr input : graph->getInputs()) { graph->removeInput(input->getName()); } // Copy the output interface from the graph to the nodedef for (OutputPtr output : graph->getOutputs()) { OutputPtr nodeDefOutput = nodeDef->addOutput(output->getName(), output->getType()); if (nodeDefOutput) { nodeDefOutput->copyContentFrom(output); for (const string& filterAttribute : filterAttributes) { nodeDefOutput->removeAttribute(filterAttribute); } nodeDefOutput->setSourceUri(EMPTY_STRING); } } return nodeDef; } void Document::importLibrary(const ConstDocumentPtr& library) { if (!library) { return; } for (auto child : library->getChildren()) { if (child->getCategory().empty()) { throw Exception("Trying to import child without a category: " + child->getName()); } const string childName = child->getQualifiedName(child->getName()); // Check for duplicate elements. ConstElementPtr previous = getChild(childName); if (previous) { continue; } // Create the imported element. ElementPtr childCopy = addChildOfCategory(child->getCategory(), childName); childCopy->copyContentFrom(child); if (!childCopy->hasFilePrefix() && library->hasFilePrefix()) { childCopy->setFilePrefix(library->getFilePrefix()); } if (!childCopy->hasGeomPrefix() && library->hasGeomPrefix()) { childCopy->setGeomPrefix(library->getGeomPrefix()); } if (!childCopy->hasColorSpace() && library->hasColorSpace()) { childCopy->setColorSpace(library->getColorSpace()); } if (!childCopy->hasNamespace() && library->hasNamespace()) { childCopy->setNamespace(library->getNamespace()); } if (!childCopy->hasSourceUri() && library->hasSourceUri()) { childCopy->setSourceUri(library->getSourceUri()); } } } StringSet Document::getReferencedSourceUris() const { StringSet sourceUris; for (ElementPtr elem : traverseTree()) { if (elem->hasSourceUri()) { sourceUris.insert(elem->getSourceUri()); } } return sourceUris; } std::pair Document::getVersionIntegers() const { if (!hasVersionString()) { return { MATERIALX_MAJOR_VERSION, MATERIALX_MINOR_VERSION }; } return InterfaceElement::getVersionIntegers(); } vector Document::getMatchingPorts(const string& nodeName) const { return _cache->getMatchingPorts(nodeName); } ValuePtr Document::getGeomPropValue(const string& geomPropName, const string& geom) const { ValuePtr value; for (GeomInfoPtr geomInfo : getGeomInfos()) { if (!geomStringsMatch(geom, geomInfo->getActiveGeom())) { continue; } GeomPropPtr geomProp = geomInfo->getGeomProp(geomPropName); if (geomProp) { value = geomProp->getValue(); } } return value; } vector Document::getMaterialOutputs() const { vector materialOutputs; const string documentUri = getSourceUri(); for (NodeGraphPtr docNodeGraph : getNodeGraphs()) { // Skip nodegraphs which are either definitions or are from an included file. const string graphUri = docNodeGraph->getSourceUri(); if (docNodeGraph->getNodeDef() || (!graphUri.empty() && documentUri != graphUri)) { continue; } vector docNodeGraphOutputs = docNodeGraph->getMaterialOutputs(); if (!docNodeGraphOutputs.empty()) { materialOutputs.insert(materialOutputs.end(), docNodeGraphOutputs.begin(), docNodeGraphOutputs.end()); } } return materialOutputs; } vector Document::getMatchingNodeDefs(const string& nodeName) const { // Recurse to data library if present. vector matchingNodeDefs = hasDataLibrary() ? getDataLibrary()->getMatchingNodeDefs(nodeName) : vector(); // Append all nodedefs matching the given node name. vector localNodeDefs = _cache->getMatchingNodeDefs(nodeName); matchingNodeDefs.insert(matchingNodeDefs.end(), localNodeDefs.begin(), localNodeDefs.end()); return matchingNodeDefs; } vector Document::getMatchingImplementations(const string& nodeDef) const { // Recurse to data library if present. vector matchingImplementations = hasDataLibrary() ? getDataLibrary()->getMatchingImplementations(nodeDef) : vector(); // Append all implementations matching the given nodedef string. vector localImpls = _cache->getMatchingImplementations(nodeDef); matchingImplementations.insert(matchingImplementations.end(), localImpls.begin(), localImpls.end()); return matchingImplementations; } bool Document::validate(string* message) const { bool res = true; std::pair expectedVersion(MATERIALX_MAJOR_VERSION, MATERIALX_MINOR_VERSION); validateRequire(getVersionIntegers() >= expectedVersion, res, message, "Unsupported document version"); validateRequire(getVersionIntegers() <= expectedVersion, res, message, "Future document version"); return GraphElement::validate(message) && res; } void Document::invalidateCache() { _cache->invalidate(); } // // Deprecated methods // NodeDefPtr Document::addNodeDefFromGraph(NodeGraphPtr nodeGraph, const string& nodeDefName, const string& node, const string&, bool, const string&, const string& newGraphName) { return addNodeDefFromGraph(nodeGraph, nodeDefName, node, newGraphName); } MATERIALX_NAMESPACE_END