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.
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
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
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
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.
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
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") ...
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
The result is something similar to this.
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.
And then finally let us have a look at the
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
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.
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
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
Additionally, you can also pass maya native commands as well. So to have a button that creates a poly cube we would do
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.
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.