Python API for Tools #61105

Open
opened 2019-02-01 18:18:25 +01:00 by Jacques Lucke · 10 comments
Member

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.

lang=python
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:

lang=python
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).

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. ``` lang=python 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: ``` lang=python 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).
Author
Member
Added subscribers: @JacquesLucke, @WilliamReynish, @ideasman42

Added subscriber: @brecht

Added subscriber: @brecht

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.

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.
Author
Member

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.

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.

In #61105#612049, @brecht wrote:
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.

> In #61105#612049, @brecht wrote: > 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.

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`.

Added subscriber: @Jayanam

Added subscriber: @Jayanam

Is there an official Python-API now for registering tools in Blender 2.8 / 2.9 ?

Is there an official Python-API now for registering tools in Blender 2.8 / 2.9 ?

Added subscriber: @MACHIN3

Added subscriber: @MACHIN3

Added subscriber: @ckohl_art

Added subscriber: @ckohl_art
Hans Goudey added
Type
Design
and removed
Type
To Do
labels 2024-03-03 04:24:03 +01:00
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
6 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#61105
No description provided.