Page MenuHome

Python API for Tools
Open, NormalPublic

Description

Goals of this task:

  1. Create an API to create new tools.
  2. Create an API to register tools in the toolbar.

Create new Tool

Every tool is a namedtuple called ToolDef currently. That is a nice internal format but does not work well in an API due to the amount of attributes.
There is a wrapper called ToolDef.from_fn that makes it easier to create new tools. This function is used can be used as a decorator. Every decorated function then turns into a tool definition.

The advantage of that approach is that it makes tool definitions very compact. However, personally I think that this does not work well in an API because "why should I define a function to create a tool".
A better approach could be to use concepts that are used in other parts of the API already: classes.

Unfortunately the concept of a class does not fit exactly, because there aren't really instances of a tool. The tool would be the class itself.
A combination of the current tool syntax and classes could fit best. Below is an example of how that API might look like.

from bpy. tools import ToolDefinition

@ToolDefinition
class CursorTool:
    label = "Cursor"
    description = "Set the cursor location"
    icon = "ops.generic.cursor"
    keymap = ... # to be discussed

    def draw_settings(self, context, layout):
        props = self.operator_properties("view3d.cursor3d")        
        layout.prop(props, "use_depth")
        layout.prop(props, "orientation")

This approach also allows individual attributes to be normal values or functions (e.g. there can be a function to generate the description dynamically).
We could also change it so that CursorTool has to inherit ToolDefinition. However I prefer to use a decorator here because of the following reasons.

  • It differentiates it from Panels, Menus, etc. which work quite differently.
  • Allows us to run code when the new tool is created. This code can check if everything is setup correctly and can add missing attributes. Although this can probably be done with __init_subclass__ as well, haven't tried it yet.
  • A decorator can transform the class completely, potentially even convert it into a namedtuple if necessary.

Since tools are (currently?) very lightweight, tools don't have to be registered. The "registration" happens when it is added to the toolbar.

Add Tool to Toolbar

I use the following terminology, not sure if other terminology is established already.

  • Toolbar: The toolbar (I know, recursive definition) within one specific mode. E.g. the toolbar in curve edit and mesh edit mode are completely separate toolbars.
  • Toolbar Entry: A single icon in the toolbar. A toolbar entry can contain an arbitrary amound of tools. If it has 0, it will be hidden. If it has more than 1, a submenu will be displayed.
  • Entry Block: Can contain an arbitrary amount toolbar entries. A toolbar can have multiple entry blocks. Entry blocks are separated in the UI.

For users to be able to use a tool, it has to be in a toolbar. The goal is to allow addon developers to do the following.

  • Add a new entry block to a toolbar.
  • Add a toolbar entry to an existing or new entry block.
  • Add a tool to an existing or new toolbar entry.

We do not support creating a new toolbar. Only existing toolbars can be extended (for now).

In the current design it is difficult to allow all these operations because toolbars are mainly tuples, which means they are immutable. Changing this is not too difficult but then we face the bigger problem: How can we identify toolbars, entry blocks and entries? Also, should the identifier be just a string or some python object? I guess we will use strings, because they are used in many other areas in Blender already.

  • Toolbar: This is mostly a solved problem because there is already the tool mode. The main question is whether the space type should become part of the identifier. E.g. is the identifier just EDIT_ARMATURE or VIEW3D.EDIT_ARMATURE?
  • Entry Block: Currently there is no concept of an identifier for entry blocks. This would have to be added to support the second kind of operation. An example block identifier could be EDIT_MESH.ADD_MESH which contains the Add Cube and possibly other tools.
  • Entry: An entry also does not have an identifier currently. However these identifiers are easier to come up with because the scope is much smaller. In most cases the identifier could just be the name/label of the tool. A full entry identifier could look like EDIT_MESH.MANIPULATE.SPIN. We might also want to flatten the namespace out a little so that entry blocks and entries themselves are in the same namespace. Then EDIT_MESH.SPIN would be an entry while EDIT_MESH.ADD_MESH is a block. Don't know how likely name collisions are.

Once the identifier issue is solved, the API could look like so:

import bpy

bpy.tools.register(BLOCK_OR_ENTRY_IDENTIFIER, tool)
bpy.tools.unregister(BLOCK_OR_ENTRY_IDENTIFIER, tool)

The same API could be used by Blender itself to setup the default toolbars.
The order of tools within an entry and the order of entries within a block depends entirely on the registration order and cannot be changed using the API for now. This could be added later if it proofs to be useful, however it would be better to implement support for custom reordering. Panels are handled similarly; the order of panels can be changed by the user but not by an addon (afaik).

Details

Type
To Do

Event Timeline

I don't see the advantages of using a different mechanism than panels or operators, seems like another thing for Python API users to learn without a clear benefit for them.

Actually, I don't know that either. I was under the impression that this won't change for now due to the following statement from Campbell in an email:

Currently tools are just a tuple (immutable Python types),
we could pass in a class that gets converted into data, but I'm wary
of faking this behavior,
mainly because none of the other characteristics of RNA sub-classing
will follow.

Suggest to first make basic working register/unregister that handles
submenu (append/prepend/remove from sub-tools, creating top-level
tools with sub-tools, everything we do currently).

After that, what to pass in can be evaluated separately, since it
doesn't change the Python data-manipulation side of things.

I'm not too aware of how the internals of tools currently work, I mainly checked the existing Python code today and yesterday.

Another issue is that tools currently can't define their own properties. Again from Campbell:

Tool settings are typically operator properties, or in one case, gizmo
properties (extrude axis type for eg).

Even though the operator/gizmo definitions are used, they are stored
in the tool.

I don't see the advantages of using a different mechanism than panels or operators, seems like another thing for Python API users to learn without a clear benefit for them.

Currently tools are not registered in C, so its not a given that we should make registration work like panels/operators/menus etc. internally.
If we do this it has other implications, besides registration.

If we do class registration, it could follow conventions from bpy.types.* registration.

My concern is mainly with consistency for Python API users, regardless if internally it's implemented in C or Python.

It's not clear why this needs to use a decorator instead of a subclass, and label instead of bl_label.

Aaron Carlisle (Blendify) triaged this task as Normal priority.Feb 13 2019, 8:37 PM