Real Time update between Maya and Gui

#1

Hi everyone, I am trying to make a real-time update, eg. a renaming between maya scene and my tool.

Currently I am able to effect changes from my tool and modify the naming in the scene. But in the event if I perform an undo in the maya scene itself, the undo changes are not effected back into the tool.

Here is my code:

class Example(QtGui.QWidget):
    def __init__(self, parent=None):
        super(Example, self).__init__()
        self.initUI()
        self.setup_connections()
        

    def initUI(self):         
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Rename')

        self.list_widget = QtGui.QListWidget()
        self.add_loc_btn = QtGui.QPushButton("Add new Locator")

        vlayout = QtGui.QVBoxLayout()
        vlayout.addWidget(self.list_widget)
        vlayout.addWidget(self.add_loc_btn)
        vlayout.addStretch(1)
        self.setLayout(vlayout)

    def setup_connections(self):
        self.add_loc_btn.clicked.connect(self.add_locator)
        self.list_widget.itemDoubleClicked.connect(self.list_item_dbl_click)
        self.list_widget.itemChanged.connect(self.list_item_click)


    def add_locator(self):
        new_loc = cmds.spaceLocator()
        loc_fp = cmds.ls(new_loc[0], long=True)
        loc_item = QtGui.QListWidgetItem(str(new_loc[0]))
        loc_item.setData(32, loc_fp[0])
        loc_item.setFlags(
            loc_item.flags() |
            QtCore.Qt.ItemIsUserCheckable |
            QtCore.Qt.ItemIsEditable
        )
        self.list_widget.addItem(loc_item)

    def list_item_dbl_click(self):
        current_item = self.list_widget.currentItem()
        self.prev_name = current_item.text()
        self.rename_counter = 1

    def list_item_click(self, test):
        success = False
        current_item = self.list_widget.currentItem()
        list_item_row = self.list_widget.currentRow()
        new_name = current_item.text()

        if self.validate_naming(new_name):
            locator = current_item.data(32)
            if cmds.objExists(locator) and self.rename_counter == 1:
                cmds.select(locator)
                cmds.rename(new_name)

                # reset counter back to 0
                self.rename_counter = 0
                success = True

        else:
            # Set back to its initial naming before rename
            current_item.setText(self.prev_name)

        if success:
            list_item = self.list_widget.item(list_item_row)
            self.list_widget.setCurrentItem(list_item)

    def validate_naming(self, name_input):
        if re.search("^\s*[0-9]", name_input):
            LOG.warning("Input naming cannot be started off with numbers!")
            return False
        return True


myWin = Example()
myWin.show()

Here are some steps to reproduce:

  • Click on the ‘Add new Locator’ button
  • It will creates a locator1 in the scene and populated in the list widget
  • Double click on the locator1 in the list widget and input in a new name. Eg. ‘myLocator’
  • Go back to Maya and tried pressing ‘Ctrl+Z’ (undo)
  • While it does changes the name ‘myLocator’ back to ‘locator1’ in Maya context, I would also like it to be ‘updated’ in my Gui.

How should I go about doing this?
P.S: I was also trying to find a way to peform undoing in the Gui which will also in turns effects the changes in the Maya scene if possible.

#2

You could create a few scriptJobs and subscribe them to undo and rename events, and perform necessary UI update. At least this is the first things that comes to mind, curious if there are more elegant solutions.

#3

Hey Nix, thanks for replying back.
About the script jobs that you have mentioned, how can I ‘tied’ the events to the Gui?

While googling around, I found MVC. I have not tried it before, but could that be a viable solution for it?
Rather, when is the appropriate cause to be using MVC then?

#4

In this particular case MVC isn’t really going to solve the synchronization issues.

You need to be listening for Maya related events, either through scriptJob or one of the MMessage based classes available in the api.

#5

Hi @Teh_Ki,

I’d use an event callback - set a property in your class, test/remove if it exists first then instantiate it. This is if the selection changes but I’m pretty sure there are name change callback events too. You can also register your own! Also remember to remove the callback on a close interface event:

import maya.api.OpenMaya as apiOM

_callback = None

if self._callback:
   apiOM.MMessage.removeCallback(self._callback)

# "SelectionChanged" is one of many - you can use 
# apiOM.MEventMessage.getEventNames() to return
# a list.

# self._callback_method - this is YOUR method.

self._callback = apiOM.MEventMessage.addEventCallback(
   "SelectionChanged", self._callback_method) 

# on close 'X' on the interface - *note this isn't custom, just
# a member function of QtWidgets.QMainWindow

def closeEvent(self, *args, **kwargs):
    apiOM.MMessage.removeCallback(self._callback)

On undo - your actually storing the redo event (if that makes sense) - so your basically storing state, capturing all your actions into a redo buffer, then rolling back to the previous state. On redo, you instigate your redo buffer again. So you have two buffers: one the undo states and the actions in the redo - both are finite. Qt (which PySide wraps) does have an undo/redo stack system you can potentially leverage:

https://doc.qt.io/qtforpython/PySide2/QtWidgets/QUndoStack.html

As @bob.w said, truly managing MVC in Maya is really tricky - because state is NOT consistent and neither Maya nor your tool know about the scene data (unless you store it or query it) So you have to decide who takes the lead and from a user perspective who is the director of the situation. I.e. if your interface changes the scene, all the heavy lifting in the scene should come from it too really.

In simple terms if your interfaces manipulates a cube, and you go and decide to extrude one of its faces - your interfaces does not know about that change i.e. the state is different from what the tool assumes.

Theres a bigger discussion about state vs operation and I guess the perception of control when building tools, methods, classes etc… but thats for another day :slight_smile:

-c

1 Like
#6

Hi chalk, thanks for getting back.
In the end, I am using scriptJob that tracks the selection as well as the undo (within maya context.

#7

Here’s a library you might find useful for working with “pseudo-concurrent” UI in maya. It basically gives you a single “event loop” like you’d have in a regular GUI framework but it’s attached to a script job in the main thread so it does not create the usual deadly blowups that come from combining Maya GUI and threads:

One of the examples includes quasi-real-time update of sports scores off the internet while keeping the scene responsive.