Determine When My WorkSpaceTool Is Activated/Deactivated?

Is there a reliable way to determine when my Edit mode WorkSpaceTool is activated or deactivated that allows you to run operators?

I’m developing an addon for Blender that has its own tool that you switch to in Edit mode. I’d like to do some setup and teardown when the tool is activated or deactivated. I haven’t found any documentation on any handlers or methods on the WorkSpaceTool that let you do this. There are two workarounds I’ve found, one being using a timer and the second one is using a GizmoGroup(which doesn’t allow you to use operators).

The problem with timers is that unless you run it very quickly, a user could make a choice that gets overridden by the tool activation getting detected by the timer shortly after that choice. The user could also run a modal operator and put things in a very strange state if the timer triggers during that. I don’t know what the ramifications of running a faster timer are but I imagine you want to keep what it does to as fast as possible which isn’t ideal here.

The issue with using a GizmoGroup’s setup() and del() is that it is turned on and off in a number of places including running operators, using Ctrl-Z to undo and moving windows. These would cause flickering from my addon as it tries to be polite and set and restore state when it’s activated/deactivated. Also, because this appears to run in a draw/rendering only state or thread, you can’t run operators which I need to do to change the Select mode and to change tools(want to abort my tool with an error if the user goes into multi-edit which can’t be supported properly).

I did find an archived differential D10635 that looks absolutely perfect for what I need. It’s only 16 lines of code, maybe it could be resurrected and merged? @mauge? I didn’t see it in the pull requests and I could probably do it if you didn’t want to.

I can take it again and do a pull request.

But I worked on another patch that was working in a more general way , allows to just execute your code when editmode_toggle operator (or any other operator) is executed. Timers should be avoided, because may affect performance specially if lots of addons are abusing on it.

I get this updated and sync on o separate project https://www.tornavis.org/#sources__patches__full-list__mb-0011-operator-handlers

sources are on

https://gitea.tmaq.es/tmaq/tornavis/src/branch/mb-0011-operator-handlers

3 Likes

Created the Pull request

3 Likes

Will this work with WorkSpaceTools though? It appears to be a thing for Operators and I don’t see any inheritance shared between Operator and WorkSpaceTool(except bpy_struct).

Do you refer the Tools on the left ? With the operator handlers proposal, you can get it. just tested

bpy.ops.wm.tool_set_by_id.handlers.invoke_pre.append(pre_invoke_callback)
# Result
[PRE] invoke {'name': 'builtin.select_box', 'cycle': True, 'as_fallback': True, 'space_type': 0}
[PRE] invoke {'name': 'builtin.rotate', 'cycle': True, 'as_fallback': True, 'space_type': 0}

Yeah, I have made my own tool for edit mode that’s on the left. You call operators from it with hotkeys but the tool itself is not an operator. I think builtin.select_box and builtin.rotate are operators but they do some stuff to turn them into fake WorkSpaceTools to appear on the left. I’m still not sure if your patch will work with the activation of a real WorkSpaceTool.

I’ll try to get Blender compiled with your patch this afternoon and test the WorkSpaceTool from my addon and see if it works. Will let you know.

1 Like

‘builtin.select_box’ and similars are not the operator. The operator is ‘tool_set_by_id’ and the string is the param. So when you click the tool, using the handler you can know the active tool, as you get the operator params, and do whatever you need.

To check if you are in edit mode, you could set the handler in ‘editmode_toggle’ operator, so you get notified when it changes.

I’ll take a look on how it works, ideally you should be able to register the tool, specifying when it should be displayed, the addon itself should not be worried about it.

I guess I’m not seeing how to apply this to my WorkSpaceTool. I need a handler that runs once when the user clicks the icon for the tool on the left or presses the hotkey for it. Then another one when they click a different tool or exit edit mode(the tool is exited automatically then and switches to an Object mode tool).

I got the code compiled and your invoke_pre handler does work for my tool, but I can’t find an equivalent for “switching away from the tool”, the invoke_post fires immediately(I’m assuming after the “invoke” is finished). I’d have to do some hacky thing where I keep track of the state of the invoke_pre for what tool is selected and what tool is being switched to(like the one in my addon). Kind of like what I’m doing now with a timer.

If I understand the code and example of D10635 correctly, this one seemed to be much more self contained to the tool system itself and would better fill a current gap in Blender.

1 Like

Thanks for your feedback!

The difference between using timers and my proposal is that code is executed when needed, so you don’t have code running on background always doing checks. So if you have lots of addons installed, and each one is doing things in background, with as little delay as possible because want not loosing the “event”, may affect blender’s performance.

The difference between post and pre, is that is executed previous or after the operator code. as example if you attach to translate you can get the original position, the finish position, or if you attach as modal, the position while user is moving.

D10635 could be equivalent as using the handler on tool_set handler, with the only improvement that you have to tool_unset callback. You should track the current tool using the handlers patch. It will not work for a custom icon added on toolbar (eg drawn there via python) if you do not make the correct calls. Anyway I’ll do the PR for it, as it’s less invasive in code and may be easier to get it applied, i did not keep updated on tornavís fork, because i’m not needing it.

I’ve looking at Blender’s code, and i think the best approach could be something that like this.

bpy.types.VIEW3D_PT_tools_active.register_tool(owner, mode, data)
bpy.types.VIEW3D_PT_tools_active.unregister_tool(owner)

The tools are already added on startup under this class, with a dict that sets tools behaviour. I can see on python console (as example)

>>> bpy.types.VIEW3D_PT_tools_active._tools['OBJECT'][1]
ToolDef(idname='builtin.cursor', label='Cursor', description='Set the cursor location, drag to transform', icon='ops.generic.cursor', cursor=None, widget_properties=None, widget=None, keymap=['3D View Tool: Cursor'], data_block=None, operator=None, draw_settings=<function _defs_view3d_generic.cursor.<locals>.draw_settings at 0x0000019ED8A7C050>, draw_cursor=None, options=None)

Oh, I 100% agree that your solution is better than a timer. However, having to keep track of the state of the current tool in a handler isn’t ideal. It leads to bugs like if you change the name of your tool or if you exit the tool within the handler. Separate handlers that only fire when the specific tool for that handler is entered or exited seems more correct to me. If I understand it correctly, your pull request is actually following the tool_set_by_id operator, and it’s just a side effect that a WorkSpaceTool is being selected with it.

Right, but for a non-modal operator like tool_set_by_id in the case of selecting my tool, there is no code execution so the post fires immediately.

I don’t think I’m entirely following you. It doesn’t seem like that dictionary has all the registered tools. In Edit mode, it only has the first 9. It doesn’t have Extrude Region, Inset Faces, … (mine) etc.

I think I understand what you need, was beating around the bush

Created the Pull request. I added example code with a custom tool, executing and operator when the tool is set.

2 Likes

Thanks! Going to build and test it after work this afternoon.

Patch doesn’t work for my addon, it throws an error when I try to use item_from_id.

Forum doesn’t like the Python stack trace, it starts throwing 403s if I paste it here so sorry this is mangled(and late because I couldn’t even post last night)

tool = bl_ui.space_toolsystem_common.item_from_id(bpy.context, 'VIEW_3D', 'my_addon.tool_edit')
...
item, _index = cls._tool_get_by_id(context, idname)
...
    mode = context.mode
AttributeError: '_RestrictContext' object has no attribute 'mode'

edit: I think this might be a build issue since I get the same exception in Blender 3.6 where I’m developing this addon. Is this supposed to be for Blender 4.1 or 4.2? After I switch to your branch, I just ran make update then make which worked fine for your last patch but maybe I’m not doing the right thing?

Don’t know where the code is executed, but i think that when is executed you cannot get bpy.context as may not be user started ?

I think the tool could be retrieved without relaying on context. Also, as same tool may appear in diferent contexts, so it depends from where users start executing your code. In case of a custom tool getting the tool just when created could be a good solution.

Ahh! I’m trying to setup and tear down the handlers in the register()/unregister() for my addon. I don’t think they’re going to accept a patch that doesn’t support that.

edit: I can confirm that it does work when I register the handlers in the Python console after the addon is loaded. I could use a timer to setup the handlers… :rofl:

I was thinking: What if you modified bpy.utils.register_tool and added parameters to allow adding handlers then? Or you could tweak the ToolDef change to have separate activated and deactivated(or whatever the Blender internal term is) methods that can be overridden like draw_cursor and draw settings.

If this isn’t something you’re interested in taking further, just let me know and I’ll see if I can figure it out. I don’t know the Blender python internals very well but looking at the register_tool code I can probably figure it out for a pull request. Thanks for what you’ve done so far, I do appreciate it!

I pushed 3 commits more over the PR.

  • You can now define the callback on the class itself.
  • The tool is returned by the register_tool function
  • You can specify the mode on item_from_id and omit the context

Best for your own tool should be the first one

4 Likes

Just chiming in to say that both the operator and worspace tool patches would be incredibly helpful for us as well. Thank you! I hope those make it through!

4 Likes

I hope b3d core devs can see the benefits of these patches.

When we talk about tools development, having a way to notify you about a tool enter and tool exit is really useful for more interactive tool applications. Currently, we have this problem in a project and the only solutions available are dirty hacks as an infinite timer or overriding Blender Python functions that are used internally when switching tools, but all these workarounds are truly unwanted, so an API solution for this is really appreciated.

The same apply when we talk about operators, it’s all about choosing between a dirty hack VS API solution, that’s why these patches are so awesome, specially for more experienced developers, since it lower the risk and makes things easier, cleaner and more versatile.

4 Likes

Maybe both features could be managed using the MessageBus. Adding new procedures, as there is nothing related to RNA here.

Could be interfaced, or maybe refactored to fit there.

I think this way would get more points in order to be accepted.