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

MAYA-99006 Filter out objects to export by hierarchy. #657

Merged
merged 2 commits into from
Jul 16, 2020
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
25 changes: 4 additions & 21 deletions lib/mayaUsd/commands/baseExportCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,30 +265,13 @@ try
frameSamples.insert(tmpArgList.asDouble(0));
}

// Get the objects to export as a MSelectionList
MSelectionList objSelList;
if (argData.isFlagSet(kSelectionFlag)) {
MGlobal::getActiveSelectionList(objSelList);
}
else {
argData.getObjects(objSelList);

// If no objects specified, then get all objects at DAG root
if (objSelList.isEmpty()) {
objSelList.add("|*", true);
}
}

// Convert selection list to jobArgs dagPaths
UsdMayaUtil::MDagPathSet dagPaths;
for (unsigned int i=0; i < objSelList.length(); i++) {
MDagPath dagPath;
status = objSelList.getDagPath(i, dagPath);
if (status == MS::kSuccess)
{
dagPaths.emplace(dagPath);
}
bool exportSelected = argData.isFlagSet(kSelectionFlag);
if (!exportSelected) {
argData.getObjects(objSelList);
}
UsdMayaUtil::GetFilteredSelectionToExport(exportSelected, objSelList, dagPaths);

const std::vector<double> timeSamples = UsdMayaWriteUtil::GetTimeSamples(
timeInterval, frameSamples, frameStride);
Expand Down
79 changes: 78 additions & 1 deletion lib/mayaUsd/utils/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
#include <maya/MPlug.h>
#include <maya/MPlugArray.h>
#include <maya/MPoint.h>
#include <maya/MSelectionList.h>
Copy link
Contributor

Choose a reason for hiding this comment

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

We still use MSelectionList in this file below as a param to GetFilteredSelectionToExport()?

#include <maya/MStatus.h>
#include <maya/MString.h>
#include <maya/MStringArray.h>
Expand All @@ -75,6 +74,39 @@

PXR_NAMESPACE_USING_DIRECTIVE

namespace {
bool shouldAddToSet(const MDagPath& toAdd, const UsdMayaUtil::MDagPathSet& dagPaths)
// Utility function to check if an object should be added to the set of objects to
// export. An object should not be added if it's invalid, or if any of it's parent
Copy link
Collaborator

Choose a reason for hiding this comment

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

"it's" --> "its"

// objects are already in the set.
{
if (!toAdd.isValid())
return false;

MStatus status = MS::kSuccess;
bool pathIsValid = true;
MDagPath dp = toAdd;

UsdMayaUtil::MDagPathSet::const_iterator end = dagPaths.end();

// Travel up the hierarchy looking for a parent object that is already in the set.
// That is our only reason to return false. Not finding any ancestors in the set
// will eventually hit the world root, which will be an invalid path and in that case
// we just exit the loop and return true.
while (pathIsValid && status == MS::kSuccess)
{
UsdMayaUtil::MDagPathSet::const_iterator dpIter = dagPaths.find(dp);
if (dpIter != end)
return false;

status = dp.pop();
pathIsValid = dp.isValid();
}

return true;
}
}

double
UsdMayaUtil::ConvertMDistanceUnitToUsdGeomLinearUnit(
const MDistance::Unit mdistanceUnit)
Expand Down Expand Up @@ -2101,3 +2133,48 @@ UsdMayaUtil::nameToDagPath(const std::string& name)
CHECK_MSTATUS(status);
return dag;
}

void
UsdMayaUtil::GetFilteredSelectionToExport(bool exportSelected, MSelectionList& objectList, UsdMayaUtil::MDagPathSet& dagPaths)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Had hoped that there was an easy API call or Maya command to do what I want here but I never found anything. Most of the code I found that did something similar ended up traversing the entire Maya scene using various dag iterators, sometimes multiple times, and it didn’t fit what we want to do here very well.

What I ended up doing was:

  1. Gather input in a MSelectionList
  2. Move the selection list into a set, sorted by hierarchy depth.
  3. Iterate over that set, so kind of depth first traversal of the input, not the entire Maya scene, moving objects we want to keep into a second set that will be returned to the caller.

{
dagPaths.clear();

bool filterInput = true;

// There are three cases depending on the input:
// If exportSelected is true then we will grab the active selection
// If objectList is empty then we will grab all immediate children of the world root.
// Else there was a populated list of objects to use, most likely passed explicitly to the command.
if (exportSelected) {
MGlobal::getActiveSelectionList(objectList);
} else if (objectList.isEmpty()) {
objectList.add("|*", true);
Copy link
Contributor

Choose a reason for hiding this comment

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

I hadn't noticed this before, but will this miss any top-level objects that have a namespace? Is there a syntax that would catch objects with arbitrarily deep namespaces?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

|* will catch all top-level objects, including objects in a namespace. I could add a test for something like this though, can you give an example of what you mean by arbitrarily deep namespaces?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, sorry, nevermind this then. Apparently selection lists work differently from the ls command. I remembered this being an issue with the latter, so it made me think the former might be affected as well.

I did this in the script editor to setup a test scene:

cmds.polyCube(name='Cube')

cmds.namespace(addNamespace=':NS_One')
cmds.namespace(setNamespace=':NS_One')
cmds.polyCube(name='Cube')

cmds.namespace(addNamespace=':NS_One:NS_Two')
cmds.namespace(setNamespace=':NS_One:NS_Two')
cmds.polyCube(name='Cube')

cmds.namespace(addNamespace=':NS_One:NS_Two:NS_Three')
cmds.namespace(setNamespace=':NS_One:NS_Two:NS_Three')
cmds.polyCube(name='Cube')

That yields four top-level nodes:

[n for n in cmds.ls(long=True) if n.endswith('Cube')]

# Result: [u'|Cube', u'|NS_One:Cube', u'|NS_One:NS_Two:Cube', u'|NS_One:NS_Two:NS_Three:Cube'] #

If I try to find those nodes using ls though, I seem to have to explicitly add the right level of namespacing:

cmds.ls('|*')

# Result: [u'Cube', u'front', u'persp', u'side', u'top'] # 

cmds.ls('|*:*')

# Result: [u'NS_One:Cube'] # 

cmds.ls('|*:*:*')

# Result: [u'NS_One:NS_Two:Cube'] # 

cmds.ls('|*:*:*:*')

# Result: [u'NS_One:NS_Two:NS_Three:Cube'] # 

But the OpenMaya API indeed seems to behave the way you're describing:

from maya.api import OpenMaya as OM

objectList = OM.MSelectionList()
objectList.add('|*', True)

for i in range(objectList.length()):
    print(objectList.getDagPath(i))

That yields (ignore the default cameras):

Cube
front
persp
side
top
NS_One:Cube
NS_One:NS_Two:Cube
NS_One:NS_Two:NS_Three:Cube

// By construction, the list will only include the single top level objects
// when we get the input list with |*, so no need to filter selection.
filterInput = false;
}

unsigned int nbObj = objectList.length();
if (0 == nbObj) {
return;
}

MStatus status;

// Easiest way to filter by hierarchy is to:
// 1. Put the input into a set that is sorted by distance from the world root.
// 2. For each input object we iterate up its hierarchy checking if any parent is in the set.
// 3. If no parent is in the set then we can add it.
UsdMayaUtil::MDagPathSet sortedInput;
for (unsigned int i=0; i < nbObj; i++) {
MDagPath dagPath;
status = objectList.getDagPath(i, dagPath);
if (status == MS::kSuccess)
sortedInput.emplace(dagPath);
}

for(auto pIter : sortedInput) {
if (!filterInput || shouldAddToSet(pIter, dagPaths))
dagPaths.emplace(pIter);
}
}
21 changes: 19 additions & 2 deletions lib/mayaUsd/utils/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@
#include <maya/MDataHandle.h>
#include <maya/MDistance.h>
#include <maya/MFnDagNode.h>
#include <maya/MSelectionList.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnMesh.h>
#include <maya/MFnNumericData.h>
#include <maya/MMatrix.h>
#include <maya/MObject.h>
#include <maya/MObjectHandle.h>
#include <maya/MPlug.h>
#include <maya/MSelectionList.h>
#include <maya/MStatus.h>
#include <maya/MString.h>

Expand Down Expand Up @@ -66,7 +66,9 @@ struct _CmpDag
{
bool operator()(const MDagPath& lhs, const MDagPath& rhs) const
{
return strcmp(lhs.fullPathName().asChar(), rhs.fullPathName().asChar()) < 0;
int pathCountDiff = lhs.pathCount() - rhs.pathCount();
return (0 != pathCountDiff) ? (pathCountDiff < 0) :
(strcmp(lhs.fullPathName().asChar(), rhs.fullPathName().asChar()) < 0);
Comment on lines -69 to +71
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changing the sorting function to more explicitly sort by hierarchy depth, and then alphabetically within the same depth. End result is the same because of the path separator used in the fullPathName, but adding the pathCount check makes it more obvious that it’s doing this. It would also be faster this way since the path count is a stored value internally so it’s a quick int compare in a lot of cases, avoiding a bunch of string compares.

}
};

Expand Down Expand Up @@ -595,6 +597,21 @@ bool containsUnauthoredValues(const VtIntArray& indices);
MAYAUSD_CORE_PUBLIC
MDagPath nameToDagPath(const std::string& name);

/// Utility function used by the export translator and commands to filter out objects to export
/// by hierarchy. When an object is exported then all of its children are exported as well, so
/// the children should be removed from the list before the set of objects ends up in the write
/// job, where it would error out.
/// If \p exportSelected is true then the active selection list will be added to \p objectList
/// and then used to fill \p dagPaths with the objects to be exported.
/// If \p exportSelected is false and \p objectList is not empty then \p objectList will
/// be used to fill \p dagPaths with the objects to be exported.
/// If \p exportSelected is false and \p objectList is empty then all objects starting at
/// the DAG root will be added to \p objectList and then used to fill \p dagPaths with
/// the objects to be exported.
///
MAYAUSD_CORE_PUBLIC
void GetFilteredSelectionToExport(bool exportSelected, MSelectionList& objectList, UsdMayaUtil::MDagPathSet& dagPaths);

} // namespace UsdMayaUtil

PXR_NAMESPACE_CLOSE_SCOPE
Expand Down
27 changes: 9 additions & 18 deletions plugin/adsk/plugin/exportTranslator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ UsdMayaExportTranslator::writer(const MFileObject &file,
const MString &optionsString,
MPxFileTranslator::FileAccessMode mode ) {

// If we are in neither of these modes then there won't be anything to do
if (mode != MPxFileTranslator::kExportActiveAccessMode &&
mode != MPxFileTranslator::kExportAccessMode) {
return MS::kSuccess;
}
Comment on lines +55 to +59
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The logic farther down used to be that we would only fill the input list if one of these two flags were set. If neither were set then we had nothing to export and just returned kSuccess. Moving this check to the start since there is no point in continuing if we are in a different mode.


std::string fileName(file.fullName().asChar(), file.fullName().length());
VtDictionary userArgs;
bool exportAnimation = false;
Expand Down Expand Up @@ -123,24 +129,9 @@ UsdMayaExportTranslator::writer(const MFileObject &file,
}

MSelectionList objSelList;
if(mode == MPxFileTranslator::kExportActiveAccessMode) {
// Get selected objects
MGlobal::getActiveSelectionList(objSelList);
} else if(mode == MPxFileTranslator::kExportAccessMode) {
// Get all objects at DAG root
objSelList.add("|*", true);
}

// Convert selection list to jobArgs dagPaths
UsdMayaUtil::MDagPathSet dagPaths;
unsigned int len = objSelList.length();
for (unsigned int i=0; i < len; i++) {
MDagPath dagPath;
if (objSelList.getDagPath(i, dagPath) == MS::kSuccess) {
dagPaths.insert(dagPath);
}
}

GetFilteredSelectionToExport((mode == MPxFileTranslator::kExportActiveAccessMode), objSelList, dagPaths);

if (dagPaths.empty()) {
TF_WARN("No DAG nodes to export. Skipping.");
return MS::kSuccess;
Expand All @@ -151,7 +142,7 @@ UsdMayaExportTranslator::writer(const MFileObject &file,
PXR_NS::UsdMayaJobExportArgs jobArgs = PXR_NS::UsdMayaJobExportArgs::CreateFromDictionary(
userArgs, dagPaths, timeSamples);

len = filteredTypes.length();
unsigned int len = filteredTypes.length();
for (unsigned int i=0; i < len; ++i) {
jobArgs.AddFilteredTypeName(filteredTypes[i].asChar());
}
Expand Down
1 change: 1 addition & 0 deletions test/lib/usd/translators/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set(test_script_files
## XXX: This test is disabled by default since it requires the RenderMan for Maya plugin.
# testUsdExportRfMLight.py
testUsdExportSelection.py
testUsdExportSelectionHierarchy.py
testUsdExportShadingInstanced.py
testUsdExportShadingModeDisplayColor.py
testUsdExportShadingModePxrRis.py
Expand Down
Loading