mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-25 06:04:57 +00:00
1020 lines
30 KiB
C++
1020 lines
30 KiB
C++
//
|
|
// Copyright Contributors to the MaterialX Project
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
#include <MaterialXCore/Element.h>
|
|
|
|
#include <MaterialXCore/Document.h>
|
|
#include <MaterialXCore/Util.h>
|
|
|
|
#include <iterator>
|
|
|
|
MATERIALX_NAMESPACE_BEGIN
|
|
|
|
const string Element::NAME_ATTRIBUTE = "name";
|
|
const string Element::FILE_PREFIX_ATTRIBUTE = "fileprefix";
|
|
const string Element::GEOM_PREFIX_ATTRIBUTE = "geomprefix";
|
|
const string Element::COLOR_SPACE_ATTRIBUTE = "colorspace";
|
|
const string Element::INHERIT_ATTRIBUTE = "inherit";
|
|
const string Element::NAMESPACE_ATTRIBUTE = "namespace";
|
|
const string Element::DOC_ATTRIBUTE = "doc";
|
|
const string Element::XPOS_ATTRIBUTE = "xpos";
|
|
const string Element::YPOS_ATTRIBUTE = "ypos";
|
|
const string TypedElement::TYPE_ATTRIBUTE = "type";
|
|
const string ValueElement::VALUE_ATTRIBUTE = "value";
|
|
const string ValueElement::INTERFACE_NAME_ATTRIBUTE = "interfacename";
|
|
const string ValueElement::ENUM_ATTRIBUTE = "enum";
|
|
const string ValueElement::IMPLEMENTATION_NAME_ATTRIBUTE = "implname";
|
|
const string ValueElement::IMPLEMENTATION_TYPE_ATTRIBUTE = "impltype";
|
|
const string ValueElement::ENUM_VALUES_ATTRIBUTE = "enumvalues";
|
|
const string ValueElement::UI_NAME_ATTRIBUTE = "uiname";
|
|
const string ValueElement::UI_FOLDER_ATTRIBUTE = "uifolder";
|
|
const string ValueElement::UI_MIN_ATTRIBUTE = "uimin";
|
|
const string ValueElement::UI_MAX_ATTRIBUTE = "uimax";
|
|
const string ValueElement::UI_SOFT_MIN_ATTRIBUTE = "uisoftmin";
|
|
const string ValueElement::UI_SOFT_MAX_ATTRIBUTE = "uisoftmax";
|
|
const string ValueElement::UI_STEP_ATTRIBUTE = "uistep";
|
|
const string ValueElement::UI_ADVANCED_ATTRIBUTE = "uiadvanced";
|
|
const string ValueElement::UNIT_ATTRIBUTE = "unit";
|
|
const string ValueElement::UNITTYPE_ATTRIBUTE = "unittype";
|
|
const string ValueElement::UNIFORM_ATTRIBUTE = "uniform";
|
|
|
|
Element::CreatorMap Element::_creatorMap;
|
|
|
|
//
|
|
// Element methods
|
|
//
|
|
|
|
bool Element::operator==(const Element& rhs) const
|
|
{
|
|
if (getCategory() != rhs.getCategory() ||
|
|
getName() != rhs.getName())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Compare attributes.
|
|
if (getAttributeNames() != rhs.getAttributeNames())
|
|
return false;
|
|
for (const string& attr : rhs.getAttributeNames())
|
|
{
|
|
if (getAttribute(attr) != rhs.getAttribute(attr))
|
|
return false;
|
|
}
|
|
|
|
// Compare children.
|
|
const ElementVec& c1 = getChildren();
|
|
const ElementVec& c2 = rhs.getChildren();
|
|
if (c1.size() != c2.size())
|
|
return false;
|
|
for (size_t i = 0; i < c1.size(); i++)
|
|
{
|
|
if (*c1[i] != *c2[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Element::operator!=(const Element& rhs) const
|
|
{
|
|
return !(*this == rhs);
|
|
}
|
|
|
|
void Element::setName(const string& name)
|
|
{
|
|
ElementPtr parent = getParent();
|
|
if (parent && parent->_childMap.count(name) && name != getName())
|
|
{
|
|
throw Exception("Element name is not unique at the given scope: " + name);
|
|
}
|
|
|
|
getDocument()->invalidateCache();
|
|
|
|
if (parent)
|
|
{
|
|
parent->_childMap.erase(getName());
|
|
parent->_childMap[name] = getSelf();
|
|
}
|
|
_name = name;
|
|
}
|
|
|
|
string Element::getNamePath(ConstElementPtr relativeTo) const
|
|
{
|
|
if (!relativeTo)
|
|
{
|
|
relativeTo = getDocument();
|
|
}
|
|
|
|
string res;
|
|
for (ConstElementPtr elem = getSelf(); elem; elem = elem->getParent())
|
|
{
|
|
if (elem == relativeTo)
|
|
{
|
|
break;
|
|
}
|
|
res = res.empty() ? elem->getName() : elem->getName() + NAME_PATH_SEPARATOR + res;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
ElementPtr Element::getDescendant(const string& namePath) const
|
|
{
|
|
const StringVec nameVec = splitString(namePath, NAME_PATH_SEPARATOR);
|
|
ElementPtr elem = getSelfNonConst();
|
|
for (const string& name : nameVec)
|
|
{
|
|
elem = elem->getChild(name);
|
|
if (!elem)
|
|
{
|
|
return ElementPtr();
|
|
}
|
|
}
|
|
return elem;
|
|
}
|
|
|
|
void Element::registerChildElement(ElementPtr child)
|
|
{
|
|
getDocument()->invalidateCache();
|
|
|
|
_childMap[child->getName()] = child;
|
|
_childOrder.push_back(child);
|
|
}
|
|
|
|
void Element::unregisterChildElement(ElementPtr child)
|
|
{
|
|
getDocument()->invalidateCache();
|
|
|
|
_childMap.erase(child->getName());
|
|
_childOrder.erase(
|
|
std::find(_childOrder.begin(), _childOrder.end(), child));
|
|
}
|
|
|
|
int Element::getChildIndex(const string& name) const
|
|
{
|
|
ElementPtr child = getChild(name);
|
|
ElementVec::const_iterator it = std::find(_childOrder.begin(), _childOrder.end(), child);
|
|
if (it == _childOrder.end())
|
|
{
|
|
return -1;
|
|
}
|
|
return (int) std::distance(_childOrder.begin(), it);
|
|
}
|
|
|
|
void Element::setChildIndex(const string& name, int index)
|
|
{
|
|
ElementPtr child = getChild(name);
|
|
ElementVec::iterator it = std::find(_childOrder.begin(), _childOrder.end(), child);
|
|
if (it == _childOrder.end())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (index < 0 || index >= (int) _childOrder.size())
|
|
{
|
|
throw Exception("Invalid child index");
|
|
}
|
|
|
|
_childOrder.erase(it);
|
|
_childOrder.insert(_childOrder.begin() + (size_t) index, child);
|
|
}
|
|
|
|
void Element::removeChild(const string& name)
|
|
{
|
|
ElementMap::iterator it = _childMap.find(name);
|
|
if (it == _childMap.end())
|
|
{
|
|
return;
|
|
}
|
|
|
|
unregisterChildElement(it->second);
|
|
}
|
|
|
|
void Element::setAttribute(const string& attrib, const string& value)
|
|
{
|
|
getDocument()->invalidateCache();
|
|
|
|
if (!_attributeMap.count(attrib))
|
|
{
|
|
_attributeOrder.push_back(attrib);
|
|
}
|
|
_attributeMap[attrib] = value;
|
|
}
|
|
|
|
void Element::removeAttribute(const string& attrib)
|
|
{
|
|
StringMap::iterator it = _attributeMap.find(attrib);
|
|
if (it != _attributeMap.end())
|
|
{
|
|
getDocument()->invalidateCache();
|
|
|
|
_attributeMap.erase(it);
|
|
_attributeOrder.erase(
|
|
std::find(_attributeOrder.begin(), _attributeOrder.end(), attrib));
|
|
}
|
|
}
|
|
|
|
template <class T> shared_ptr<T> Element::asA()
|
|
{
|
|
return std::dynamic_pointer_cast<T>(getSelf());
|
|
}
|
|
|
|
template <class T> shared_ptr<const T> Element::asA() const
|
|
{
|
|
return std::dynamic_pointer_cast<const T>(getSelf());
|
|
}
|
|
|
|
ElementPtr Element::addChildOfCategory(const string& category, string name)
|
|
{
|
|
if (name.empty())
|
|
{
|
|
name = createValidChildName(category + "1");
|
|
}
|
|
if (_childMap.count(name))
|
|
{
|
|
throw Exception("Child name is not unique: " + name);
|
|
}
|
|
|
|
ElementPtr child;
|
|
|
|
// Check for this category in the creator map.
|
|
CreatorMap::iterator it = _creatorMap.find(category);
|
|
if (it != _creatorMap.end())
|
|
{
|
|
child = it->second(getSelf(), name);
|
|
}
|
|
|
|
// Check for a node within a graph.
|
|
if (!child && isA<GraphElement>())
|
|
{
|
|
child = createElement<Node>(getSelf(), name);
|
|
child->setCategory(category);
|
|
}
|
|
|
|
// If no match was found, then create a generic element.
|
|
if (!child)
|
|
{
|
|
child = createElement<GenericElement>(getSelf(), name);
|
|
child->setCategory(category);
|
|
}
|
|
|
|
registerChildElement(child);
|
|
|
|
return child;
|
|
}
|
|
|
|
ElementPtr Element::changeChildCategory(ElementPtr child, const string& category)
|
|
{
|
|
int childIndex = getChildIndex(child->getName());
|
|
if (childIndex == -1)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
removeChild(child->getName());
|
|
ElementPtr newChild = addChildOfCategory(category, child->getName());
|
|
setChildIndex(child->getName(), childIndex);
|
|
newChild->copyContentFrom(child);
|
|
return newChild;
|
|
}
|
|
|
|
template <class T> shared_ptr<T> Element::getChildOfType(const string& name) const
|
|
{
|
|
ElementPtr child;
|
|
ConstDocumentPtr doc = asA<Document>();
|
|
if (doc && doc->hasDataLibrary())
|
|
{
|
|
child = doc->getDataLibrary()->getChild(name);
|
|
}
|
|
if (!child)
|
|
{
|
|
child = getChild(name);
|
|
}
|
|
return child ? child->asA<T>() : shared_ptr<T>();
|
|
}
|
|
|
|
template <class T> vector<shared_ptr<T>> Element::getChildrenOfType(const string& category) const
|
|
{
|
|
vector<shared_ptr<T>> children;
|
|
ConstDocumentPtr doc = asA<Document>();
|
|
if (doc && doc->hasDataLibrary())
|
|
{
|
|
children = doc->getDataLibrary()->getChildrenOfType<T>(category);
|
|
}
|
|
for (ElementPtr child : _childOrder)
|
|
{
|
|
shared_ptr<T> instance = child->asA<T>();
|
|
if (!instance)
|
|
continue;
|
|
if (!category.empty() && child->getCategory() != category)
|
|
continue;
|
|
children.push_back(instance);
|
|
}
|
|
return children;
|
|
}
|
|
|
|
ElementPtr Element::getRoot()
|
|
{
|
|
ElementPtr root = _root.lock();
|
|
if (!root)
|
|
{
|
|
throw ExceptionOrphanedElement("Requested root of orphaned element: " + asString());
|
|
}
|
|
return root;
|
|
}
|
|
|
|
ConstElementPtr Element::getRoot() const
|
|
{
|
|
ElementPtr root = _root.lock();
|
|
if (!root)
|
|
{
|
|
throw ExceptionOrphanedElement("Requested root of orphaned element: " + asString());
|
|
}
|
|
return root;
|
|
}
|
|
|
|
DocumentPtr Element::getDocument()
|
|
{
|
|
return getRoot()->asA<Document>();
|
|
}
|
|
|
|
/// Return the root document of our tree.
|
|
ConstDocumentPtr Element::getDocument() const
|
|
{
|
|
return getRoot()->asA<Document>();
|
|
}
|
|
|
|
bool Element::hasInheritedBase(ConstElementPtr base) const
|
|
{
|
|
for (ConstElementPtr elem : traverseInheritance())
|
|
{
|
|
if (elem == base)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Element::hasInheritanceCycle() const
|
|
{
|
|
try
|
|
{
|
|
for (ConstElementPtr elem : traverseInheritance()) { }
|
|
}
|
|
catch (ExceptionFoundCycle&)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Element::isEquivalent(ConstElementPtr rhs, const ElementEquivalenceOptions& options, string* message) const
|
|
{
|
|
if (getName() != rhs->getName())
|
|
{
|
|
if (message)
|
|
{
|
|
*message += "Name of " + getNamePath() + " differs from " + rhs->getNamePath() + "\n";
|
|
}
|
|
return false;
|
|
}
|
|
if (getCategory() != rhs->getCategory())
|
|
{
|
|
if (message)
|
|
{
|
|
*message += "Category of " + getNamePath() + " differs from " + rhs->getNamePath() + "\n";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Compare attribute names.
|
|
StringVec attributeNames = getAttributeNames();
|
|
StringVec rhsAttributeNames = rhs->getAttributeNames();
|
|
|
|
// Filter out any attributes specified in the options.
|
|
const StringSet& attributeExclusionList = options.attributeExclusionList;
|
|
if (!attributeExclusionList.empty())
|
|
{
|
|
attributeNames.erase(std::remove_if(attributeNames.begin(), attributeNames.end(),
|
|
[&attributeExclusionList](const string& attr) { return attributeExclusionList.find(attr) != attributeExclusionList.end(); }),
|
|
attributeNames.end());
|
|
rhsAttributeNames.erase(std::remove_if(rhsAttributeNames.begin(), rhsAttributeNames.end(),
|
|
[&attributeExclusionList](const string& attr) { return attributeExclusionList.find(attr) != attributeExclusionList.end(); }),
|
|
rhsAttributeNames.end());
|
|
}
|
|
|
|
// Ignore attribute ordering by sorting names
|
|
std::sort(attributeNames.begin(), attributeNames.end());
|
|
std::sort(rhsAttributeNames.begin(), rhsAttributeNames.end());
|
|
|
|
if (attributeNames != rhsAttributeNames)
|
|
{
|
|
if (message)
|
|
{
|
|
*message += "Attribute names of '" + getNamePath() + "' differ from '" + rhs->getNamePath() + "\n";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
for (const string& attr : rhsAttributeNames)
|
|
{
|
|
if (!isAttributeEquivalent(rhs, attr, options, message))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Compare all child elements that affect functional equivalence.
|
|
ElementVec children;
|
|
for (ElementPtr child : getChildren())
|
|
{
|
|
if (child->getCategory() == CommentElement::CATEGORY)
|
|
{
|
|
continue;
|
|
}
|
|
children.push_back(child);
|
|
}
|
|
ElementVec rhsChildren;
|
|
for (ElementPtr child : rhs->getChildren())
|
|
{
|
|
if (child->getCategory() == CommentElement::CATEGORY)
|
|
{
|
|
continue;
|
|
}
|
|
rhsChildren.push_back(child);
|
|
}
|
|
if (children.size() != rhsChildren.size())
|
|
{
|
|
if (message)
|
|
{
|
|
*message += "Child count of " + getNamePath() + " differs from " + rhs->getNamePath() + "\n";
|
|
}
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < children.size(); i++)
|
|
{
|
|
ElementPtr rhsElement = rhsChildren[i];
|
|
// Handle unordered children if parent is a compound graph (NodeGraph, Document).
|
|
// (Functional graphs have a "nodedef" reference and define node interfaces
|
|
// so require strict interface ordering.)
|
|
ConstGraphElementPtr graph = this->getSelf()->asA<GraphElement>();
|
|
if (graph)
|
|
{
|
|
ConstNodeGraphPtr nodeGraph = graph->asA<NodeGraph>();
|
|
ConstDocumentPtr document = graph->asA<Document>();
|
|
if (document || (nodeGraph && !nodeGraph->getNodeDef()))
|
|
{
|
|
const string& childName = children[i]->getName();
|
|
rhsElement = rhs->getChild(childName);
|
|
if (!rhsElement)
|
|
{
|
|
if (message)
|
|
{
|
|
*message += "Child name `" + childName + "` of " + getNamePath() + " differs from " + rhs->getNamePath() + "\n";
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (!children[i]->isEquivalent(rhsElement, options, message))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Element::isAttributeEquivalent(ConstElementPtr rhs, const string& attributeName,
|
|
const ElementEquivalenceOptions& /*options*/, string* message) const
|
|
{
|
|
if (getAttribute(attributeName) != rhs->getAttribute(attributeName))
|
|
{
|
|
if (message)
|
|
{
|
|
*message += "Attribute name `" + attributeName + "` of " + getNamePath() + " differs from " + rhs->getNamePath() + "\n";
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TreeIterator Element::traverseTree() const
|
|
{
|
|
return TreeIterator(getSelfNonConst());
|
|
}
|
|
|
|
GraphIterator Element::traverseGraph() const
|
|
{
|
|
return GraphIterator(getSelfNonConst());
|
|
}
|
|
|
|
Edge Element::getUpstreamEdge(size_t) const
|
|
{
|
|
return getNullEdge();
|
|
}
|
|
|
|
ElementPtr Element::getUpstreamElement(size_t index) const
|
|
{
|
|
return getUpstreamEdge(index).getUpstreamElement();
|
|
}
|
|
|
|
InheritanceIterator Element::traverseInheritance() const
|
|
{
|
|
return InheritanceIterator(getSelf());
|
|
}
|
|
|
|
void Element::copyContentFrom(const ConstElementPtr& source)
|
|
{
|
|
getDocument()->invalidateCache();
|
|
|
|
_sourceUri = source->_sourceUri;
|
|
_attributeMap = source->_attributeMap;
|
|
_attributeOrder = source->_attributeOrder;
|
|
|
|
for (auto child : source->getChildren())
|
|
{
|
|
const string& name = child->getName();
|
|
|
|
// Check for duplicate elements.
|
|
ConstElementPtr previous = getChild(name);
|
|
if (previous)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Create the copied element.
|
|
ElementPtr childCopy = addChildOfCategory(child->getCategory(), name);
|
|
childCopy->copyContentFrom(child);
|
|
}
|
|
}
|
|
|
|
void Element::clearContent()
|
|
{
|
|
getDocument()->invalidateCache();
|
|
|
|
_sourceUri.clear();
|
|
_attributeMap.clear();
|
|
_attributeOrder.clear();
|
|
_childMap.clear();
|
|
_childOrder.clear();
|
|
}
|
|
|
|
bool Element::validate(string* message) const
|
|
{
|
|
bool res = true;
|
|
validateRequire(isValidName(getName()), res, message, "Invalid element name");
|
|
if (hasInheritString())
|
|
{
|
|
bool validInherit = getInheritsFrom() && getInheritsFrom()->getCategory() == getCategory();
|
|
validateRequire(validInherit, res, message, "Invalid element inheritance");
|
|
}
|
|
for (auto child : getChildren())
|
|
{
|
|
res = child->validate(message) && res;
|
|
}
|
|
validateRequire(!hasInheritanceCycle(), res, message, "Cycle in element inheritance chain");
|
|
return res;
|
|
}
|
|
|
|
StringResolverPtr Element::createStringResolver(const string& geom) const
|
|
{
|
|
StringResolverPtr resolver = StringResolver::create();
|
|
|
|
// Compute file and geom prefixes as this scope.
|
|
resolver->setFilePrefix(getActiveFilePrefix());
|
|
resolver->setGeomPrefix(getActiveGeomPrefix());
|
|
|
|
// If a geometry name is specified, then apply it to the filename map.
|
|
if (!geom.empty())
|
|
{
|
|
for (GeomInfoPtr geomInfo : getDocument()->getGeomInfos())
|
|
{
|
|
if (!geomStringsMatch(geom, geomInfo->getActiveGeom()))
|
|
continue;
|
|
for (TokenPtr token : geomInfo->getTokens())
|
|
{
|
|
string key = "<" + token->getName() + ">";
|
|
string value = token->getResolvedValueString();
|
|
resolver->setFilenameSubstitution(key, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add in token substitutions
|
|
resolver->addTokenSubstitutions(getSelf());
|
|
|
|
return resolver;
|
|
}
|
|
|
|
string Element::asString() const
|
|
{
|
|
string res = "<" + getCategory();
|
|
if (getName() != EMPTY_STRING)
|
|
{
|
|
res += " name=\"" + getName() + "\"";
|
|
}
|
|
for (const string& attrName : getAttributeNames())
|
|
{
|
|
res += " " + attrName + "=\"" + getAttribute(attrName) + "\"";
|
|
}
|
|
res += ">";
|
|
return res;
|
|
}
|
|
|
|
void Element::validateRequire(bool expression, bool& res, string* message, const string& errorDesc) const
|
|
{
|
|
if (!expression)
|
|
{
|
|
res = false;
|
|
if (message)
|
|
{
|
|
*message += errorDesc + ": " + asString() + "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// TypedElement methods
|
|
//
|
|
|
|
TypeDefPtr TypedElement::getTypeDef() const
|
|
{
|
|
return resolveNameReference<TypeDef>(getType());
|
|
}
|
|
|
|
//
|
|
// ValueElement methods
|
|
//
|
|
|
|
string ValueElement::getResolvedValueString(StringResolverPtr resolver) const
|
|
{
|
|
if (!StringResolver::isResolvedType(getType()))
|
|
{
|
|
return getValueString();
|
|
}
|
|
if (!resolver)
|
|
{
|
|
resolver = createStringResolver();
|
|
}
|
|
return resolver->resolve(getValueString(), getType());
|
|
}
|
|
|
|
ValuePtr ValueElement::getValue() const
|
|
{
|
|
if (!hasValue())
|
|
return ValuePtr();
|
|
|
|
return Value::createValueFromStrings(getValueString(), getType(), getDocument()->getTypeDef(getType()));
|
|
}
|
|
|
|
ValuePtr ValueElement::getResolvedValue(StringResolverPtr resolver) const
|
|
{
|
|
if (!hasValue())
|
|
return ValuePtr();
|
|
|
|
return Value::createValueFromStrings(getResolvedValueString(resolver), getType(), getDocument()->getTypeDef(getType()));
|
|
}
|
|
|
|
ValuePtr ValueElement::getDefaultValue() const
|
|
{
|
|
ConstElementPtr parent = getParent();
|
|
ConstInterfaceElementPtr interface = parent ? parent->asA<InterfaceElement>() : nullptr;
|
|
if (interface)
|
|
{
|
|
ConstInterfaceElementPtr decl = interface->getDeclaration();
|
|
if (decl)
|
|
{
|
|
ValueElementPtr value = decl->getActiveValueElement(getName());
|
|
if (value)
|
|
{
|
|
return value->getValue();
|
|
}
|
|
}
|
|
}
|
|
return ValuePtr();
|
|
}
|
|
|
|
const string& ValueElement::getActiveUnit() const
|
|
{
|
|
// Return the unit, if any, stored in our declaration.
|
|
ConstElementPtr parent = getParent();
|
|
ConstInterfaceElementPtr interface = parent ? parent->asA<InterfaceElement>() : nullptr;
|
|
if (interface)
|
|
{
|
|
ConstInterfaceElementPtr decl = interface->getDeclaration();
|
|
if (decl)
|
|
{
|
|
ValueElementPtr value = decl->getActiveValueElement(getName());
|
|
if (value)
|
|
{
|
|
return value->getUnit();
|
|
}
|
|
}
|
|
}
|
|
return EMPTY_STRING;
|
|
}
|
|
|
|
bool ValueElement::isAttributeEquivalent(ConstElementPtr rhs, const string& attributeName,
|
|
const ElementEquivalenceOptions& options, string* message) const
|
|
{
|
|
// Perform value comparisons
|
|
bool performedValueComparison = false;
|
|
if (options.performValueComparisons)
|
|
{
|
|
const StringSet uiAttributes =
|
|
{
|
|
ValueElement::UI_MIN_ATTRIBUTE, ValueElement::UI_MAX_ATTRIBUTE,
|
|
ValueElement::UI_SOFT_MIN_ATTRIBUTE, ValueElement::UI_SOFT_MAX_ATTRIBUTE,
|
|
ValueElement::UI_STEP_ATTRIBUTE
|
|
};
|
|
|
|
// Get precision and format options
|
|
ScopedFloatFormatting fmt(options.floatFormat, options.floatPrecision);
|
|
|
|
// Check value equality
|
|
ConstValueElementPtr rhsValueElement = rhs->asA<ValueElement>();
|
|
if (rhsValueElement && attributeName == ValueElement::VALUE_ATTRIBUTE)
|
|
{
|
|
ValuePtr thisValue = getValue();
|
|
ValuePtr rhsValue = rhsValueElement->getValue();
|
|
if (thisValue && rhsValue)
|
|
{
|
|
if (thisValue->getValueString() != rhsValue->getValueString())
|
|
{
|
|
if (message)
|
|
{
|
|
*message += "Attribute `" + attributeName + "` of " + getNamePath() + " differs from " + rhs->getNamePath() + "\n";
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
performedValueComparison = true;
|
|
}
|
|
|
|
// Check ui attribute value equality
|
|
else if (uiAttributes.find(attributeName) != uiAttributes.end())
|
|
{
|
|
const string& uiAttribute = getAttribute(attributeName);
|
|
const string& rhsUiAttribute = rhs->getAttribute(attributeName);
|
|
ValuePtr uiValue = !rhsUiAttribute.empty() ? Value::createValueFromStrings(uiAttribute, getType()) : nullptr;
|
|
ValuePtr rhsUiValue = !rhsUiAttribute.empty() ? Value::createValueFromStrings(rhsUiAttribute, getType()) : nullptr;
|
|
if (uiValue && rhsUiValue)
|
|
{
|
|
if (uiValue->getValueString() != rhsUiValue->getValueString())
|
|
{
|
|
if (message)
|
|
{
|
|
*message += "Attribute `" + attributeName + "` of " + getNamePath() + " differs from " + rhs->getNamePath() + "\n";
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
performedValueComparison = true;
|
|
}
|
|
}
|
|
|
|
// If did not perform a value comparison, perform the default comparison
|
|
if (!performedValueComparison)
|
|
{
|
|
return Element::isAttributeEquivalent(rhs, attributeName, options, message);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ValueElement::validate(string* message) const
|
|
{
|
|
bool res = true;
|
|
if (hasType() && hasValueString())
|
|
{
|
|
validateRequire(getValue() != nullptr, res, message, "Invalid value");
|
|
}
|
|
|
|
if (hasInterfaceName())
|
|
{
|
|
validateRequire(isA<Input>() || isA<Token>(), res, message, "Only input and token elements support interface names");
|
|
ConstNodeGraphPtr nodeGraph = getAncestorOfType<NodeGraph>();
|
|
ConstInterfaceElementPtr decl = nodeGraph ? nodeGraph->getDeclaration() : nullptr;
|
|
if (decl)
|
|
{
|
|
ValueElementPtr valueElem = decl->getActiveValueElement(getInterfaceName());
|
|
validateRequire(valueElem != nullptr, res, message, "Interface name not found in referenced declaration");
|
|
if (valueElem)
|
|
{
|
|
validateRequire(getType() == valueElem->getType(), res, message, "Interface name refers to value element of a different type");
|
|
}
|
|
}
|
|
}
|
|
|
|
UnitTypeDefPtr unitTypeDef;
|
|
if (hasUnitType())
|
|
{
|
|
const string& unittype = getUnitType();
|
|
if (!unittype.empty())
|
|
{
|
|
unitTypeDef = getDocument()->getUnitTypeDef(unittype);
|
|
validateRequire(unitTypeDef != nullptr, res, message, "Unit type definition does not exist in document");
|
|
}
|
|
}
|
|
if (hasUnit())
|
|
{
|
|
bool foundUnit = false;
|
|
if (unitTypeDef)
|
|
{
|
|
const string& unit = getUnit();
|
|
for (UnitDefPtr unitDef : unitTypeDef->getUnitDefs())
|
|
{
|
|
if (unitDef->getUnit(unit))
|
|
{
|
|
foundUnit = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
validateRequire(foundUnit, res, message, "Unit definition does not exist in document");
|
|
}
|
|
return TypedElement::validate(message) && res;
|
|
}
|
|
|
|
//
|
|
// StringResolver methods
|
|
//
|
|
|
|
void StringResolver::setUdimString(const string& udim)
|
|
{
|
|
setFilenameSubstitution(UDIM_TOKEN, udim);
|
|
}
|
|
|
|
void StringResolver::setUvTileString(const string& uvTile)
|
|
{
|
|
setFilenameSubstitution(UV_TILE_TOKEN, uvTile);
|
|
}
|
|
|
|
void StringResolver::addTokenSubstitutions(ConstElementPtr element)
|
|
{
|
|
const string DELIMITER_PREFIX = "[";
|
|
const string DELIMITER_POSTFIX = "]";
|
|
|
|
// Traverse from siblings up until root is reached.
|
|
// Child tokens override any parent tokens.
|
|
ConstElementPtr parent = element->getParent();
|
|
while (parent)
|
|
{
|
|
ConstInterfaceElementPtr interfaceElem = parent->asA<InterfaceElement>();
|
|
if (interfaceElem)
|
|
{
|
|
vector<TokenPtr> tokens = interfaceElem->getActiveTokens();
|
|
for (auto token : tokens)
|
|
{
|
|
string key = DELIMITER_PREFIX + token->getName() + DELIMITER_POSTFIX;
|
|
string value = token->getResolvedValueString();
|
|
if (!_filenameMap.count(key))
|
|
{
|
|
setFilenameSubstitution(key, value);
|
|
}
|
|
}
|
|
|
|
// This is a functional nodegraph. Get the corresponding nodedef and
|
|
// check for tokens on it.
|
|
ConstNodeGraphPtr nodegraph = parent->asA<NodeGraph>();
|
|
if (nodegraph)
|
|
{
|
|
NodeDefPtr nodedef = nodegraph->getNodeDef();
|
|
if (nodedef)
|
|
{
|
|
tokens = nodedef->getActiveTokens();
|
|
for (auto token : tokens)
|
|
{
|
|
string key = DELIMITER_PREFIX + token->getName() + DELIMITER_POSTFIX;
|
|
string value = token->getResolvedValueString();
|
|
if (!_filenameMap.count(key))
|
|
{
|
|
setFilenameSubstitution(key, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
parent = parent->getParent();
|
|
}
|
|
}
|
|
|
|
string StringResolver::resolve(const string& str, const string& type) const
|
|
{
|
|
if (type == FILENAME_TYPE_STRING)
|
|
{
|
|
return _filePrefix + replaceSubstrings(str, _filenameMap);
|
|
}
|
|
if (type == GEOMNAME_TYPE_STRING)
|
|
{
|
|
return _geomPrefix + replaceSubstrings(str, _geomNameMap);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
//
|
|
// Global functions
|
|
//
|
|
|
|
bool targetStringsMatch(const string& target1, const string& target2)
|
|
{
|
|
if (target1.empty() || target2.empty())
|
|
return true;
|
|
|
|
StringVec vec1 = splitString(target1, ARRAY_VALID_SEPARATORS);
|
|
StringVec vec2 = splitString(target2, ARRAY_VALID_SEPARATORS);
|
|
StringSet set1(vec1.begin(), vec1.end());
|
|
StringSet set2(vec2.begin(), vec2.end());
|
|
|
|
StringSet matches;
|
|
std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(),
|
|
std::inserter(matches, matches.end()));
|
|
return !matches.empty();
|
|
}
|
|
|
|
string prettyPrint(ConstElementPtr elem)
|
|
{
|
|
string text;
|
|
for (TreeIterator it = elem->traverseTree().begin(); it != TreeIterator::end(); ++it)
|
|
{
|
|
string indent(it.getElementDepth() * 2, ' ');
|
|
text += indent + it.getElement()->asString() + "\n";
|
|
}
|
|
return text;
|
|
}
|
|
|
|
//
|
|
// Element registry class
|
|
//
|
|
|
|
template <class T> class ElementRegistry
|
|
{
|
|
public:
|
|
ElementRegistry()
|
|
{
|
|
Element::_creatorMap[T::CATEGORY] = Element::createElement<T>;
|
|
}
|
|
~ElementRegistry() = default;
|
|
};
|
|
|
|
//
|
|
// Template instantiations
|
|
//
|
|
|
|
#define INSTANTIATE_SUBCLASS(T) \
|
|
template MX_CORE_API shared_ptr<T> Element::asA<T>(); \
|
|
template MX_CORE_API shared_ptr<const T> Element::asA<T>() const; \
|
|
template MX_CORE_API shared_ptr<T> Element::getChildOfType<T>(const string& name) const; \
|
|
template MX_CORE_API vector<shared_ptr<T>> Element::getChildrenOfType<T>(const string& category) const;
|
|
|
|
INSTANTIATE_SUBCLASS(Element)
|
|
INSTANTIATE_SUBCLASS(GeomElement)
|
|
INSTANTIATE_SUBCLASS(GraphElement)
|
|
INSTANTIATE_SUBCLASS(InterfaceElement)
|
|
INSTANTIATE_SUBCLASS(PortElement)
|
|
INSTANTIATE_SUBCLASS(TypedElement)
|
|
INSTANTIATE_SUBCLASS(ValueElement)
|
|
|
|
#define INSTANTIATE_CONCRETE_SUBCLASS(T, category) \
|
|
const string T::CATEGORY(category); \
|
|
ElementRegistry<T> registry##T; \
|
|
INSTANTIATE_SUBCLASS(T)
|
|
|
|
INSTANTIATE_CONCRETE_SUBCLASS(AttributeDef, "attributedef")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Backdrop, "backdrop")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Collection, "collection")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(CommentElement, "comment")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Document, "materialx")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(GenericElement, "generic")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(GeomInfo, "geominfo")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(GeomProp, "geomprop")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(GeomPropDef, "geompropdef")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Implementation, "implementation")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Input, "input")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Look, "look")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(LookGroup, "lookgroup")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(MaterialAssign, "materialassign")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Member, "member")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(NewlineElement, "newline")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Node, "node")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(NodeDef, "nodedef")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(NodeGraph, "nodegraph")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Output, "output")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Property, "property")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(PropertyAssign, "propertyassign")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(PropertySet, "propertyset")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(PropertySetAssign, "propertysetassign")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(TargetDef, "targetdef")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Token, "token")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(TypeDef, "typedef")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Unit, "unit")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(UnitDef, "unitdef")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(UnitTypeDef, "unittypedef")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Variant, "variant")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(VariantAssign, "variantassign")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(VariantSet, "variantset")
|
|
INSTANTIATE_CONCRETE_SUBCLASS(Visibility, "visibility")
|
|
|
|
MATERIALX_NAMESPACE_END
|