Skip to content
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
13 changes: 13 additions & 0 deletions lib/mayaUsd/nodes/proxyShapeBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,19 @@ class MayaUsdProxyShapeBase
bool* drawProxyPurpose,
bool* drawGuidePurpose);

/// Returns true if the Maya DAG path already accounts for the USD root
/// prim's world transform. When true, the rendering delegate shouldn't
/// multiply by GetLocalToWorldTransform(rootPrim), as that would double the
/// transforms already present in the Maya DAG.
///
/// Defaults to false, which is correct for proxy shapes that reference a USD
/// stage without replicating prim transforms into the Maya DAG hierarchy.
/// Override to return true only when your plugin expands assembly/subcomponent
/// prims as Maya DAG nodes whose transforms already reflect the root prim
/// (e.g., Pixar's scene assembly workflow).
MAYAUSD_CORE_PUBLIC
virtual bool isRootPrimTransformInDagPath() const { return false; }
Comment thread
seando-adsk marked this conversation as resolved.

MAYAUSD_CORE_PUBLIC
MStatus
preEvaluation(const MDGContext& context, const MEvaluationNode& evaluationNode) override;
Expand Down
5 changes: 2 additions & 3 deletions lib/mayaUsd/render/pxrUsdMayaGL/proxyDrawOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,8 @@ UsdMayaProxyDrawOverride::transform(const MDagPath& objPath, const MDagPath& cam
GfMatrix4d rootXform(transform.matrix);

MayaUsdProxyShapeBase* pShape = MayaUsdProxyShapeBase::GetShapeAtDagPath(objPath);
if (pShape && pShape->usdPrim().GetPath() != SdfPath::AbsoluteRootPath()) {
// If we're not computing the transform of the root of the Usd
// scene, apply the usdPrim transform from within the scene
if (pShape && pShape->usdPrim().GetPath() != SdfPath::AbsoluteRootPath()
&& !pShape->isRootPrimTransformInDagPath()) {
const UsdTimeCode timeCode = pShape->getTime();
UsdGeomXformCache xformCache(timeCode);
GfMatrix4d primTransform = xformCache.GetLocalToWorldTransform(pShape->usdPrim());
Expand Down
14 changes: 6 additions & 8 deletions lib/mayaUsd/render/pxrUsdMayaGL/usdProxyShapeAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,19 +171,17 @@ bool PxrMayaHdUsdProxyShapeAdapter::_Sync(
_renderTags.push_back(HdRenderTagTokens->guide);
}

// Update the root transform used to render by the delagate.
// USD considers that the root prim transform is always the Identity matrix so that means
// the root transform define the root prim transform. When the real stage root is used to
// render this is not a issue because the root transform will be the maya transform.
// The problem is when using a primPath as the root prim, we are losing
// the prim path world transform. So we need to set the root transform as the world
// transform of the prim used for rendering.
// Update the root transform used to render by the delegate.
// When using a primPath as the root prim, USD treats it as identity, so we
// compensate by including its world transform in the root transform — unless
// the Maya DAG already accounts for it (isRootPrimTransformInDagPath).
MStatus status;
const MMatrix transform = GetDagPath().inclusiveMatrix(&status);
if (status == MS::kSuccess) {
_rootXform = GfMatrix4d(transform.matrix);

if (usdProxyShape->usdPrim().GetPath() != SdfPath::AbsoluteRootPath()) {
if (usdProxyShape->usdPrim().GetPath() != SdfPath::AbsoluteRootPath()
&& !usdProxyShape->isRootPrimTransformInDagPath()) {
const UsdTimeCode timeCode = usdProxyShape->getTime();
UsdGeomXformCache xformCache(timeCode);
GfMatrix4d primTransform
Expand Down
14 changes: 6 additions & 8 deletions lib/mayaUsd/render/vp2RenderDelegate/proxyRenderDelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -841,17 +841,15 @@ void ProxyRenderDelegate::_UpdateSceneDelegate()
_sceneDelegate->SetTime(timeCode);
}

// Update the root transform used to render by the delagate.
// USD considers that the root prim transform is always the Identity matrix so that means
// the root transform define the root prim transform. When the real stage root is used to
// render this is not a issue because the root transform will be the maya transform.
// The problem is when using a primPath as the root prim, we are losing
// the prim path world transform. So we need to set the root transform as the world
// transform of the prim used for rendering.
// Update the root transform used to render by the delegate.
// When using a primPath as the root prim, USD treats it as identity, so we
// compensate by including its world transform in the root transform — unless
// the Maya DAG already accounts for it (isRootPrimTransformInDagPath).
const MMatrix inclusiveMatrix = _proxyShapeData->ProxyDagPath().inclusiveMatrix();
GfMatrix4d transform(inclusiveMatrix.matrix);

if (_proxyShapeData->ProxyShape()->usdPrim().GetPath() != SdfPath::AbsoluteRootPath()) {
if (_proxyShapeData->ProxyShape()->usdPrim().GetPath() != SdfPath::AbsoluteRootPath()
&& !_proxyShapeData->ProxyShape()->isRootPrimTransformInDagPath()) {
const UsdTimeCode timeCode = _proxyShapeData->ProxyShape()->getTime();
UsdGeomXformCache xformCache(timeCode);
GfMatrix4d m
Expand Down
93 changes: 0 additions & 93 deletions plugin/pxr/maya/lib/usdMaya/AEpxrUsdProxyShapeTemplate.mel

This file was deleted.

199 changes: 199 additions & 0 deletions plugin/pxr/maya/lib/usdMaya/AEpxrUsdProxyShapeTemplate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#
# Copyright 2026 Pixar
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from pxr import UsdMaya

from maya import cmds
from maya import mel

import functools


# ========================================================================
# VARIANT SETS
# ========================================================================

def _GetVariantSetInfoFromNode(node, variantSetName):
'''Returns (override, settable) for the variantSetName on node.'''
variantAttrName = 'usdVariantSet_%s' % variantSetName
override = ''
settable = True
if cmds.attributeQuery(variantAttrName, node=node, exists=True):
variantSetPlgVal = cmds.getAttr('%s.%s' % (node, variantAttrName))
if variantSetPlgVal:
override = variantSetPlgVal
settable = cmds.getAttr('%s.%s' % (node, variantAttrName), settable=True)
return override, settable


def _variantSets_changeCommand(unused, omg, node, variantSetName):
val = cmds.optionMenuGrp(omg, q=True, value=True)

AuthorVariantSelectionFromAE(node, variantSetName, val)

# Update the resolved variant selection label
resolvedVariant = ''
usdPrim = UsdMaya.GetPrim(node)
if usdPrim:
variantSet = usdPrim.GetVariantSet(variantSetName)
if variantSet:
resolvedVariant = variantSet.GetVariantSelection()
cmds.optionMenuGrp(omg, edit=True, extraLabel=resolvedVariant)


def AuthorVariantSelectionFromAE(node, variantSetName, variantSelection):
variantAttr = 'usdVariantSet_%s' % variantSetName
if not cmds.attributeQuery(variantAttr, node=node, exists=True):
cmds.addAttr(node, ln=variantAttr, dt='string', internalSet=True)
cmds.setAttr('%s.%s' % (node, variantAttr), variantSelection, type='string')


def variantSets_Replace(nodeAttr, new):
origParent = cmds.setParent(q=True)

frameLayoutName = 'AEpxrUsdProxyShapeTemplate_variantSets_Layout'
if new:
cmds.frameLayout(frameLayoutName, label='VariantSets', collapse=False)
else:
cmds.setParent(frameLayoutName)

# Remove existing children of layout
children = cmds.frameLayout(frameLayoutName, q=True, childArray=True)
if children:
for child in children:
cmds.deleteUI(child)

node = nodeAttr.split('.', 1)[0]
usdPrim = UsdMaya.GetPrim(node)
if usdPrim:
for variantSetName in usdPrim.GetVariantSets().GetNames():
usdVariantSet = usdPrim.GetVariantSet(variantSetName)
variantResolved = usdVariantSet.GetVariantSelection()
variantSetChoices = [''] + usdVariantSet.GetVariantNames()
variantOverride, variantSettable = _GetVariantSetInfoFromNode(
node, variantSetName)

omg = cmds.optionMenuGrp(
label=variantSetName,
enable=variantSettable,
extraLabel=variantResolved)
for choice in variantSetChoices:
cmds.menuItem(label=choice)

try:
cmds.optionMenuGrp(omg, e=True, value=variantOverride)
except RuntimeError:
cmds.warning('Invalid choice %r for %r'
% (variantOverride, variantSetName))

cmds.optionMenuGrp(omg, e=True,
changeCommand=functools.partial(
_variantSets_changeCommand,
omg=omg, node=node, variantSetName=variantSetName))

cmds.setParent(origParent)


def variantSets_Replace_new(nodeAttr):
variantSets_Replace(nodeAttr, new=True)

def variantSets_Replace_replace(nodeAttr):
variantSets_Replace(nodeAttr, new=False)


# ========================================================================
# FILE PATH
# ========================================================================

def filePath_Replace(nodeAttr, new):
if new:
cmds.setUITemplate('attributeEditorTemplate', pushTemplate=True)
cmds.rowLayout(numberOfColumns=3)
cmds.text(label='File Path')
cmds.textField('pxrUsdProxyShape_usdFilePathField')
cmds.symbolButton('pxrUsdProxyShape_usdFileBrowserButton',
image='navButtonBrowse.xpm')
cmds.setParent('..')
cmds.setUITemplate(popTemplate=True)

def _showFileBrowser(*args):
filePaths = cmds.fileDialog2(
caption="Specify USD File",
fileFilter="USD Files (*.usd*) (*.usd*);;Alembic Files (*.abc)",
fileMode=1)
if filePaths:
cmds.setAttr(nodeAttr, filePaths[0], type='string')
cmds.button('pxrUsdProxyShape_usdFileBrowserButton', edit=True,
command=_showFileBrowser)

cmds.evalDeferred(functools.partial(
cmds.connectControl, 'pxrUsdProxyShape_usdFilePathField', nodeAttr))


def filePath_Replace_new(nodeAttr):
filePath_Replace(nodeAttr, new=True)

def filePath_Replace_replace(nodeAttr):
filePath_Replace(nodeAttr, new=False)


# ========================================================================
# EDITOR TEMPLATE
# ========================================================================

def editorTemplate(nodeName):
cmds.editorTemplate(beginScrollLayout=True)

isSubcomponent = UsdMaya.IsSubcomponentProxy(nodeName)

cmds.editorTemplate(beginLayout='USD', collapse=False)
cmds.editorTemplate(
'filePath',
callCustom=[filePath_Replace_new, filePath_Replace_replace])
cmds.editorTemplate('primPath', addControl=True)
cmds.editorTemplate('excludePrimPaths', addControl=True)
cmds.editorTemplate('variantKey', addControl=True)
cmds.editorTemplate('complexity', addControl=True)
cmds.editorTemplate('drawRenderPurpose', addControl=True)
cmds.editorTemplate('drawProxyPurpose', addControl=True)
cmds.editorTemplate('drawGuidePurpose', addControl=True)
cmds.editorTemplate(endLayout=True)

if isSubcomponent:
cmds.editorTemplate(beginLayout='Variant Sets', collapse=False)
cmds.editorTemplate(
'',
callCustom=[variantSets_Replace_new, variantSets_Replace_replace])
cmds.editorTemplate(endLayout=True)

mel.eval('AEsurfaceShapeTemplate "%s"' % nodeName)
cmds.editorTemplate(addExtraControls=True)

cmds.editorTemplate(endScrollLayout=True)


# ========================================================================
# MEL STUBS
# ========================================================================

def addMelFunctionStubs():
'''Create MEL proc that delegates to Python for the AE template.'''
mel.eval('''
global proc AEpxrUsdProxyShapeTemplate( string $nodeName )
{
python("AEpxrUsdProxyShapeTemplate.editorTemplate('"+$nodeName+"')");
}
''')
Loading
Loading