Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Element Equivalence Interfaces #2003

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
97c9ff2
Working version of isEquivalent().
kwokcb Sep 4, 2024
59bacd8
Add option to perform literal string vs value comparisons.
kwokcb Sep 4, 2024
a5a3627
Review updates:
kwokcb Sep 5, 2024
7791d18
Add in underered child testing. Refine result struct.
kwokcb Sep 6, 2024
126600e
Fix test
kwokcb Sep 7, 2024
3104481
Merge branch 'main' into value_equivalent
kwokcb Sep 7, 2024
ba70b52
Remove attribute order option. Add in functional graph ordering check.
kwokcb Sep 8, 2024
bb7507d
Remove extra newline
jstone-lucasfilm Sep 25, 2024
2216af5
Review fixes.
kwokcb Sep 27, 2024
e8a71cf
Review updates.
kwokcb Sep 28, 2024
d33cb84
Merge branch 'main' into value_equivalent
kwokcb Sep 30, 2024
5c48623
Merge branch 'main' into value_equivalent
jstone-lucasfilm Oct 1, 2024
eab74b1
Add Python bindings for new APIs.
kwokcb Oct 3, 2024
af3c1fc
Merge branch 'main' into value_equivalent
jstone-lucasfilm Oct 3, 2024
160c7b1
Minor spelling fixes
jstone-lucasfilm Oct 3, 2024
eb9c6f7
Remove extra header include
jstone-lucasfilm Oct 3, 2024
ec8342f
Minor formatting fixes
jstone-lucasfilm Oct 3, 2024
4eb7ffe
Review fixes:
kwokcb Oct 4, 2024
e1f6e4f
Group constants for clarity
jstone-lucasfilm Oct 4, 2024
7733ec8
Review update
kwokcb Oct 5, 2024
e258aa7
Merge branch 'main' into value_equivalent
jstone-lucasfilm Oct 6, 2024
4e40efd
Remove extra line
jstone-lucasfilm Oct 6, 2024
bbc7dbe
Refactor header for clarity
jstone-lucasfilm Oct 6, 2024
a990049
Remove extra line
jstone-lucasfilm Oct 6, 2024
734b567
Reorder for clarity
jstone-lucasfilm Oct 6, 2024
11d8a01
Reorder for clarity
jstone-lucasfilm Oct 6, 2024
9dd9ef3
Clarify language
jstone-lucasfilm Oct 6, 2024
f9744ef
Reorder for clarity
jstone-lucasfilm Oct 6, 2024
7b58cea
Remove duplicate qualifier
jstone-lucasfilm Oct 6, 2024
fb24722
Minor spelling fix
jstone-lucasfilm Oct 6, 2024
8ee6a3b
Remove erronesous extra declaration.
kwokcb Oct 6, 2024
def7db6
Simplify using range-based for loop
jstone-lucasfilm Oct 7, 2024
fbab4bc
Clarify variable names and docs
jstone-lucasfilm Oct 7, 2024
1784427
Clarify variable names
jstone-lucasfilm Oct 7, 2024
5ab964b
Use consistent order in C++/Python
jstone-lucasfilm Oct 7, 2024
9609d1d
Minor reorder for clarity
jstone-lucasfilm Oct 7, 2024
bb9d8be
Minor reorder for clarity
jstone-lucasfilm Oct 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions source/MaterialXCore/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ 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";
const string ElementEquivalenceResult::ATTRIBUTE = "attribute";
const string ElementEquivalenceResult::ATTRIBUTE_NAMES = "attribute names";
const string ElementEquivalenceResult::CHILD_COUNT = "child count";
const string ElementEquivalenceResult::CHILD_NAME = "child name";
const string ElementEquivalenceResult::NAME = "name";
const string ElementEquivalenceResult::CATEGORY = "category";

Element::CreatorMap Element::_creatorMap;

Expand Down Expand Up @@ -334,6 +340,108 @@ bool Element::hasInheritanceCycle() const
return false;
}

bool Element::isEquivalent(ConstElementPtr rhs, const ElementEquivalenceOptions& options,
ElementEquivalenceResultVec* results) const
{
if (getName() != rhs->getName())
{
if (results)
results->push_back(ElementEquivalenceResult(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::NAME));
return false;
}
if (getCategory() != rhs->getCategory())
{
if (results)
results->push_back(ElementEquivalenceResult(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::CATEGORY));
return false;
}

// Compare attribute names.
StringVec attributeNames = getAttributeNames();
StringVec rhsAttributeNames = rhs->getAttributeNames();

// Filter out any attributes specified in the options.
const StringSet& skipAttributes = options.skipAttributes;
if (!skipAttributes.empty())
{
attributeNames.erase(std::remove_if(attributeNames.begin(), attributeNames.end(),
[&skipAttributes](const string& attr) { return skipAttributes.find(attr) != skipAttributes.end(); }),
attributeNames.end());
rhsAttributeNames.erase(std::remove_if(rhsAttributeNames.begin(), rhsAttributeNames.end(),
[&skipAttributes](const string& attr) { return skipAttributes.find(attr) != skipAttributes.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 (results)
results->push_back(ElementEquivalenceResult(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::ATTRIBUTE_NAMES));
return false;
}

for (const string& attr : rhsAttributeNames)
{
if (!isAttributeEquivalent(rhs, attr, options, results))
{
return false;
}
}

// Compare children.
const vector<ElementPtr>& children = getChildren();
const vector<ElementPtr>& rhsChildren = rhs->getChildren();
if (children.size() != rhsChildren.size())
{
if (results)
results->push_back(ElementEquivalenceResult(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::CHILD_COUNT));
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 (results)
results->push_back(ElementEquivalenceResult(children[i]->getNamePath(), "<NONE>",
ElementEquivalenceResult::CHILD_NAME, childName));
return false;
}
}
}
if (!children[i]->isEquivalent(rhsElement, options, results))
return false;
}
return true;
}

bool Element::isAttributeEquivalent(ConstElementPtr rhs, const string& attributeName,
const ElementEquivalenceOptions& /*options*/, ElementEquivalenceResultVec* results) const
{
if (getAttribute(attributeName) != rhs->getAttribute(attributeName))
{
if (results)
results->push_back(ElementEquivalenceResult(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::ATTRIBUTE, attributeName));
return false;
}
return true;
}

TreeIterator Element::traverseTree() const
{
return TreeIterator(getSelfNonConst());
Expand Down Expand Up @@ -534,6 +642,72 @@ const string& ValueElement::getActiveUnit() const
return EMPTY_STRING;
}

bool ValueElement::isAttributeEquivalent(ConstElementPtr rhs, const string& attributeName,
const ElementEquivalenceOptions& options, ElementEquivalenceResultVec* results) const
{
// Perform value comparisons
bool performedValueComparison = false;
if (!options.skipValueComparisons)
{
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.format, options.precision);

ConstValueElementPtr rhsValueElement = rhs->asA<ValueElement>();

// Check value equality
if (attributeName == ValueElement::VALUE_ATTRIBUTE)
{
ValuePtr thisValue = getValue();
ValuePtr rhsValue = rhsValueElement->getValue();
if (thisValue && rhsValue)
{
if (thisValue->getValueString() != rhsValue->getValueString())
{
if (results)
results->push_back(ElementEquivalenceResult(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::ATTRIBUTE, attributeName));
return false;
}
}
performedValueComparison = true;
}

// Check ui attribute value equality
else if (uiAttributes.find(attributeName) != uiAttributes.end())
{
const string& uiAttribute = getAttribute(attributeName);
const string& rhsUiAttribute = 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 (results)
results->push_back(ElementEquivalenceResult(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::ATTRIBUTE, attributeName));
return false;
}
}

performedValueComparison = true;
}
}

// If did not peform a value comparison, perform the default comparison
if (!performedValueComparison)
{
return Element::isAttributeEquivalent(rhs, attributeName, options, results);
}

return true;
}

bool ValueElement::validate(string* message) const
{
bool res = true;
Expand Down
110 changes: 110 additions & 0 deletions source/MaterialXCore/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ using ElementMap = std::unordered_map<string, ElementPtr>;
/// A standard function taking an ElementPtr and returning a boolean.
using ElementPredicate = std::function<bool(ConstElementPtr)>;

class ElementEquivalenceOptions;
class ElementEquivalenceResult;
using ElementEquivalenceResultVec = vector<ElementEquivalenceResult>;

/// @class Element
/// The base class for MaterialX elements.
///
Expand Down Expand Up @@ -612,6 +616,31 @@ class MX_CORE_API Element : public std::enable_shared_from_this<Element>
return nullptr;
}

/// @}
/// @name Functional Equivalence
/// @{

/// Return true if the given element tree, including all descendents,
/// is considered to be equivalent to this one based on the equivalence
/// criteria provided.
/// @param rhs Element to compare against
/// @param options Equivalence criteria
/// @param results Results of comparison if argument is specified.
/// @return True if the elements are equivalent. False otherwise.
bool isEquivalent(ConstElementPtr rhs, const ElementEquivalenceOptions& options,
ElementEquivalenceResultVec* results = nullptr) const;

/// Return true if the attribute on a given element is equivalent
/// based on the equivalence criteria provided.
/// @param rhs Element to compare against
/// @param attributeName Name of attribute to compare
/// @param options Equivalence criteria
/// @param results Results of comparison if argument is specified.
/// @return True if the attribute on the elements are equivalent. False otherwise.
virtual bool isAttributeEquivalent(ConstElementPtr rhs, const string& attributeName,
const ElementEquivalenceOptions& options,
ElementEquivalenceResultVec* results = nullptr) const;

/// @}
/// @name Traversal
/// @{
Expand Down Expand Up @@ -1114,6 +1143,21 @@ class MX_CORE_API ValueElement : public TypedElement
return getTypedAttribute<bool>(UNIFORM_ATTRIBUTE);
}

/// @}
/// @name Functional Equivalence
/// @{

/// Return true if the attribute on a given element is equivalent
/// based on the equivalence criteria provided.
/// @param rhs Element to compare against
/// @param attributeName Name of attribute to compare
/// @param options Equivalence criteria
/// @param results Results of comparison if argument is specified.
/// @return True if the attribute on the elements are equivalent. False otherwise.
bool isAttributeEquivalent(ConstElementPtr rhs, const string& attributeName,
const ElementEquivalenceOptions& options,
ElementEquivalenceResultVec* results = nullptr) const override;

/// @}
/// @name Validation
/// @{
Expand Down Expand Up @@ -1336,6 +1380,72 @@ class MX_CORE_API StringResolver
StringMap _geomNameMap;
};

/// @class ElementEquivalenceResult
/// A comparison result for the functional equivalence of two elements.
class MX_CORE_API ElementEquivalenceResult
{
public:
ElementEquivalenceResult(const string& p1, const string& p2, const string& type,
const string& attrName = EMPTY_STRING)
{
path1 = p1;
path2 = p2;
differenceType = type;
attributeName = attrName;
}
ElementEquivalenceResult() = delete;
~ElementEquivalenceResult() = default;

string path1;
string path2;
string differenceType;
string attributeName;

static const string ATTRIBUTE;
static const string ATTRIBUTE_NAMES;
static const string CHILD_COUNT;
static const string CHILD_NAME;
static const string NAME;
static const string CATEGORY;
};

/// @class ElementEquivalenceOptions
/// A set of options for comparing the functional equivalence of elements.
class MX_CORE_API ElementEquivalenceOptions
{
public:
ElementEquivalenceOptions()
{
format = Value::getFloatFormat();
precision = Value::getFloatPrecision();
skipAttributes = {};
skipValueComparisons = false;
};
~ElementEquivalenceOptions() { }

/// Floating point format option for floating point value comparisons
Value::FloatFormat format;

/// Floating point precision option for floating point value comparisons
int precision;

/// Attribute filtering options. By default all attributes are considered.
/// Name, category attributes cannot be skipped.
///
/// For example UI attribute comparision be skipped by setting:
/// skipAttributes = {
/// ValueElement::UI_MIN_ATTRIBUTE, ValueElement::UI_MAX_ATTRIBUTE,
/// ValueElement::UI_SOFT_MIN_ATTRIBUTE, ValueElement::UI_SOFT_MAX_ATTRIBUTE,
/// ValueElement::UI_STEP_ATTRIBUTE, Element::XPOS_ATTRIBUTE,
/// Element::YPOS_ATTRIBUTE };
StringSet skipAttributes;

/// Do not perform any value comparisions. Instead perform exact string comparisons for attributes
/// Default is false. The operator==() method can be used instead as it always performs
/// a strict comparison. Default is false.
bool skipValueComparisons;
};

/// @class ExceptionOrphanedElement
/// An exception that is thrown when an ElementPtr is used after its owning
/// Document has gone out of scope.
Expand Down
Loading
Loading