55import contextlib
66
77from maya import cmds , OpenMaya
8+ import maya .utils
9+ import maya .api .OpenMaya as om
810from pyblish import api as pyblish
911
1012from . import lib , compat
@@ -93,6 +95,16 @@ def find_host_config(config):
9395 return config
9496
9597
98+ def get_main_window ():
99+ """Acquire Maya's main window"""
100+ if self ._parent is None :
101+ self ._parent = {
102+ widget .objectName (): widget
103+ for widget in QtWidgets .QApplication .topLevelWidgets ()
104+ }["MayaWindow" ]
105+ return self ._parent
106+
107+
96108def uninstall (config ):
97109 """Uninstall Maya-specific functionality of avalon-core.
98110
@@ -194,15 +206,20 @@ def deferred():
194206 command = interactive .reset_resolution )
195207
196208 # Allow time for uninstallation to finish.
197- QtCore .QTimer .singleShot (100 , deferred )
209+ # We use Maya's executeDeferred instead of QTimer.singleShot
210+ # so that it only gets called after Maya UI has initialized too.
211+ # This is crucial with Maya 2020+ which initializes without UI
212+ # first as a QCoreApplication
213+ maya .utils .executeDeferred (deferred )
198214
199215
200216def launch_workfiles_app (* args ):
201217 workfiles .show (
202218 os .path .join (
203219 cmds .workspace (query = True , rootDirectory = True ),
204220 cmds .workspace (fileRuleEntry = "scene" )
205- )
221+ ),
222+ parent = self ._parent
206223 )
207224
208225
@@ -248,18 +265,21 @@ def reload_pipeline(*args):
248265 module = importlib .import_module (module )
249266 reload (module )
250267
251- self ._parent = {
252- widget .objectName (): widget
253- for widget in QtWidgets .QApplication .topLevelWidgets ()
254- }["MayaWindow" ]
268+ get_main_window ()
255269
256270 import avalon .maya
257271 api .install (avalon .maya )
258272
259273
260274def _uninstall_menu ():
261- app = QtWidgets .QApplication .instance ()
262- widgets = dict ((w .objectName (), w ) for w in app .allWidgets ())
275+
276+ # In Maya 2020+ don't use the QApplication.instance()
277+ # during startup (userSetup.py) as it will return a
278+ # QtCore.QCoreApplication instance which does not have
279+ # the allWidgets method. As such, we call the staticmethod.
280+ all_widgets = QtWidgets .QApplication .allWidgets ()
281+
282+ widgets = dict ((w .objectName (), w ) for w in all_widgets )
263283 menu = widgets .get (self ._menu )
264284
265285 if menu :
@@ -422,7 +442,7 @@ def containerise(name,
422442 return container
423443
424444
425- def parse_container (container , validate = True ):
445+ def parse_container (container ):
426446 """Return the container node's full container data.
427447
428448 Args:
@@ -440,28 +460,59 @@ def parse_container(container, validate=True):
440460 # Append transient data
441461 data ["objectName" ] = container
442462
443- if validate :
444- schema .validate (data )
445-
446463 return data
447464
448465
449466def _ls ():
450- containers = list ()
451- for identifier in (AVALON_CONTAINER_ID ,
452- "pyblish.mindbender.container" ):
453- containers += lib .lsattr ("id" , identifier )
467+ """Yields Avalon container node names.
454468
455- return containers
469+ Used by `ls()` to retrieve the nodes and then query the full container's
470+ data.
471+
472+ Yields:
473+ str: Avalon container node name (objectSet)
474+
475+ """
476+
477+ def _maya_iterate (iterator ):
478+ """Helper to iterate a maya iterator"""
479+ while not iterator .isDone ():
480+ yield iterator .thisNode ()
481+ iterator .next ()
482+
483+ ids = {AVALON_CONTAINER_ID ,
484+ # Backwards compatibility
485+ "pyblish.mindbender.container" }
486+
487+ # Iterate over all 'set' nodes in the scene to detect whether
488+ # they have the avalon container ".id" attribute.
489+ fn_dep = om .MFnDependencyNode ()
490+ iterator = om .MItDependencyNodes (om .MFn .kSet )
491+ for mobject in _maya_iterate (iterator ):
492+ if mobject .apiTypeStr != "kSet" :
493+ # Only match by exact type
494+ continue
495+
496+ fn_dep .setObject (mobject )
497+ if not fn_dep .hasAttribute ("id" ):
498+ continue
499+
500+ plug = fn_dep .findPlug ("id" , True )
501+ value = plug .asString ()
502+ if value in ids :
503+ yield fn_dep .name ()
456504
457505
458506def ls ():
459- """List containers from active Maya scene
507+ """Yields containers from active Maya scene
460508
461509 This is the host-equivalent of api.ls(), but instead of listing
462510 assets on disk, it lists assets already loaded in Maya; once loaded
463511 they are called 'containers'
464512
513+ Yields:
514+ dict: container
515+
465516 """
466517 container_names = _ls ()
467518
@@ -592,10 +643,7 @@ def _on_maya_initialized(*args):
592643 return
593644
594645 # Keep reference to the main Window, once a main window exists.
595- self ._parent = {
596- widget .objectName (): widget
597- for widget in QtWidgets .QApplication .topLevelWidgets ()
598- }["MayaWindow" ]
646+ get_main_window ()
599647
600648
601649def _on_scene_new (* args ):
0 commit comments