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

Fix crash in Attribute Editor when renaming a prim with pulled descendants #3979

Merged
merged 5 commits into from
Nov 21, 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
24 changes: 22 additions & 2 deletions lib/mayaUsd/fileio/orphanedNodesManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,17 @@
#include <pxr/usd/usd/editContext.h>

#include <maya/MFnDagNode.h>
#include <maya/MGlobal.h>
#include <maya/MPlug.h>
#include <ufe/hierarchy.h>
#include <ufe/sceneSegmentHandler.h>
#include <ufe/trie.imp.h>

// Workaround to avoid a crash that occurs when the Attribute Editor is visible
// and displaying a USD prim that is being renamed, while we are updating proxyAccessor
// connections for its descendant pulled objects.
#define MAYA_USD_AE_CRASH_ON_RENAME_WORKAROUND

// For Tf diagnostics macros.
PXR_NAMESPACE_USING_DIRECTIVE

Expand Down Expand Up @@ -163,8 +169,22 @@ void renamePulledObject(
for (const PullVariantInfo& info : trieNode->data()) {
const MDagPath& mayaPath = info.editedAsMayaRoot;
TF_VERIFY(writePullInformation(pulledPath, mayaPath));
if (usdPathChanged) {
TF_VERIFY(reparentPulledObject(mayaPath, pulledPath.pop()));
if (usdPathChanged && mayaPath.isValid()) {
#ifdef MAYA_USD_AE_CRASH_ON_RENAME_WORKAROUND
// Update the proxyAccessor connections on idle to avoid a crash when
// attribute editor is visible and showing the ancestor USD prim beeing renamed.
if (MGlobal::mayaState() == MGlobal::kInteractive) {
pierrebai-adsk marked this conversation as resolved.
Show resolved Hide resolved
using ReparentArgs = std::pair<MDagPath, Ufe::Path>;
MGlobal::executeTaskOnIdle(
[](void* data) {
const auto* args = static_cast<ReparentArgs*>(data);
TF_VERIFY(reparentPulledObject(args->first, args->second));
delete args;
},
new ReparentArgs(mayaPath, pulledPath.pop()));
} else
#endif
TF_VERIFY(reparentPulledObject(mayaPath, pulledPath.pop()));
}
}
}
Expand Down
9 changes: 1 addition & 8 deletions lib/mayaUsd/fileio/utils/proxyAccessorUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,13 @@ MStatus ProxyAccessorUndoItem::parentPulledObject(
const auto ufeChildPath = MayaUsd::ufe::dagPathToUfe(pulledDagPath).pop();

// Quick workaround to reuse some POC code - to rewrite later

// Communication to current proxyAccessor code is through the global selection.
// This is not logically necessary, and should be re-written to avoid going through
// the global selection.
static const MString kPyTrueLiteral("True");
static const MString kPyFalseLiteral("False");

MString pyCommand;
pyCommand.format(
"from mayaUsd.lib import proxyAccessor as pa\n"
"import maya.cmds as cmds\n"
"cmds.select('^1s', '^2s')\n"
"pa.parent(force=^3s)\n"
"cmds.select(clear=True)\n",
"pa.parent('^1s', '^2s', force=^3s)\n",
Ufe::PathString::string(ufeChildPath).c_str(),
Ufe::PathString::string(ufeParentPath).c_str(),
force ? kPyTrueLiteral : kPyFalseLiteral);
Expand Down
38 changes: 21 additions & 17 deletions lib/mayaUsd/nodes/proxyAccessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,12 @@ def isUfeUsdPath(ufeObject):
lastSegment = ufeObject.path().segments[segmentCount-1]
return Sdf.Path.IsValidPathString(str(lastSegment))

def _createUfeSceneItem(ufePathStr):
return ufe.Hierarchy.createItem(ufe.PathString.path(ufePathStr))

def createUfeSceneItem(dagPath, sdfPath=None):
ufePath = ufe.PathString.path('{},{}'.format(dagPath,sdfPath) if sdfPath != None else '{}'.format(dagPath))
ufeItem = ufe.Hierarchy.createItem(ufePath)
return ufeItem
ufePathStr = '{},{}'.format(dagPath,sdfPath) if sdfPath != None else '{}'.format(dagPath)
return _createUfeSceneItem(ufePathStr)

def createXformOps(ufeObject):
selDag, selPrim = getDagAndPrimFromUfe(ufeObject)
Expand Down Expand Up @@ -323,18 +325,18 @@ def parentItems(ufeChildren, ufeParent, connect=True):
# Cannot use 'visibility' here because it's already used by orphan manager
connectParentChildAttr(parentVisibilityAttr, childDagPath, 'lodVisibility', connect)

def __parent(doParenting, forceUnparenting=False):
ufeSelection = iter(ufe.GlobalSelection.get())
ufeSelectionList = []
for ufeItem in ufeSelection:
ufeSelectionList.append(ufeItem)

# clearing this selection so nobody will try to use it.
# next steps can result in new selection being made due to potential call to createAccessPlug
ufeSelection = None
def __parent(doParenting, forceUnparenting, *ufeItemPathStrings):
if ufeItemPathStrings:
ufeSelectionList = [_createUfeSceneItem(pStr) for pStr in ufeItemPathStrings]
else:
ufeSelection = iter(ufe.GlobalSelection.get())
ufeSelectionList = [ufeItem for ufeItem in ufeSelection]
# clearing this selection so nobody will try to use it.
# next steps can result in new selection being made due to potential call to createAccessPlug
ufeSelection = None

if len(ufeSelectionList) < 2:
print("Select at least two objects. DAG child/ren and USD parent at the end")
print("Provide or select at least two objects. DAG child/ren and USD parent at the end")
return

ufeParent = ufeSelectionList[-1]
Expand All @@ -345,11 +347,13 @@ def __parent(doParenting, forceUnparenting=False):

parentItems(ufeChildren, ufeParent, doParenting)

def parent(force=False):
__parent(True, forceUnparenting=force)
def parent(*ufeItemPathStrings, **kwargs):
# Use **kwargs instead of keyword-only arguments after varargs for python2 compat.
forceUnparenting = kwargs.get('force', False)
__parent(True, forceUnparenting, *ufeItemPathStrings)

def unparent():
__parent(False)
def unparent(*ufeItemPathStrings):
__parent(False, False, *ufeItemPathStrings)

def connectItems(ufeObjectSrc, ufeObjectDst, attrToConnect):
connectMayaToUsd = isUfeUsdPath(ufeObjectDst)
Expand Down