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

EMSUSD-1570 add a class-prims filter to USD UFE #3987

Merged
merged 1 commit into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions lib/mayaUsd/ufe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ if (UFE_SCENE_SEGMENT_HANDLER_ROOT_PATH)
)
endif()

if (NOT MAYA_APP_VERSION VERSION_GREATER 2025)
target_compile_definitions(${PROJECT_NAME}
PRIVATE
MAYAUSD_NEED_OUTLINER_FILTER_FIX=1
)
endif()


if(CMAKE_UFE_V4_FEATURES_AVAILABLE)
target_sources(${PROJECT_NAME}
PRIVATE
Expand Down
6 changes: 4 additions & 2 deletions lib/mayaUsd/ufe/MayaUsdContextOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,10 @@ Ufe::ContextOps::Items MayaUsdContextOps::getItems(const Ufe::ContextOps::ItemPa
#endif

#ifdef UFE_V3_FEATURES_AVAILABLE
const bool isClassPrim = prim().IsAbstract();
const bool isMayaRef = (prim().GetTypeName() == TfToken("MayaReference"));
if (!_isAGatewayType && PrimUpdaterManager::getInstance().canEditAsMaya(path())) {
if (!isClassPrim && !_isAGatewayType
&& PrimUpdaterManager::getInstance().canEditAsMaya(path())) {
items.emplace_back(kEditAsMayaItem, kEditAsMayaLabel, kEditAsMayaImage);

Ufe::ContextItem item(kEditAsMayaOptionsItem, kEditAsMayaOptionsLabel);
Expand All @@ -416,7 +418,7 @@ Ufe::ContextOps::Items MayaUsdContextOps::getItems(const Ufe::ContextOps::ItemPa
items.emplace_back(kDuplicateAsMayaItem, kDuplicateAsMayaLabel);
}
}
if (!isMayaRef) {
if (!isMayaRef && !isClassPrim) {
items.emplace_back(kAddMayaReferenceItem, kAddMayaReferenceLabel);
}
items.emplace_back(Ufe::ContextItem::kSeparator);
Expand Down
87 changes: 87 additions & 0 deletions lib/mayaUsd/ufe/MayaUsdHierarchyHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
#include <usdUfe/ufe/UsdSceneItem.h>
#include <usdUfe/ufe/Utils.h>

#include <maya/MGlobal.h>
#include <maya/MString.h>

namespace MAYAUSD_NS_DEF {
namespace ufe {

Expand Down Expand Up @@ -50,5 +53,89 @@ Ufe::Hierarchy::Ptr MayaUsdHierarchyHandler::hierarchy(const Ufe::SceneItem::Ptr
return MayaUsdHierarchy::create(usdItem);
}

#ifdef MAYAUSD_NEED_OUTLINER_FILTER_FIX

static std::map<std::string, bool> fsCachedFilterValues = {
{ "InactivePrims", true },
{ "ClassPrims", false },
};

// This flag prevents infinite recursion.
static std::atomic<bool> hasPendingFilterDefaultsUpdate(false);

static void updateFilterDefaults()
{
// This script finds a valid outiner panel that has valid UFE filters
// settings. Calling this script call to the outlinerEditor command
// triggers a call to UFE childFilter function, which would cause an
// infinite recursion if we did not protect against it.
static const char outlinerScriptTemplate[] = R"MEL(
proc string _getUSDFilterValue() {
string $outliners[] = `getPanel -type "outlinerPanel"`;
for ($index = 0; $index < size($outliners); $index++) {
string $outliner = $outliners[$index];
string $value = `outlinerEditor -query -ufeFilter "USD" "^1s" -ufeFilterValue $outliner`;
if (size($value) > 0) {
return $value;
}
}
return "";
}
_getUSDFilterValue()
)MEL";

try {
for (auto& nameAndValue : fsCachedFilterValues) {
MString script;
script.format(outlinerScriptTemplate, nameAndValue.first.c_str());
const MString value = MGlobal::executeCommandStringResult(script);
if (value.length() > 0 && value.isInt()) {
nameAndValue.second = (value.asInt() != 0);
}
}
} catch (const std::exception&) {
// Ignore exceptions in callbacks.
}

// Allow another filter update in the future.
hasPendingFilterDefaultsUpdate = false;
}

static void triggerUpdateFilterDefaults()
{
// Don't trigger an update if there is already one pending.
if (hasPendingFilterDefaultsUpdate)
return;

// Mark that an update will be pending.
hasPendingFilterDefaultsUpdate = true;

updateFilterDefaults();
}

Ufe::Hierarchy::ChildFilter MayaUsdHierarchyHandler::childFilter() const
{
Ufe::Hierarchy::ChildFilter filters = UsdUfe::UsdHierarchyHandler::childFilter();
for (Ufe::ChildFilterFlag& filter : filters) {
const auto iter = fsCachedFilterValues.find(filter.name);
if (iter != fsCachedFilterValues.end()) {
filter.value = fsCachedFilterValues[filter.name];
}
}

triggerUpdateFilterDefaults();

return filters;
}

#else

Ufe::Hierarchy::ChildFilter MayaUsdHierarchyHandler::childFilter() const
{
return UsdUfe::UsdHierarchyHandler::childFilter();
}

#endif

} // namespace ufe
} // namespace MAYAUSD_NS_DEF
4 changes: 3 additions & 1 deletion lib/mayaUsd/ufe/MayaUsdHierarchyHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ class MAYAUSD_CORE_PUBLIC MayaUsdHierarchyHandler : public UsdUfe::UsdHierarchyH
static MayaUsdHierarchyHandler::Ptr create();

// UsdHierarchyHandler overrides
Ufe::Hierarchy::Ptr hierarchy(const Ufe::SceneItem::Ptr& item) const override;
Ufe::Hierarchy::Ptr hierarchy(const Ufe::SceneItem::Ptr& item) const override;
Ufe::Hierarchy::ChildFilter childFilter() const override;

}; // MayaUsdHierarchyHandler

} // namespace ufe
Expand Down
14 changes: 2 additions & 12 deletions lib/mayaUsd/ufe/ProxyShapeHierarchy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,18 +186,8 @@ Ufe::SceneItemList ProxyShapeHierarchy::filteredChildren(const ChildFilter& chil
if (!rootPrim.IsValid())
return Ufe::SceneItemList();

// Note: for now the only child filter flag we support is "Inactive Prims".
// See UsdHierarchyHandler::childFilter()
if ((childFilter.size() == 1) && (childFilter.front().name == "InactivePrims")) {
// See uniqueChildName() for explanation of USD filter predicate.
const bool showInactive = childFilter.front().value;
Usd_PrimFlagsPredicate flags
= showInactive ? UsdPrimIsDefined && !UsdPrimIsAbstract : kMayaUsdPrimDefaultPredicate;
return createUFEChildList(getUSDFilteredChildren(rootPrim, flags), !showInactive);
}

UFE_LOG("Unknown child filter");
return Ufe::SceneItemList();
Usd_PrimFlagsPredicate flags = UsdUfe::getUsdPredicate(childFilter);
return createUFEChildList(getUSDFilteredChildren(rootPrim, flags), false);
}

// Return UFE child list from input USD child list.
Expand Down
12 changes: 8 additions & 4 deletions lib/usdUfe/ufe/UsdContextOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ Ufe::ContextOps::Items UsdContextOps::getItems(const Ufe::ContextOps::ItemPath&

Ufe::ContextOps::Items items;
if (itemPath.empty()) {
const bool isClassPrim = prim().IsAbstract();

if (!_isAGatewayType) {
// Working set management (load and unload):
const auto itemLabelPairs = _computeLoadAndUnloadItems(prim());
Expand Down Expand Up @@ -363,10 +365,12 @@ Ufe::ContextOps::Items UsdContextOps::getItems(const Ufe::ContextOps::ItemPath&
// Default Prim:
// - If the prim is the default prim, add clearing the default prim
// - Otherwise, if the prim is a root prim, add set default prim
if (prim().GetStage()->GetDefaultPrim() == prim()) {
items.emplace_back(kUSDClearDefaultPrim, kUSDClearDefaultPrim);
} else if (prim().GetPath().IsRootPrimPath()) {
items.emplace_back(kUSDSetAsDefaultPrim, kUSDSetAsDefaultPrim);
if (!isClassPrim) {
if (prim().GetStage()->GetDefaultPrim() == prim()) {
items.emplace_back(kUSDClearDefaultPrim, kUSDClearDefaultPrim);
} else if (prim().GetPath().IsRootPrimPath()) {
items.emplace_back(kUSDSetAsDefaultPrim, kUSDSetAsDefaultPrim);
}
}

// Prim active state:
Expand Down
14 changes: 2 additions & 12 deletions lib/usdUfe/ufe/UsdHierarchy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,18 +189,8 @@ Ufe::SceneItemList UsdHierarchy::children() const

Ufe::SceneItemList UsdHierarchy::filteredChildren(const ChildFilter& childFilter) const
{
// Note: for now the only child filter flag we support is "Inactive Prims".
// See UsdHierarchyHandler::childFilter()
if ((childFilter.size() == 1) && (childFilter.front().name == "InactivePrims")) {
// See uniqueChildName() for explanation of USD filter predicate.
const bool showInactive = childFilter.front().value;
Usd_PrimFlagsPredicate flags
= showInactive ? UsdPrimIsDefined && !UsdPrimIsAbstract : kUsdUfePrimDefaultPredicate;
return createUFEChildList(getUSDFilteredChildren(_item, flags), !showInactive);
}

UFE_LOG("Unknown child filter");
return Ufe::SceneItemList();
Usd_PrimFlagsPredicate flags = UsdUfe::getUsdPredicate(childFilter);
return createUFEChildList(getUSDFilteredChildren(_item, flags), false);
seando-adsk marked this conversation as resolved.
Show resolved Hide resolved
}

bool UsdHierarchy::childrenHook(
Expand Down
1 change: 1 addition & 0 deletions lib/usdUfe/ufe/UsdHierarchyHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Ufe::Hierarchy::ChildFilter UsdHierarchyHandler::childFilter() const
{
Ufe::Hierarchy::ChildFilter childFilters;
childFilters.emplace_back("InactivePrims", "Inactive Prims", true);
childFilters.emplace_back("ClassPrims", "Class Prims", false);
return childFilters;
}

Expand Down
32 changes: 32 additions & 0 deletions lib/usdUfe/ufe/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1523,4 +1523,36 @@ void removeSessionLeftOvers(
stage->RemovePrim(primPath);
}

Usd_PrimFlagsPredicate getUsdPredicate(const Ufe::Hierarchy::ChildFilter& childFilter)
{
// Note: for now the only child filter flags we support are "Inactive Prims"
// and "Class Prims".
// See UsdHierarchyHandler::childFilter()

bool showInactive = false;
bool showClass = false;

for (const Ufe::ChildFilterFlag& filter : childFilter) {
if (filter.name == "InactivePrims") {
showInactive = filter.value;
} else if (filter.name == "ClassPrims") {
showClass = filter.value;
}
}

// Note: unfortunately, the way the USD predicate are implemented,
// we cannot use && on a Usd_PrimFlagsPredicate, only on a
// Usd_Term or a Usd_PrimFlagsConjunction.

auto predicate = Usd_PrimFlagsConjunction(Usd_Term(UsdPrimIsDefined));

if (!showInactive)
predicate &= UsdPrimIsActive;

if (!showClass)
predicate &= !UsdPrimIsAbstract;

return predicate;
}

} // namespace USDUFE_NS_DEF
8 changes: 8 additions & 0 deletions lib/usdUfe/ufe/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/sdr/shaderNode.h>
#include <pxr/usd/usd/attribute.h>
#include <pxr/usd/usd/primFlags.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usdImaging/usdImaging/delegate.h>

#include <ufe/attribute.h>
#include <ufe/hierarchy.h>
#include <ufe/path.h>
#include <ufe/scene.h>
#include <ufe/types.h>
Expand Down Expand Up @@ -477,6 +479,12 @@ void removeSessionLeftOvers(
UsdUndoableItem* undoableItem,
bool extraEdits = true);

//! Return the USD prims predicate for the given UFE child filter.
//! Note: an empty filter or unknown filter will filter out everything.
//! Yes, that means no-filter actually means filter everything out.
USDUFE_PUBLIC
PXR_NS::Usd_PrimFlagsPredicate getUsdPredicate(const Ufe::Hierarchy::ChildFilter& childFilter);
pierrebai-adsk marked this conversation as resolved.
Show resolved Hide resolved

} // namespace USDUFE_NS_DEF

#endif // USDUFE_UFE_UTILS_H
60 changes: 58 additions & 2 deletions test/lib/ufe/testChildFilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import fixturesUtils
import mayaUtils
import testUtils

from maya import cmds
from maya import standalone
Expand All @@ -28,6 +29,7 @@
import ufe

import unittest
import collections


class ChildFilterTestCase(unittest.TestCase):
Expand Down Expand Up @@ -58,7 +60,7 @@ def testChildFilter(self):
rid = ufe.RunTimeMgr.instance().getId('USD')
usdHierHndlr = ufe.RunTimeMgr.instance().hierarchyHandler(rid)
cf = usdHierHndlr.childFilter()
self.assertEqual(1, len(cf))
self.assertEqual(2, len(cf))

# Make sure the USD hierarchy handler has an inactive prims filter
self.assertEqual('InactivePrims', cf[0].name)
Expand All @@ -68,7 +70,7 @@ def testChildFilter(self):
rid = ufe.RunTimeMgr.instance().getId('Maya-DG')
mayaHierHndlr = ufe.RunTimeMgr.instance().hierarchyHandler(rid)
mayaCf = mayaHierHndlr.childFilter()
self.assertEqual(1, len(mayaCf))
self.assertEqual(2, len(mayaCf))
self.assertEqual(cf[0].name, mayaCf[0].name)
self.assertEqual(cf[0].label, mayaCf[0].label)
self.assertEqual(cf[0].value, mayaCf[0].value)
Expand Down Expand Up @@ -116,6 +118,60 @@ def testFilteredChildren(self):
self.assertEqual(6, len(children))
self.assertIn(ball3Item, children)

def testFilteredClassPrims(self):
classPrimFileName = testUtils.getTestScene('classPrims', 'class-prims.usda')
shapeNode, stage = mayaUtils.createProxyFromFile(classPrimFileName)
self.assertTrue(stage)
cmds.select(clear=True)

# Check that we see the 2 active prims
topPathStr = shapeNode + ',/top'
topPath = ufe.PathString.path(topPathStr)
topItem = ufe.Hierarchy.createItem(topPath)
topHier = ufe.Hierarchy.hierarchy(topItem)
self.assertEqual(2, len(topHier.children()))

# Get the child filter for the hierarchy handler corresponding
# to the runtime of the top item.
usdHierarchyHandler = ufe.RunTimeMgr.instance().hierarchyHandler(topItem.runTimeId())
childrenFilters = usdHierarchyHandler.childFilter()

# The prims that can be found in the file.
activeConeItem = ufe.Hierarchy.createItem(ufe.PathString.path(topPathStr + '/active_cone'))
activeCubeItem = ufe.Hierarchy.createItem(ufe.PathString.path(topPathStr + '/active_cube'))
inactiveCylinderItem = ufe.Hierarchy.createItem(ufe.PathString.path(topPathStr + '/inactive_cylinder'))
activeClassItem = ufe.Hierarchy.createItem(ufe.PathString.path(topPathStr + '/active_class'))
inactiveClassItem = ufe.Hierarchy.createItem(ufe.PathString.path(topPathStr + '/inactive_class'))

# Declare different filter settings that will be tested.
FilterSettings = collections.namedtuple('FilterSettings', ['filters', 'expecteditems'])
filterSettings = [
FilterSettings(
filters={ 'InactivePrims': False, 'ClassPrims': False },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my thinking, that a full list of possibilities may not be known in case there's a need to include all.
Perhaps we can document the supported filters somewhere to be used as a reference.

expecteditems=[ activeConeItem, activeCubeItem ]),
FilterSettings(
filters={ 'InactivePrims': False, 'ClassPrims': True },
expecteditems=[ activeConeItem, activeCubeItem, activeClassItem ]),
FilterSettings(
filters={ 'InactivePrims': True, 'ClassPrims': False },
expecteditems=[ activeConeItem, activeCubeItem, inactiveCylinderItem ]),
FilterSettings(
filters={ 'InactivePrims': True, 'ClassPrims': True },
expecteditems=[ activeConeItem, activeCubeItem, inactiveCylinderItem, activeClassItem, inactiveClassItem ]),
]

for settings in filterSettings:
# Set the children settings according to the desired test settings.
for filter in childrenFilters:
self.assertIn(filter.name, settings.filters)
filter.value = settings.filters[filter.name]

# Verify we have the expected count of children.
children = topHier.filteredChildren(childrenFilters)
self.assertEqual(len(settings.expecteditems), len(children))
for item in settings.expecteditems:
self.assertIn(item, children)

def testProxyShapeFilteredChildren(self):
mayaUtils.openGroupBallsScene()
cmds.select(clear=True)
Expand Down
Loading