Creating a custom panel with Blender’s Python API

Did you know you can create your own panels in Blender using the Python API?

This article is also available on Medium.

A couple of weeks ago, I talked about a nice feature of Blender, the open-source 3D soft: the possibility to do some Python scripting to automate tasks! In this previous post, I focused on how we can use the API to instantiate objects, create shaders or materials on the fly and even define some animation curves.

Today, I want to introduce another cool feature of this programmer approach to Blender: the ability to create your own windows and panels 🙂

Why should you create your own tools?

When you create a piece of software, an essential question you have to ask yourself is who you are designing it for. Oftentimes, you have an end-user, a client that has ordered the product or will consume it directly.

But, sometimes, you can also work on stuff for your own team and colleagues. This specific subtype of programming is usually referred to as “tooling”, because it’s mainly about supplying various scripts, systems and util softs to fill your shared toolbox.

🛠

This can be invaluable because cooking up a nice tool specifically suited to your needs can greatly simplify and/or shorten the overall workflow. The point is that, rather than analysing the likes, dislikes and current needs of a client market, you discuss with the people on your team to find ideas of how to help them with their everyday work.

This type of programming gig is particularly common in game dev. Yes, most of the “mainstream” programmers work on things that players see before their eyes when the play: the game mechanics, AIs, networking schemes, pathfinding algorithms… But some also work on the editor that these programmers use, they create super useful procedural generation libs or user-friendly animation pipelines, etc.

And yet… tooling is not constrained to the domain of game dev! It’s used everywhere in the industry – for example in CG…

By the way: if you want to learn how you can also create your own tools and windows in the Unity game engine, you can check out either this article about visual debugging in Unity or this other one I wrote about running async processed while in edit mode 🙂

The basics of making custom panels in Blender

The Blender Python API

Like lots of other 3D tools, Blender actually relies on a core API and the UI you’re clicking on and moving about is a visual wrapper around these internal functions. In Blender, this main API is written in Python and it can be accessed to level up your game and do a lot of crazy stuff!

Diving into this API and “getting programmatic” is particularly useful to automate tasks or add custom UI wrappers to your Blender session.

To me, having this scripting feature in CG software is great for multiple reasons:

  • it brings developers and artists together around a common tool
  • it conveys the open-source philosophy 😉
  • it allows programmers to extend the functionalities of the soft and configure it to their (or the artists’) liking!

Last time, I showed a basic example for the “automation part”. So, today, let’s rather focus on creating custom UI!

Preparing your Blender for scripting

If you haven’t read my article on procedural generation in Blender with the Python API, go ahead and have a look at the “Step 1: Preparing our Blender” section. It will show you how to run Blender with a live console to debug more easily your Python scripts and what the various parts of the “Scripting” layout are used for 🙂

After you’ve configured your Blender, you should be able to run this one-line Python script in the Text Editor and see the debug pop up in your console:

print('hello world')

Creating a basic panel

Important note: all the scripting I’m showing here is done with Blender 2.93 and, over the years, the Blender Python API has changed quite a lot with regards to writing addons and custom classes; in particular 2.8x had some breaking changes. So make sure you have a compatible version for this code to work 🙂

Before we dive into actually running actions and calling functions from our UI, let’s start by making a very simple example panel that displays the “Hello world” string.

To create a custom panel in Blender, you need to import the API via import bpy, then create a class that inherits from the bpy.types.Panel type and finally register this class in the bpy.utils. If your class is properly configured, this will make your custom panel appear somewhere in the layout…

… but this requires you set up some specific properties for your class! 🙂

The 4 main properties of a Blender Panel class are:

  • bl_idname: the unique ID of the Panel – it has to follow a specific syntax convention that starts with some uppercased info about the type of the class (here: a Panel, PT) and its position in the layout (in my case, it’s shown in the 3D view, VIEW_3D)
  • bl_label: this is the actual display name of your panel in the UI, the pretty label that you want to show to the user
  • bl_space_type: this defines what part of the layout your panel can appear in: the 3D view, the image editor, the curve editor, the properties panel, etc.; you can check out the list of valid values over here!
  • bl_region_type: and this sets the exact part of the UI the panel will appear in (the topbar, the sidebar…); here is the list of all valid values!

All of these settings are defined as class variables, in the body of the class but outside of any function – for example, for our ExamplePanel class, it gives us the following:

Then, we have to define the draw() function of our panel: this is where we actually tell Blender what to show in this panel. This function receives two input parameters: the instance itself (via the usual Python self variable) and the current Blender context – this variable has a reference to the current scene and lots of util info on the session you’re working in.

Everything is done starting from the self.layout object: this is a UILayout object that can then create all the UI stuff you need: columns, rows, labels, input fields, checkboxes…

For now, let’s just make a basic label with the label() method:

Let’s finally register our class! We can do it in the main part of our script, with the common __main__ check (that basically checks that this Python script is run as the primary script and not a dependency):

Now, if you run this script (with the Run button or the <Alt + P> shortcut), you’ll see that you have a new tab in the side-panel of your 3D view, “Misc” (because we didn’t assign any category to our panel) – and if you click on it, you’ll get our brand new panel with its pretty title and its basic “Hello world” label!

Handling registration and unregistration better

At the moment, we’re calling the register_class function directly in our main routine. While this is ok for a basic script like this one, it can quickly be tiresome to remember all the custom classes we made and want to register, plus properly unregister them at the end.

To do this, we need to transform our simple Python script into a Blender addon.

To transform a Python script into an addon, you just need to add a little JSON data dict at the very top of your script and define the register() and unregister() functions.

The JSON data is what’s called “metadata” for Blender to read; this will allow the soft to integrate your script to the addons panel. In this dict, you put the following info (only the first keys are required, the rest is just good practice, and there are even more available keys):

Note that the Blender version you give in this metadata is the minimum required version; so 2.93 is probably a bit “overkill” in my case, but anyway… 😉

The register()/unregister() are called when you enable or disable the addon in your preferences, and when you start or quit Blender. In our case, this is where we want to register and unregister our Panel class.

A common pattern is to make a list of all the classes you care about so that these two methods can handle cycling through this list and storing or cleaning up the right info easily:

Note: I can’t use the variable name class in my loop because it is reserved Python-keyword, that’s why I use klass with a “k” 😉

Then, save your Python file, go to your Blender preferences panel, in the Addons section, and import your script (click on “Install” and find your Python file on your computer):

Once it’s imported, don’t forget to actually enable it! You also see that the addon metadata I specified in my dict are shown here.

If you’ve kept my little debug print, you should see “registered” printed in your console when you enable the addon. If you toggle it off again, then you’ll see “unregistered” printed – this means our Panel has been “forgotten” by Blender and it is not in the layout anymore.

You can actually check this out in your 3D view too: if you enable the addon, you’ll get the “Misc” tab with our example panel but if you disable it, the tab and the panel will be gone 😉

A more advanced example: making an object renamer

Ok – we have an idea of how to create, register and unregister simple panels with some custom UI for our Blender, and all of this can be neatly wrapped inside an addon!

Now, it’s time to step it up and start actually tweaking properties and calling functions 🙂

If you want to get the code directly, it’s available as a Github gist 🚀

An overview of our addon

What we’ll create here is a very basic addon that takes all the objects currently selected and renames them according to a pre-defined format. More precisely, we’ll want to add a prefix, a suffix and optionally a number version.

So, for example, an object initially named “Cube” might get renamed to something like: “MyPrefix_Cube_aGreatSuffix-v10”. But, of course, if this object has already been renamed and we re-apply the operation, we don’t want to wrap all of this in a new level of prefixes and suffixes. So our function will actually check with a regular expression (or regex) whether the object’s name currently matches the format, in which case it will only extract the middle part and replace the rest. And we would get something like: “MyNewPrefix_Cube_anotherSuffix-v11”.

We won’t have any clever logic for the version number, it will be an input that the user enters by hand, just like the prefix and the suffix. But we’ll make it optional by also having a boolean flag enable or disable the addition of the version number at the end of the names.

Note: if you’re not too familiar with regex, you can check out an article I wrote a while ago about how they work and how you can use them in C# 🙂

Preparing our renaming function

Before we dive into actual Blender scripting, let’s prepare this renaming logic in a rename_object() function. This method will take in a Blender object and modify its name using the aforementioned rules:

But the question is: how do we get these prefix, suffix, version and add_version variables? How do the users set those in our UI?

Creating properties

To make the variables “global” to the scene and accessible from everywhere in our panel and operators, we need to create scene properties.

Those are created using the bpy.props module – you can then choose between various functions depending on the property type you want (string, float, int, boolean…):

For our addon, I’ve decided to automate the listing of our properties with some tuples to make it easier to configure them:

Then, just like the classes, we have to register and unregister those props in our scene. This time, it is done by defining or deleting an attribute on Blender’s bpy.types.Scene object:

Displaying our properties

Now that we have defined our properties, let’s actually display them in our UI!

We’ll make a Panel-derived class called ObjectRenamerPanel, set its properties as before and then make a draw() function that iterates through the properties and shows them in a column:

You’ll see that Blender automatically chooses the right type of input for our variables according to their variable type (a checkbox for booleans, an input field for texts, a number input for ints…). This makes it really quick to make intuitive user-friendly interfaces, because your fields will have the appearance that people expect for this kind of variable! 🙂

If we re-import the addon in Blender, we’ll get our updated UI:

The little bonus is that we can actually make a sub-layout for the version property so that its input is conditionally enabled depending on the current value of the add_version property. This value is available in our context.scene:

Creating an operator and linking it to a button

The final part is to actually have a button to trigger our renaming function and run the logic. In Blender, you can’t call a function directly from a Panel – you first need to create an Operator.

Operators are derived from the bpy.types.Operator type and they must define an execute() function (that’s what’s called when you click the associated button). This function receives both the instance (via self) and the context, and it has to return a set containing the result state of the function (a value amongst FINISHED, RUNNING_MODAL, CANCELLED, PASS_THROUGH or INTERFACE).

Let’s create an ObjectRenamerOperator class. Its bl_idname is the unique ID that we’ll have to reference later on in our UI button. Then, our execute() function will simply get the values of our 4 properties from the context.scene and use them in our rename_object() function:

Finally, we just have to add a line in the draw() function of our ObjectRenamerPanel to add a button that calls this operator:

And add the ObjectRenamerOperator to our CLASSES list:

If we re-import our addon again, we’ll get a fully functional object renamer 🙂

Conclusion

Blender is a really amazing CG tool that is improving with each version. Over the years, it’s accumulated tools for the entire classical 3D chain (modelling, UV editing, shading, lighting, compositing…) – but also, like other 3D softs, some features like Python scripting that give a great entry point to Blender for the dev community as well!

Being able to create your own tools and UI panels is nice because it allows you to customise your sessions, your workspace and even your work process, and to make tailor-made solutions for your team or yourself.

I hope you’ve enjoyed this new quick peek at Blender’s Python API – and of course, feel free to comment and tell me if you have ideas of other nice tools we could create 🙂

3 thoughts on “Creating a custom panel with Blender’s Python API”

  1. Thanks Mina!

    Is there any way to create a custom timeline panel as well? While using Grease pencil I need to get rid of the navigation sliders (located on bottom and the right side) because I don’t need them as I navigate with shortcuts..Also I wish there is a way to import sound clips and camera keys into this grease pencil dope sheet panel?

    1. Hello – happy this article was useful! 🙂

      Mmh, to my knowledge the technique I showed here is to create panels, not windows. I’m not yet an expert with Blender tooling, so sadly I can’t say if there’s an easy way of creating custom windows :/
      Looking at Blender’s source code, and more precisely the editors, it looks like their editors (so the actual big windows in your layout like the Dope Sheet or the 3D View) are coded in C, not Python. So I’m guessing the addon system doesn’t go to that level. But once again, I might be missing something!

      For importing/playing audio or video clips, I’ve personally used the “Video Sequencer” editor. But again it doesn’t “blend” with the Dope Sheet, those are two separate editors.

      I hope it will be of some help,
      sorry I couldn’t really give you better advice!

      Cheers 🙂

Leave a Reply to mpecheux Cancel reply

Your email address will not be published. Required fields are marked *