PySide Decorators for Unity-like Context Menus

posted in: Uncategorized | 0

Today I wanted to share a recipe I’ve cooked up for creating menus in PySide. Having worked within the QWidgets framework of Qt for a while, I’ve always found the process of creating context menus and menu bars needlessly complex and error-prone. During my struggles trying to get hotkeys to work correctly and nested menus to build in the right order, I found myself reminiscing about Unity — in particular, its many intelligent API design choices made to aid with editor extensions. Fed up with the time I was spending on each new tool to get menu systems working correctly, I decided to develop a reusable library that would make me feel right at home.

The core problems I struggle with in PySide’s menu system are:

  • Creating hotkeys that actually fire QActions regardless of the state of the QMenu or QMenuItem
  • Building and managing nested QMenus
  • Flexibly specifying QMenuItem order in a QMenu
  • Creating callbacks for each QAction’s triggered signal

In PySide, I wanted to be able to do something like this Unity C# snippet:


// Add a new menu item with hotkey CTRL-A
[MenuItem("Tools/Create Actor %a")]
private static void CreateActor()
{
    // Do stuff...
}

And I ended up with this in:


# Adds a new menu item with hotkey CTRL-A
@AppContextMenu.register_action("Tools/Create Actor", shortcut="Ctrl+A")
def create_actor():
    # Do Stuff...
    pass

# App-specific code
class DemoMenuApp(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(DemoMenuApp, self).__init__(parent=parent)
        # Connect the context menu to tree_view_widget's right click
        AppContextMenu.add_widget(self.tree_view_widget)

Not bad, eh? For reference, in PySide the context menu appears like:

context_menus_01

A more complex example of a context menu created with this system.

What It Does

The register_action decorator handles building all QMenus and sub menus by parsing out /’s in your action name. It correctly hooks up QActions to fire when keyboard shortcuts are fired. It allows you to specify manual order with automatically placed separators by specifying a priority value. And lastly, it allows certain items to be enabled or disabled based by providing a validator callable.

The full source with the implementation of the BaseContextMenuTree class can be found on my GitHub. I’ve also included a mini PySide app to show different uses and demo its full feature set.
https://github.com/Mouthlessbobcat/Python-Projects/blob/master/milk/lib/qt/contextmenu.py

Afterthoughts

Implementing Unity’s API for creating menus was a neat exploration. In the process, I’ve come to terms with some of its design limitations. In both Unity’s and my implementation, it is not possible to pass arbitrary arguments into the decorated functions. Unity provides the user with some flexibility to unbox a generic “context object” into a specific type based on the context of the menu item. My system could be extended to do similar, but this relies on specific conventions and predetermined contexts. The system works best when functions are defined to operate on “static” objects and methods or where all the arguments required in the menu item’s action can be acquired from external systems.

Because Python evaluates its decorators as soon as the module is imported, all instances of my BaseContextMenuTree class must be declared in code as well. It is not possible to create new menu trees during runtime. However, it is possible to disable and enable certain menu items based on the “validator” parameter in the decorator. The validator is any callable that returns a boolean value. The callable is called each time the context menu is about to present itself. This allows for dynamic control of individual menu items. Validators offer more than enough freedom, so the limitation of not being able to create¬†new menu trees becomes much easier to cope with.

For further reading,¬†Unity’s documentation on its Menu Items system can be found here:

https://unity3d.com/learn/tutorials/modules/intermediate/editor/menu-items

Baby’s First Substance

posted in: Uncategorized | 0

Forward

Let’s get back to the blogging thing. I’ve recently picked up Substance Designer + Painter and the motivation to start making some arts. It’s been ages since I’ve posted here and even longer since I’ve modeled anything. Work has me wholly focused on code, so to keep my head balanced I’m starting up some small projects on the side to practice (read: learn) PBR and get back into Unity. But you say you’re only interesting in codes? Don’t worry. We’ll keep exploring cool patterns and strategies in Python and C# for application in art tools!


The Grenade Tutorial

I recently completed a fantastic tutorial by Tim Bergholz on Gum Road. The two-part series is free and goes through the entire process of modeling a realistic grenade in 3ds Max, followed by baking and texturing in Substance Painter. Having been my first project in Painter, I can say I’ll fallen in love. The baking workflow is thoughtfully designed, comprehensive, and fast! There are some superbly clever and intuitive design decisions made for layering materials together, like the ability to bias a layer’s height map up or down. This helps you nail those subtle depth changes across materials!

Here’s my results in Unity 2048×2048 textures…

grenade_render_beauty_01


Texture Maps

grenade_tex_maps
 

Bonus Points

I took the tutorial one step further and brought the asset into Unity for a real-time turnaround render. There were some tweaks needed to the normal map bake to account for Unity’s tangent space. I also needed to pack my roughness map into the alpha channel of my roughness map map and invert this roughness map into a metallic map. (I should totally make a Substance Designer material to automate this and provide clean exports for Unity.)
 
grenade_turnaround

The most amazing thing? Nope. Inspired design? Nah. But that comes next!