A lot of the UI is built via MEL commands and can be traced down to where it exists.
- Open Maya Script Editor
- Enable Echo all Commands
- Right click in the maya timeline (to show the menu)
You’ll now see what maya does in code, at that moment.
One of those commands is:
updateTimeSliderMenu TimeSlider|MainTimeSliderLayout|TimeSliderMenu;
Now, where is that? Let’s find out.
Run in MEL:
whatIs updateTimeSliderMenu;
Which returns e.g.:
// Result: Mel procedure found in: C:/Program Files/Autodesk/Maya2024/scripts/others/TimeSliderMenu.mel
Opening that file shows a command updateTimeSliderMenu
at the bottom that implements the menu it shows. It also shows that TimeSlider|MainTimeSliderLayout|TimeSliderMenu
is the actual menu name.
print(cmds.menu("TimeSlider|MainTimeSliderLayout|TimeSliderMenu", query=True, exists=True))
# True
So using that name we can modify the menu, e.g.
cmds.setParent("TimeSlider|MainTimeSliderLayout|TimeSliderMenu", menu=True)
cmds.menuItem(label="Hello World")
Or:
parent = "TimeSlider|MainTimeSliderLayout|TimeSliderMenu"
cmds.menuItem(label="Not again", parent=parent)
Making:
If you add something to the menu item before first show maya will not populate it - since their code basically checks “if empty: populate()” on first show.
You can either force populate it:
import maya.mel
maya.mel.eval("updateTimeSliderMenu TimeSlider|MainTimeSliderLayout|TimeSliderMenu;")
# Then do your magic stuff after
Or make sure your code runs after in another way.
Callback on menu about to show?
You can also add a callback to the menu to do more dynamic stuff:
from maya import cmds
from functools import partial
parent = "TimeSlider|MainTimeSliderLayout|TimeSliderMenu"
my_item = cmds.menuItem("MySpecialMenuItem", parent=parent)
def update_my_item(item, *args):
frame = cmds.currentTime(query=True)
cmds.menuItem(item, edit=True, label=f"Current frame is {frame}")
cmds.menu("TimeSlider|MainTimeSliderLayout|TimeSliderMenu", edit=True, postMenuCommand=partial(update_my_item, my_item))
But note that each menu will always have at most one pre-show command set via this - so might not give you all that you need plus you might be hitting issues with others relying on the callback to.
But there’s also Qt we can use.
from maya import cmds
import maya.OpenMayaUI
import shiboken2
from PySide2 import QtWidgets
menu_ptr = maya.OpenMayaUI.MQtUtil.findControl("TimeSlider|MainTimeSliderLayout|TimeSliderMenu")
menu = shiboken2.wrapInstance(int(menu_ptr), QtWidgets.QMenu)
def callback():
# Do something with the menu on show
import random
actions = menu.actions()
for action in actions:
menu.removeAction(action)
random.shuffle(actions)
for action in actions:
menu.addAction(action)
menu.aboutToShow.connect(callback)
And yes, this randomly shuffles the menu entries around.
Anyway, you might see some errors now:
// Error: file: C:/Program Files/Autodesk/Maya2024/scripts/others/TimeSliderMenu.mel line 1104: menuItem: Object 'playbackRealtimeItem' not found.
Apparently I removed something in the menu that Maya expected to exist and it didn’t like what I did with it.
But maybe we shouldn’t be removing entries, and such. However. You could use the QtWidgets.QMenu
to do your own stuff.
Now that the Qt aboutToShow
callback can have as many listeners as you’d like. So you wouldn’t be fighting over someone else’s possible code of the menu.
Plus, you can do crazy stuff:
from maya import cmds
import maya.OpenMayaUI
import shiboken2
from PySide2 import QtWidgets
menu_ptr = maya.OpenMayaUI.MQtUtil.findControl("TimeSlider|MainTimeSliderLayout|TimeSliderMenu")
menu = shiboken2.wrapInstance(int(menu_ptr), QtWidgets.QMenu)
class MyActionWidget(QtWidgets.QWidgetAction):
def __init__(self, parent):
super(MyActionWidget, self).__init__(parent)
widget = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(widget)
edit = QtWidgets.QLineEdit()
layout.addWidget(edit)
self.setDefaultWidget(widget)
action_widget = MyActionWidget(menu)
menu.addAction(action_widget)
Or have very strong styling opinions:
menu.setStyleSheet("* {margin: 10px; padding: 10px; background-color: #FFFFFF}")
But admittedly nothing beats my personal preference:
from PySide2 import QtWidgets
QtWidgets.QApplication.instance().setStyleSheet("""
* { font-family: "Comic Sans MS", "Comic Sans", cursive; color: #FFC0CB; font-size: 12px; }
""")
Sorry, what was your question again?
I guess you are now officially a Jedi.