Scripting a custom shelf in Maya with Python
So, a few months back I was looking into clean ways of scripting a custom shelf in Maya, that can be easily shared, and I was surprised that there were not many resources on how to go about it. Therefore, I thought I would share the way that I am maintaining my shelf.
tl;dr Grab the code for the shelf base class from here and derive your own shelf class from it, overwriting and populating the build()
method with calls to the addButton, addMenuItem, etc. calls to create your own custom maya shelf.
Scripting your own custom shelf in Maya is much easier than you would think, or at least than I thought. For some odd reason, I always assumed that it would involve a lot of messing about with MEL, which I would really rather not. If that is what you thought as well, you will be pleasantly surprised.
Here is what we are trying to achieve.
Since, we want to keep the code as modular and versatile as possible, I wrote the main functionality in a base class which needs to be extended for each shelf you want to build. The code for the base class with a little example is on github.
Now we will go through it to make see how it works, so it can be extended and modified to include more functionalities.
Constructor
def __init__(self, name="customShelf", iconPath=""):
self.name = name
self.iconPath = iconPath
self.labelBackground = (0, 0, 0, 0)
self.labelColour = (.9, .9, .9)
self._cleanOldShelf()
mc.setParent(self.name)
self.build()
In the constructor we initialize the variables we make two calls to _cleanOldShelf()
and build()
, which we will look at in a bit. The name
argument is going to be the name of the shelf. It is important to note that if a shelf with this name already exists it will be replaced with this one. The iconPath
argument can be used to define a directory from which to get images to be used as icons of the shelf buttons and commands in the popup menus. If it is not defined, you can still use maya’s default icons like commandButton.png for example.
Additionally there are the labelBackground
and labelColour
variables which can be seen in the following image.
The reason I have hardcoded them in the base class is because I think for the sake of consistency all our shelves should have the same style, but if you want to change them, obviously feel free to do it.
And lastly, there is that mc.setParent(self.name)
function, which makes sure that whatever ui elements we are going to build in the following build()
method, they will be build as children to our shelf. Otherwise, we might end up with buttons breaking the other layouts.
Clean old shelf
Let’s have a look at what the _cleanOldShelf()
method does.
def _cleanOldShelf(self):
if mc.shelfLayout(self.name, ex=1):
if mc.shelfLayout(self.name, q=1, ca=1):
for each in mc.shelfLayout(self.name, q=1, ca=1):
mc.deleteUI(each)
else:
mc.shelfLayout(self.name, p="ShelfLayout")
Essentially, we are checking if our shelf already exists by using the mc.shelfLayout(self.name, ex=1)
command where self.name
is the name of our shelf and ex
is the flag for checking existence. If it does we go through all children of the shelf which we get from mc.shelfLayout(self.name, q=1, ca=1)
and delete them. That makes sure we start our build()
method with a fresh clean shelf.
If the shelf does not exist though, we simply create it, passing "ShelfLayout"
as parent, because that is the parent layout of all the shelves in maya. For example, turn on History > Echo all commands
in the the script editor and click through some of the shelves. You will see the paths to some of their popupMenus as so
Shelf|MainShelfLayout|formLayout16|ShelfLayout|CurvesSurfaces||popupMenu546
So, knowing that CurvesSurfaces
is a name of a shelf, we can deduce that the ShelfLayout
is the parent of all shelves.
Then, before we get into the build()
method, let’s have a look at some of the methods we are going to use to actually populate our shelf.
Add button
def addButon(self, label, icon="commandButton.png", command=_null, doubleCommand=_null):
mc.setParent(self.name)
if icon:
icon = self.iconPath + icon
mc.shelfButton(width=37, height=37, image=icon, l=label, command=command, dcc=doubleCommand, imageOverlayLabel=label, olb=self.labelBackground, olc=self.labelColour)
Very simply, this is the method that creates the main buttons in the shelf. Essentially, we just need to pass it a label
which is the name of our button, an optional icon
and an optional command
and doubleCommand
which refer to single click and double click. The command is optional, because we might want buttons that just have popup menus. We will look at those in a sec. By default the _null()
command, which is at the top of the file is called. The reason I have that command is that if you pass None
to the mc.shelfButton
command flag, it will error. So the _null()
method essentially does nothing. It is important to note that when we pass a command we are not using the brackets after the name ()
, because that would call the command instead of passing it.
Add menu item
def addMenuItem(self, parent, label, command=_null, icon=""):
if icon:
icon = self.iconPath + icon
return mc.menuItem(p=parent, l=label, c=command, i="")
This method is very similar to the add button one, but instead of adding a button to the shelf, this one adds a menuItem
to an existing popupMenu
passed as the parent argument. The popupMenu
needs to be created manually, as it doesn’t make sense to wrap it in a method if it is just a simple one line command anyway. So, a quick example of how that works is
...
self.addButon("popup")
p = mc.popupMenu(b=1)
self.addMenuItem(p, "popupMenuItem1")
...
where the b=1
stands for left mouse click. This snippet if added inside the build()
method will attach a popupMenu
with one menuItem
to the popup button.
Add sub menu
def addSubMenu(self, parent, label, icon=None):
if icon:
icon = self.iconPath + icon
return mc.menuItem(p=parent, l=label, i=icon, subMenu=1)
Now, I realize that sub menus inside a popup on a shelf button is getting a bit too deep, as the whole point of a shelf is to streamline everything, but I thought I would add it, as even I have used it on a couple of my buttons.
What this method does is, it creates a menuItem
which is a menu in itself, attached to an existing popupMenu
which is passed as the parent
.
The result is something similar to this.
Separators
You know how some of the shelves have separating lines between the buttons. You can achieve that with the mc.separator()
command. I usually set the style
flag to "none"
as I rather having blank space than having lines, but have a look at the docs page for the separator command for more info.
Build
And then finally let us have a look at the build()
method.
def build(self):
'''This method should be overwritten in derived classes to actually build the shelf
elements. Otherwise, nothing is added to the shelf.'''
pass
As the docstring says, this is an empty method that needs to be defined in each shelf we build. The way this works is, we just populate it with addButton()
, mc.popup()
, addMenuItem()
and addSubMenu()
calls as we need them.
Example of a custom shelf in maya
Let’s have a look at the simple example in the end of the file.
class customShelf(_shelf):
def build(self):
self.addButon(label="button1", command=mc.polyCube)
self.addButon("button2")
self.addButon("popup")
p = mc.popupMenu(b=1)
self.addMenuItem(p, "popupMenuItem1")
self.addMenuItem(p, "popupMenuItem2")
sub = self.addSubMenu(p, "subMenuLevel1")
self.addMenuItem(sub, "subMenuLevel1Item1")
sub2 = self.addSubMenu(sub, "subMenuLevel2")
self.addMenuItem(sub2, "subMenuLevel2Item1")
self.addMenuItem(sub2, "subMenuLevel2Item2")
self.addMenuItem(sub, "subMenuLevel1Item2")
self.addMenuItem(p, "popupMenuItem3")
self.addButon("button3")
customShelf()
As you can see, all we do is we create a new class inheriting from the _shelf
class. By the way the _
prefix is used to remind us that it is a class that should not be directly accessed.
Then we overwrite the build()
method by simply defining it, and we populate it with the necessary shelf elements.
This example results in the image from above. Here is it again.
Commands
I have not demonstrated the use of any commands on these buttons, but all it takes is just passing the name of the function as a command
argument. The one tricky bit about it is that the menuItem
commands get passed some arguments, so we need to have *args
in our function calls to prevent errors. For example
def createPolyCube(*args):
mc.polyCube()
We would include this command in our shelf by doing self.addButton(label="cube", command=createPolyCube)
in our build()
method.
For the sake of clarity though, I recommend having a separate file which contains only the functions that are going to be used in the shelf and import them from there. So at the top of the file where are custom shelf is defined, import your shelf functions file with import shelfFunctions
or however you decide to package them. I would advise having a reload(shelfFunctions)
statement immediately after that, so you can be sure the latest changes to the file have been taken into account.
And then when creating buttons, just do
self.addButton(label="cube", command=shelfFunctions.createPolyCube)
Additionally, you can also pass maya native commands as well. So to have a button that creates a poly cube we would do
self.addButton(label="cube", command=mc.polyCube)
Building our shelf on load up
The way we will include our shelf on load up is to just add it to our userSetup.py
file. If you are not familiar with it, take a look at this. I know this one is for MEL, but it is essentially the same thing for Python as well.
So, assuming that our shelf file is in the maya scripts path, in our userSetup.py
we can just do
import maya.cmds as mc
import shelf
mc.evalDeferred("shelf.customShelf()")
where shelf is the file containing our shelf and customShelf
is the name of the class.
We are using the evalDeferred()
command, because maya loads the userSetup.py
file as it is starting, so we are not sure if the shelfLayout
is already in place as it gets executed. Therefore, evalDeferred()
helps us call our script after maya has been initialized.
Conclusion
And that is it, you have built your own shelf. What I really like about it is that if you are collaborating with other people you can add the shelf file to your version control system and in your userSetup.py
file just source it from there. That way you can all contribute to it and have it always updated.
If you have any questions, shoot me a message.