Skip to content

Commit

Permalink
EMSUSD-1657 light linking undo and redo
Browse files Browse the repository at this point in the history
- Added methods on the Host class to create the data used by the UI.
- Host create USD collection and string list by default.
- Use the Host to create the data in the UI classes.
- UsdCollectionData uses Host to create its string lists.
- Make the expression widget update itself on USD data changed
  notification even when there is no expression attribute.
- Rename the light linking custom control to be collection custom
  control.
- Move the Maya host class to its own file.
- Implement Maya-specific collection data with all needed setters.
- Add a _UsdUndoBlockContext class to capture all USD changes into
  undo items and later transfer them to a Maya command.
- Add a _UsdUndoBlockCommand that tarnsfer the captured undo items
  into itself for future undo and redo.
- Add a _UndoItemHolder class to hold the undo items to be transferred
  between the Python undo context and the Maya command.
- Add multiple command sub-classes so that the undo UI shows a nice and
  comprehensible undo name.

In theory, we could have just used the existing UsdUndoBlock and that
was in the initial prototype, but this makes all entries in the undo UI
all be "UsdUndoBlock" which is incomprehensible for the user and does
not state what would be undone or redone. Using one command per action
allows the user to know what is in the undo stack.
  • Loading branch information
pierrebai-adsk committed Jan 10, 2025
1 parent 058e1ca commit 931f4da
Show file tree
Hide file tree
Showing 9 changed files with 407 additions and 29 deletions.
3 changes: 2 additions & 1 deletion lib/mayaUsd/resources/ae/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ install(FILES __init__.py DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python/${PROJE
if(MAYA_APP_VERSION VERSION_GREATER_EQUAL 2023)
foreach(_SUBDIR ${MAYAUSD_AE_TEMPLATES})
install(FILES
${_SUBDIR}/lightCustomControl.py
${_SUBDIR}/collectionCustomControl.py
${_SUBDIR}/collectionMayaHost.py
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python/ufe_ae/usd/nodes/${_SUBDIR}
)
endforeach()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ def __init__(self, data: CollectionData, parent: QWidget, expressionChangedCallb

def _onDataChanged(self):
usdExpressionAttr = self._collData.getMembershipExpression()
if usdExpressionAttr != None:
self._expressionText.setPlainText(usdExpressionAttr)
self._expressionText.setPlainText(usdExpressionAttr or '')

def submitExpression(self):
self._collData.setMembershipExpression(self._expressionText.toPlainText())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
from .includeExcludeWidget import IncludeExcludeWidget
from .expressionWidget import ExpressionWidget
from ..common.theme import Theme
from ..common.host import Host
from ..usdData.usdCollectionData import UsdCollectionData

try:
from PySide6.QtCore import QEvent, QObject, Qt, Slot # type: ignore
from PySide6.QtCore import Qt # type: ignore
from PySide6.QtGui import QIcon, QWheelEvent # type: ignore
from PySide6.QtWidgets import QTabBar, QTabWidget, QVBoxLayout, QWidget # type: ignore
except ImportError:
from PySide2.QtCore import QEvent, QObject, Qt, Slot # type: ignore
from PySide2.QtCore import Qt # type: ignore
from PySide2.QtGui import QIcon, QWheelEvent # type: ignore
from PySide2.QtWidgets import QTabBar, QTabWidget, QVBoxLayout, QWidget # type: ignore

from pxr import Usd, Tf
from pxr import Usd


class NonScrollingTabBar(QTabBar):
Expand All @@ -38,7 +39,7 @@ def __init__(

self._collection: Usd.CollectionAPI = collection
self._prim: Usd.Prim = prim
self._collData = UsdCollectionData(prim, collection)
self._collData = Host.instance().createCollectionData(prim, collection)

mainLayout = QVBoxLayout()
mainLayout.setContentsMargins(0, 0, 0, 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,61 @@ def __init__(self):

@classmethod
def instance(cls):
'''
Retrieve the DCC-specific instance of this Host interface.
'''
if cls._instance is None:
cls._instance = cls.__new__(cls)
return cls._instance

@classmethod
def injectInstance(cls, host):
'''
Set the DCC-specific instance of this Host interface.
'''
cls._instance = host

@property
def canPick(self) -> bool:
'''
Verify if the DCC-specific instance of this Host interface can pick USD prims.
'''
return False

@property
def canDrop(self) -> bool:
'''
Verify if the DCC-specific instance of this Host interface can drag-and-drop USD prims.
'''
return True

def pick(self, stage: Usd.Stage, *, dialogTitle: str = "") -> Sequence[Usd.Prim]:
return None
'''
Pick USD prims.
Must be implemented by the DCC-specific sub-class of this Host interface if the DCC
supports picking USD prims.
'''
return None

def createCollectionData(self, prim: Usd.Prim, collection: Usd.CollectionAPI):
'''
Create the data to hold a USD collection.
Can be implemented by the DCC-specific sub-class of this Host interface
to return a DCC-specific implementation of the data, to support undo and
redo, for example.
'''
from ..usdData.usdCollectionData import UsdCollectionData
return UsdCollectionData(prim, collection)

def createStringListData(self, collection: Usd.CollectionAPI, isInclude: bool):
'''
Create the data to hold a list of included or excluded items.
Can be implemented by the DCC-specific sub-class of this Host interface
to return a DCC-specific implementation of the data, to support undo and
redo, for example.
'''
from ..usdData.usdCollectionStringListData import CollectionStringListData
return CollectionStringListData(collection, isInclude)
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from typing import AnyStr, Sequence
from ..data.collectionData import CollectionData
from .usdCollectionStringListData import CollectionStringListData
from ..common.host import Host

from pxr import Sdf, Tf, Usd

class UsdCollectionData(CollectionData):
def __init__(self, prim: Usd.Prim, collection: Usd.CollectionAPI):
super().__init__()
self._includes = CollectionStringListData(collection, True)
self._excludes = CollectionStringListData(collection, False)
from ..common.host import Host
self._includes = Host.instance().createStringListData(collection, True)
self._excludes = Host.instance().createStringListData(collection, False)
self._noticeKey = None
self.setCollection(prim, collection)

Expand Down
10 changes: 5 additions & 5 deletions lib/mayaUsd/resources/ae/usdschemabase/ae_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
from .metadataCustomControl import MetadataCustomControl
from .observers import UfeAttributesObserver, UfeConnectionChangedObserver, UsdNoticeListener
try:
from .lightCustomControl import LightLinkingCustomControl
lightLinkingSupported = True
from .collectionCustomControl import CollectionCustomControl
collectionsSupported = True
except:
lightLinkingSupported = False
collectionsSupported = False

import collections
import fnmatch
Expand Down Expand Up @@ -272,8 +272,8 @@ def __init__(self, ufeSceneItem):
pass

_controlCreators = [ConnectionsCustomControl.creator, ArrayCustomControl.creator, ImageCustomControl.creator, defaultControlCreator]
if lightLinkingSupported:
_controlCreators.insert(0, LightLinkingCustomControl.creator)
if collectionsSupported:
_controlCreators.insert(0, CollectionCustomControl.creator)

@staticmethod
def prependControlCreator(controlCreator):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import maya.cmds as cmds
from pxr import Usd
from typing import Sequence

from usd_shared_components.collection.widget import CollectionWidget # type: ignore
from usd_shared_components.common.host import Host # type: ignore

class MayaHost(Host):
'''Class to host and override maya specific functions for the collection API.'''
def __init__(self):
pass

def pick(self, stage: Usd.Stage, *, dialogTitle: str = "") -> Sequence[Usd.Prim]:
return [] # nothing to do yet

from .collectionMayaHost import MayaHost

class LightLinkingCustomControl(object):
class CollectionCustomControl(object):
'''Custom control for the light linking data we want to display.'''

@staticmethod
Expand All @@ -41,16 +33,16 @@ def creator(aeTemplate, attrName):
'''
If the attribute is a collection attribute then create a section to edit it.
'''
if LightLinkingCustomControl.isCollectionAttribute(aeTemplate, attrName):
if CollectionCustomControl.isCollectionAttribute(aeTemplate, attrName):
attrName, instanceName = attrName.split(':')
return LightLinkingCustomControl(aeTemplate.item, aeTemplate.prim, attrName, instanceName, aeTemplate.useNiceName)
return CollectionCustomControl(aeTemplate.item, aeTemplate.prim, attrName, instanceName, aeTemplate.useNiceName)
else:
return None

def __init__(self, item, prim, attrName, instanceName, useNiceName):
# In Maya 2022.1 we need to hold onto the Ufe SceneItem to make
# sure it doesn't go stale. This is not needed in latest Maya.
super(LightLinkingCustomControl, self).__init__()
super(CollectionCustomControl, self).__init__()
mayaVer = '%s.%s' % (cmds.about(majorVersion=True), cmds.about(minorVersion=True))
self.item = item if mayaVer == '2022.1' else None
self.attrName = attrName
Expand Down
Loading

0 comments on commit 931f4da

Please sign in to comment.