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 4 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
169 changes: 169 additions & 0 deletions source/MaterialXCore/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ const string ValueElement::UNIFORM_ATTRIBUTE = "uniform";

Element::CreatorMap Element::_creatorMap;

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 methods
//
Expand Down Expand Up @@ -81,6 +89,103 @@ bool Element::operator!=(const Element& rhs) const
return !(*this == rhs);
}

bool Element::isEquivalent(ConstElementPtr rhs, ElementEquivalenceOptions& options,
ElementEquivalenceResult* result) const
{
if (getName() != rhs->getName())
{
if (result)
result->addDifference(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::NAME);
return false;
}
if (getCategory() != rhs->getCategory())
{
if (result)
result->addDifference(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 ordering if option specified.
if (options.ignoreAttributeOrder)
jstone-lucasfilm marked this conversation as resolved.
Show resolved Hide resolved
{
// Sort the string arrays
std::sort(attributeNames.begin(), attributeNames.end());
std::sort(rhsAttributeNames.begin(), rhsAttributeNames.end());
}
if (attributeNames != rhsAttributeNames)
{
if (result)
result->addDifference(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::ATTRIBUTE_NAMES);
return false;
}

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

// Compare children.
const vector<ElementPtr>& c1 = getChildren();
jstone-lucasfilm marked this conversation as resolved.
Show resolved Hide resolved
const vector<ElementPtr>& c2 = rhs->getChildren();
jstone-lucasfilm marked this conversation as resolved.
Show resolved Hide resolved
if (c1.size() != c2.size())
{
if (result)
result->addDifference(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::CHILD_COUNT);
return false;
}
for (size_t i = 0; i < c1.size(); i++)
{
ElementPtr c2Element = c2[i];
// Handle unordered children if parent is a graph.
if (this->isA<GraphElement>())
{
const string& childName = c1[i]->getName();
c2Element = rhs->getChild(childName);
if (!c2Element)
{
if (result)
result->addDifference(c1[i]->getNamePath(), "<NONE>", ElementEquivalenceResult::CHILD_NAME,
childName);
return false;
}
}
if (!c1[i]->isEquivalent(c2Element, options, result))
return false;
}
return true;
}

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

void Element::setName(const string& name)
{
ElementPtr parent = getParent();
Expand Down Expand Up @@ -482,6 +587,70 @@ TypeDefPtr TypedElement::getTypeDef() const
// ValueElement methods
//

bool ValueElement::isAttributeEquivalent(ConstElementPtr rhs, const string& attributeName,
ElementEquivalenceOptions& options, ElementEquivalenceResult* result) const
{
bool perforDefaultCompare = true;
jstone-lucasfilm marked this conversation as resolved.
Show resolved Hide resolved

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 (result)
result->addDifference(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::ATTRIBUTE, attributeName);
return false;
}
}
perforDefaultCompare = false;
}

// 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 (result)
result->addDifference(getNamePath(), rhs->getNamePath(), ElementEquivalenceResult::ATTRIBUTE, attributeName);
return false;
}
}
perforDefaultCompare = false;
}
}

if (perforDefaultCompare)
{
return Element::isAttributeEquivalent(rhs, attributeName, options, result);
}

return true;
}

string ValueElement::getResolvedValueString(StringResolverPtr resolver) const
{
if (!StringResolver::isResolvedType(getType()))
Expand Down
131 changes: 131 additions & 0 deletions source/MaterialXCore/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,97 @@ using ElementMap = std::unordered_map<string, ElementPtr>;
/// A standard function taking an ElementPtr and returning a boolean.
using ElementPredicate = std::function<bool(ConstElementPtr)>;

/// @class ElemenEquivalenceResult
/// The results of comparing for equivalence.
class MX_CORE_API ElementEquivalenceResult
jstone-lucasfilm marked this conversation as resolved.
Show resolved Hide resolved
{
public:
ElementEquivalenceResult() = default;
jstone-lucasfilm marked this conversation as resolved.
Show resolved Hide resolved
~ElementEquivalenceResult() = default;

/// Append to list of equivalence differences
void addDifference(const string& path1, const string& path2, const string& differenceType,
const string& name=EMPTY_STRING)
{
StringVec difference = { path1, path2, differenceType, name};
differences.push_back(difference);
}

/// Clear result information
void clear()
{
differences.clear();
}

/// Get a list of equivalence differences
/// Difference is of the form:
/// { path to 1st element, path to 2nd element, difference type, [attribute if is attribute difference] }
StringVec getDifference(size_t index) const
{
if (index < differenceCount())
return differences[index];
return StringVec();
}

const size_t differenceCount() const
{
return differences.size();
}

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;

private:
/// A list of differences
vector<StringVec> differences;
};

/// @class ElemenEquivalenceOptions
/// A set of options for controlling for equivalence comparison.
class MX_CORE_API ElementEquivalenceOptions
{
public:
ElementEquivalenceOptions()
{
format = Value::getFloatFormat();
precision = Value::getFloatPrecision();
skipAttributes = {};
ignoreAttributeOrder = true;
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;

/// Option to indicate whether to ignore the order that attributes
/// are specified on an element. Default is to ignore order.
bool ignoreAttributeOrder;

/// 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 Element
/// The base class for MaterialX elements.
///
Expand Down Expand Up @@ -99,6 +190,9 @@ class MX_CORE_API Element : public std::enable_shared_from_this<Element>
template <class T> friend class ElementRegistry;

public:
/// @name Comparison interfaces
/// @{

/// Return true if the given element tree, including all descendants,
/// is identical to this one.
bool operator==(const Element& rhs) const;
Expand All @@ -107,6 +201,28 @@ class MX_CORE_API Element : public std::enable_shared_from_this<Element>
/// differs from this one.
bool operator!=(const Element& rhs) const;

/// Return true if the given element treee, 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 result Results of comparison if argument is specified.
/// @return True if the elements are equivalent. False otherwise.
bool isEquivalent(ConstElementPtr rhs, ElementEquivalenceOptions& options,
ElementEquivalenceResult* result = 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 result 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,
ElementEquivalenceOptions& options,
ElementEquivalenceResult* result = nullptr) const;

/// @}
/// @name Category
/// @{

Expand Down Expand Up @@ -925,6 +1041,21 @@ class MX_CORE_API ValueElement : public TypedElement
public:
virtual ~ValueElement() { }

/// @name Comparison interfaces
/// @{

/// 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 result 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,
ElementEquivalenceOptions& options,
ElementEquivalenceResult* result = nullptr) const override;

/// @}
/// @name Value String
/// @{

Expand Down
Loading