Skip to content

Quick Start

Peter Hunt edited this page May 18, 2020 · 2 revisions

Launching a GUI

The window inherits off QMainWindow, so the syntax is the exact same for the most part. Other than the way it's launched, it's been built to be fully compatible with existing QMainWindow widgets.

This is the most basic example of how the class works. With no extra parameters, this will launched in Maya and Nuke as a docked widget.

class NewWindow(VFXWindow):
    WindowID = 'my_new_window'
    WindowName = 'My New Window'
    WindowDockable = True

    # Note: Never remove **kwargs, any extra required keyword arguments should come after parent=None
    def __init__(self, parent=None, **kwargs):
        super(VFXWindow, self).__init__(parent, **kwargs)

        # Perform setup here

if __name__ == '__main__':
    NewWindow.show()

WindowID

To handle the automatic cleanup, each window can be given a unique WindowID attribute. It deals with saving the previous position and dimensions of the window between sessions, and automatically closing any window that's launched a second time.

WindowName

It's recommended to set the WindowName attribute to give the window a title. While self.setWindowTitle() can be used (and effort has been made to make sure it works correctly), there's a chance some interfaces may not be able modify the title once the GUI is running.

WindowDockable

Determine if the dockable variants of a window should be used.

Current Application

The currently loaded program can be accessed by an attribute. To run code within Maya for example, you'd check if self.maya is True. See the AbstractWindow.__init__ method for a list of these attributes.

Callbacks

Any callback is automatically unregistered if the window is closed, so it's definitely recommended to use the helper methods to create them. If a requirement is to remove or pause a selection of callbacks at a later time, an optional group argument can be provided.

These are wrappers around the built in APIs, so the inputs are the same as what would be used normally. To manually remove callbacks, use self.removeCallbacks(group=None).

# Create callback for specific program version
if self.maya:
    self.addCallbackScene('kAfterNew', self.newScene, group='sceneNew')
elif self.nuke:
    self.addCallbackOnCreate(self.newScene, nodeClass='Root', group='sceneNew')

# Delete callback
self.removeCallback('sceneNew')

Maya

self.addCallbackEvent(callback, func, clientData=None, group=None)
self.addCallbackNode(callback, node, func, clientData=None, group=None)
self.addCallbackAttributeChange(node, func, clientData=None, group=None)
self.addCallbackAttributeAddOrRemove(node, func, clientData=None, group=None)
self.addCallbackNodeRename(node, func, clientData=None, group=None)
self.addCallbackNodeDirty(node, func, clientData=None, group=None)
self.addCallbackNodeDirtyPlug(node, func, clientData=None, group=None)
self.addCallbackUuidChange(node, func, clientData=None, group=None)
self.addCallbackKeyableChange(node, func, clientData=None, group=None)
self.addCallbackNodeRemove(node, func, clientData=None, group=None)
self.addCallbackScene(callback, func, clientData=None, group=None)
self.addCallbackJobEvent(callback, func, group=None, runOnce=False)
self.addCallbackJobCondition(callback, func, group=None, runOnce=False)
self.addCallbackNodeTypeAdd(func, nodeType='dependNode', clientData=None, group=None)
self.addCallbackNodeTypeRemove(func, nodeType='dependNode', clientData=None, group=None)
self.addCallbackTimeChange(func, clientData=None, group=None)
self.addCallbackForceUpdate(func, clientData=None, group=None)
self.addCallbackConnectionAfter(func, clientData=None, group=None)
self.addCallbackConnectionBefore(func, clientData=None, group=None)

Nuke

self.addCallbackOnUserCreate(func, nodeClass=None, group=None)
self.addCallbackOnCreate(func, nodeClass=None, group=None)
self.addCallbackOnScriptLoad(func, nodeClass=None, group=None)
self.addCallbackOnScriptSave(func, nodeClass=None, group=None)
self.addCallbackOnScriptClose(func, nodeClass=None, group=None)
self.addCallbackOnDestroy(func, nodeClass=None, group=None)
self.addCallbackKnobChanged(func, nodeClass=None, group=None)
self.addCallbackUpdateUI(func, nodeClass=None, group=None)

Blender

self.addCallbackFrameChangeAfter(func, persistent=True, group=None)
self.addCallbackFrameChangeBefore(func, persistent=True, group=None)
self.addCallbackGameAfter(func, persistent=True, group=None)
self.addCallbackGameBefore(func, persistent=True, group=None)
self.addCallbackLoadSceneAfter(func, persistent=True, group=None)
self.addCallbackLoadSceneBefore(func, persistent=True, group=None)
self.addCallbackRenderCancel(func, persistent=True, group=None)
self.addCallbackRenderComplete(func, persistent=True, group=None)
self.addCallbackRenderInit(func, persistent=True, group=None)
self.addCallbackRenderAfter(func, persistent=True, group=None)
self.addCallbackRenderBefore(func, persistent=True, group=None)
self.addCallbackRenderStats(func, persistent=True, group=None)
self.addCallbackRenderWrite(func, persistent=True, group=None)
self.addCallbackSaveSceneAfter(func, persistent=True, group=None)
self.addCallbackSaveSceneBefore(func, persistent=True, group=None)
self.addCallbackSceneUpdateAfter(func, persistent=True, group=None)
self.addCallbackSceneUpdateBefore(func, persistent=True, group=None)
self.addCallbackVersionUpdate(func, persistent=True, group=None)
self.addCallbackDepsgraphUpdateAfter(func, persistent=True, group=None)
self.addCallbackDepsgraphUpdateBefore(func, persistent=True, group=None)
self.addCallbackUndoAfter(func, persistent=True, group=None)
self.addCallbackUndoBefore(func, persistent=True, group=None)
self.addCallbackRedoAfter(func, persistent=True, group=None)
self.addCallbackRedoBefore(func, persistent=True, group=None)

Signals

Signals can be handled in the same way as callbacks, with an option for pausing them.

# Add the signal
self.signalConnect(widget.textChanged, self.func, group='textChanged')

# Pause the signal
with self.signalPause('textChanged'):
    inputWgt.setText('did not trigger')
inputWgt.setText('did trigger')

# Remove the signal
self.signalDisconnect('textChanged')
inputWgt.setText('did not trigger')

Deferred Execution

Each program has different ways to defer execution, so it is all wrapped under self.deferred(func). If the program does not support for deferring, then calling self.deferred will just execute the function straight away.

Window Style

It's possible to map the user interface colours to that of an application using self.setWindowPalette(program, version=None). If nothing happens, it's because I've found the current application will cause problems and have manually disabled it. While it might be fun having Maya look like Nuke, it'll start spitting out a ton of errors and will likely crash.

self.setWindowPalette('Maya', 2018)

Parent to Window

As each window needs to clean its own callbacks up, a special way must be used to insert windows inside other windows.

Parented inside VFXWindow

Use ChildWindow.instance(self) inside the parent window to get the properly set up window.

class ContainerWindow(VFXWindow):
    def __init__(self, parent=None, **kwargs):
        super(VFXWindow, self).__init__(parent, **kwargs)

        self.setCentralWidget(ChildWindow.instance(self).centralWidget())

Parented inside other widget (not recommended)

Make sure VFXWindow.clearWindowInstance(ChildWindow.WindowID) is called when the widget closes.