Python API: add remove_link callback for custom node editors #105676

Open
Juanfran Matheu wants to merge 2 commits from jfmatheu/blender:node-remove-link-callback into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
First-time contributor
The issue

Currently, you can get notified at node level when a link is created (at Node level) BUT there is no way to know when a link has been removed in the same explicit way as the Node.insert_link method (API Ref.) since the remove link event is not covered.

This fact causes a lot of troubles to addon developers, finding no valid workaround at all and making really hard to perform a partial update/evaluation of the NodeTree to explicitly update only the affected Node, Socket or branch as we can with the Node.insert_link method from our custom Node types.

The patch

This patch makes it possible to get notified when a NodeLink is going to be removed from a Node, this new Node method is called remove_link to follow the same naming as the insert_link method.

The specification for this new Node method is the same as the Node.insert_link one, optionally defined inside the custom Node types. Addon devs will have access to the NodeLink related to that event, which in the case of Node.insert_link is the NodeLink that is going to be created and in the case of this new Node.remove_link method is the opposite, that is, the NodeLink that is going to be removed. In that context, addon devs can perform actions over the affected NodeSocket, Node or entire branch that are related to that NodeLink, giving the flexibility to perform any partial update in the NodeTree when that event happen.

Most important, and thinking in a bigger scope, imagine an addon developer want to develop a full partial update implementation over its custom Node Editor addon. We have resources to do most part of it, but not everything is covered in the API to do this. Let's see why.

Events that API currently cover that are useful to perform partial updates at Node level:

All these functions are helpful for this idea of performing a partial update of the NodeTree.

BUT, not all cases are covered, lacks the link removal case, which is why this patch is extremely useful.

By exposing this 'remove_link' - kind of a link removal event trigger - to the API we cover all the needed events to be able to have a full partial updates implementation to explicitly evaluate the affected Node, Socket or branch, whatever the event is, which make possible to have an efficient way to evaluate custom node tree, not by this patch itself but a combination of this patch addition with the current available functions, which otherwise would require to do full node-tree updates and/or hacky dirty worarounds, which impacts negatively the performance, interactivity and UX.

Video

2023-03-12 00-03-47.mp4

##### The issue Currently, you can get notified at node level when a link is created (at Node level) BUT there is no way to know when a link has been removed in the same explicit way as the `Node.insert_link` method ([API Ref.](https://docs.blender.org/api/latest/bpy.types.Node.html#bpy.types.Node.insert_link)) since the remove link event is not covered. This fact causes a lot of troubles to addon developers, finding no valid workaround at all and making really hard to perform a partial update/evaluation of the NodeTree to explicitly update only the affected Node, Socket or branch as we can with the `Node.insert_link` method from our custom Node types. ##### The patch This patch makes it possible to get notified when a NodeLink is going to be removed from a Node, this new Node method is called `remove_link` to follow the same naming as the `insert_link` method. The specification for this new Node method is the same as the `Node.insert_link` one, optionally defined inside the custom Node types. Addon devs will have access to the NodeLink related to that event, which in the case of `Node.insert_link` is the NodeLink that is going to be created and in the case of this new `Node.remove_link` method is the opposite, that is, the NodeLink that is going to be removed. In that context, addon devs can perform actions over the affected NodeSocket, Node or entire branch that are related to that NodeLink, giving the flexibility to perform any partial update in the NodeTree when that event happen. **Most important, and thinking in a bigger scope, imagine an addon developer want to develop a full partial update implementation over its custom Node Editor addon. We have resources to do most part of it, but not everything is covered in the API to do this. Let's see why.** Events that API currently cover that are useful to perform partial updates at Node level: - `update` function in Node properties. [API Ref.](https://docs.blender.org/api/latest/bpy.props.html#update-example) - `update` function in NodeSocket property. [API Ref.](https://docs.blender.org/api/latest/bpy.props.html#update-example) - `free` Node class method. [API Ref.](https://docs.blender.org/api/latest/bpy.types.Node.html#bpy.types.Node.free) - `init` Node class method. [API Ref.](https://docs.blender.org/api/latest/bpy.types.Node.html#bpy.types.Node.init) - `copy` Node class method. [API Ref.](https://docs.blender.org/api/latest/bpy.types.Node.html#bpy.types.Node.copy) - `insert_link` Node class method. [API Ref.](https://docs.blender.org/api/latest/bpy.types.Node.html#bpy.types.Node.insert_link) All these functions are helpful for this idea of performing a partial update of the NodeTree. BUT, not all cases are covered, lacks the link removal case, which is why this patch is extremely useful. By exposing this 'remove_link' - kind of a link removal event trigger - to the API we cover all the needed events to be able to have a **full partial updates implementation to explicitly evaluate the affected Node, Socket or branch, whatever the event is, which make possible to have an efficient way to evaluate custom node tree**, not by this patch itself but a combination of this patch addition with the current available functions, which otherwise would require to do full node-tree updates and/or hacky dirty worarounds, which impacts negatively the performance, interactivity and UX. ##### Video [2023-03-12 00-03-47.mp4](/attachments/425c4184-17b9-4ac8-961e-946ca876df5d)
Juanfran Matheu changed title from [PythonAPI] Add remove link callback for custom node editors. to PythonAPI: Add remove link callback for custom node editors. 2023-03-12 00:36:30 +01:00
Juanfran Matheu changed title from PythonAPI: Add remove link callback for custom node editors. to PythonAPI: add remove_link callback for custom node editors. 2023-03-12 00:37:14 +01:00
Iliya Katushenock added this to the Nodes & Physics project 2023-03-12 08:46:10 +01:00

Hello. I'm not sure why this is even necessary?
Usually there shouldn't be such a callback (?), since this kind of processing needs to be specialized for the operator itself that removes the link.
Inserting a link can be used for pre-existing functionality. But I'm not sure if anyone wants sockets, for example, to disappear when the link is disconnected.

Hello. I'm not sure why this is even necessary? Usually there shouldn't be such a callback (?), since this kind of processing needs to be specialized for the operator itself that removes the link. Inserting a link can be used for pre-existing functionality. But I'm not sure if anyone wants sockets, for example, to disappear when the link is disconnected.
Author
First-time contributor

Hello. I'm not sure why this is even necessary?

Don't worry, I'm here to clear up any confusion.

Inserting a link can be used for pre-existing functionality.

We have an init and copy callback, so by using your statement, we don't need a free callback for when Node is removed, because init and copy can be used for whatever the pre-existing functionality means in your POV?

But I'm not sure if anyone wants sockets, for example, to disappear when the link is disconnected.

Yes, that is an example, for dynamic sockets can be useful. But in general it is useful to perform partial updates over the affected Node or InputSocket or an entire branch, avoiding any full update.

Think that Python is not C/C++, the alternatives to not catching this event are:

  • Storing and comparing memory addresses or hashes of every link each time the node tree is updated, no matter the event that triggered that, and then make a FULL NodeTree update instead of a partial one since you don't have access to the removed link which is already removed, except if you waste way more memory and processing to store also sockets and nodes info per every small change in the tree, which may lag the end-user interactivity while doing common actions.

  • Other custom node editors: directly do a full NodeTree evaluation no matter what the update was (no way to know it lol). So far we can optimize and do a partial update when modifying a Node property or insert a link, but as we can't listen to remove link events it doesn't matter because we can't get all needed events in a explicit way.

As I hope you can see, no optimizations in Python Node API are possible, and this ends up sticking to bad practises that affects performance and Node API usability, and a big part of this Node API problem is not having access to this kind of callback, which are really convenient for addon development.

> Hello. I'm not sure why this is even necessary? Don't worry, I'm here to clear up any confusion. > Inserting a link can be used for pre-existing functionality. We have an init and copy callback, so by using your statement, we don't need a free callback for when Node is removed, because init and copy can be used for whatever the *pre-existing functionality* means in your POV? > But I'm not sure if anyone wants sockets, for example, to disappear when the link is disconnected. Yes, that is an example, for dynamic sockets can be useful. But in general it is useful to perform partial updates over the affected Node or InputSocket or an entire branch, avoiding any full update. Think that Python is not C/C++, the alternatives to not catching this event are: - Storing and comparing memory addresses or hashes of every link each time the node tree is updated, no matter the event that triggered that, and then make a FULL NodeTree update instead of a partial one since you don't have access to the removed link which is already removed, except if you waste way more memory and processing to store also sockets and nodes info per every small change in the tree, which may lag the end-user interactivity while doing common actions. - Other custom node editors: directly do a full NodeTree evaluation no matter what the update was (no way to know it lol). So far we can optimize and do a partial update when modifying a Node property or insert a link, but as we can't listen to remove link events it doesn't matter because we can't get all needed events in a explicit way. As I hope you can see, no optimizations in Python Node API are possible, and this ends up sticking to bad practises that affects performance and Node API usability, and a big part of this Node API problem is not having access to this kind of callback, which are really convenient for addon development.

Copying or freeing makes sense precisely in the sense that memory management would be implemented in C/C++.
On the other hand, if you want to paste the node somewhere when copying, or relinking when freeing, this should not be combined into a single free call. These are different operators called by the delete operator.
And it's still not entirely clear what the use cases might be.

Copying or freeing makes sense precisely in the sense that memory management would be implemented in C/C++. On the other hand, if you want to paste the node somewhere when copying, or relinking when freeing, this should not be combined into a single free call. These are different operators called by the delete operator. And it's still not entirely clear what the use cases might be.
Author
First-time contributor

Copying or freeing makes sense precisely in the sense that memory management would be implemented in C/C++.

What. I'm not telling to expose remove link function to the API for any kind of memory management from Python, that would still happen in C/C++ 😅, just like insert_link works or init or copy callbacks, etc... it's just a trigger to notify such event, not black magic there at all. Think of those callbacks as a way to subscribe or listen to an event that occurs in the NodeTree and it's managed by C/C++, in a way that the event can be exposed to custom node editors, this remove link is just another subscription for a not covered event.

On the other hand, if you want to paste the node somewhere when copying, or relinking when freeing, this should not be combined into a single free call. These are different operators called by the delete operator.

Not sure why mixing node operators or clipboard here, I don't see any relation with the topic really. So far, about the combined calls, the trigger doesn't combine all removed links into a single call, they are individual calls.

And it's still not entirely clear what the use cases might be.

Have you ever built a custom node editor? And tried to have an optimized tree evaluation with a full partial updates implementation? How would you do it?

You may need to know that from Python you need to build all the tree evaluation logic.

From that context now think that you are performing full updates over any micro-change that happen in the tree instead of partial updates, which is not efficient at all, specially when we are talking that all the logic and node evaluation is in Python and not C/C++. Now you can see the big issue here about speed, performance, interactivity and UX.

Try to no stick nor focus in the simple or vague idea of the individual feature because by itself alone it may say nothing - as it seems to you - but think in the big scope that together with this new API exposed callback makes a more consistent node API that allow to have a full NodeTree partial updates implementation as a real alternative without hacks nor inefficient workarounds. Look beyond the bubble.

I can't explain it better, but feel free to ask to other addon devs that actively develop custom node editors in the bpy Discord.

> Copying or freeing makes sense precisely in the sense that memory management would be implemented in C/C++. What. I'm not telling to expose remove link function to the API for any kind of memory management from Python, that would still happen in C/C++ 😅, just like insert_link works or init or copy callbacks, etc... it's just a trigger to notify such event, not black magic there at all. Think of those callbacks as a way to subscribe or listen to an event that occurs in the NodeTree and it's managed by C/C++, in a way that the event can be exposed to custom node editors, this remove link is just another subscription for a not covered event. > On the other hand, if you want to paste the node somewhere when copying, or relinking when freeing, this should not be combined into a single free call. These are different operators called by the delete operator. Not sure why mixing node operators or clipboard here, I don't see any relation with the topic really. So far, about the combined calls, the trigger doesn't combine all removed links into a single call, they are individual calls. > And it's still not entirely clear what the use cases might be. Have you ever built a custom node editor? And tried to have an optimized tree evaluation with a full partial updates implementation? How would you do it? You may need to know that from Python you need to build all the tree evaluation logic. From that context now think that you are performing full updates over any micro-change that happen in the tree instead of partial updates, which is not efficient at all, specially when we are talking that all the logic and node evaluation is in Python and not C/C++. Now you can see the big issue here about speed, performance, interactivity and UX. Try to no stick nor focus in the simple or vague idea of the individual feature because by itself alone it may say nothing - as it seems to you - but think in the big scope that together with this new API exposed callback makes a more consistent node API that allow to have a full NodeTree partial updates implementation as a real alternative without hacks nor inefficient workarounds. Look beyond the bubble. I can't explain it better, but feel free to ask to other addon devs that actively develop custom node editors in the bpy Discord.
Author
First-time contributor

Also I think this should be added to the Python API project board since it's an improvement of the API by exposing a new thing to the API, nothing to do with C/C++ nodes implementation lol

Also I think this should be added to the Python API project board since it's an improvement of the API by exposing a new thing to the API, nothing to do with C/C++ nodes implementation lol

There is still no example of a problem that can be solved knowing about deleting a link.
As I've seen from my limited experience with python, blender sticks to the idea that you need to make a new modal drag-and-drop operator with delete to be able to overload it with updates.
Storing handlers in nodes is just a way to designate a storage location. But not the duty of the node itself. These updates are still called by operators, just from C++ as part of built-in nodes.

There is still no example of a problem that can be solved knowing about deleting a link. As I've seen from my limited experience with python, blender sticks to the idea that you need to make a new modal drag-and-drop operator with delete to be able to overload it with updates. Storing handlers in nodes is just a way to designate a storage location. But not the duty of the node itself. These updates are still called by operators, just from C++ as part of built-in nodes.
Iliya Katushenock added the
Interest
Python API
label 2023-03-12 12:40:51 +01:00
Author
First-time contributor

There is still no example of a problem that can be solved knowing about deleting a link.

A code example would be a full implementation of a custom node tree including all the logic for the topology evaluation and partial updates involving (mainly) Node property updates, insert_link, remove_link and copy. As I tried to say, the functionality by itself adds nothing more than the insert_link or the copy callbacks. You need a fully working node tree with partial updates in mind to understand why exposing this to the API is quite important for an efficient evaluation, that is, thinking outside the box. This in fact would require a BIG code example and a good understanding on the Python API since it would not be just a code fragment but a full complex project. Unfortunately I don't have such a simple example since I work over the node API with an abstraction layer I made that wraps the entire Blender API which would require you to learn and understand, so it's not just as simple as to say "There is still no example" if you don't quite understand the problem and the full scope with words, even less with a complex project, so I think it doesn't require such big example if you have a good understanding of the Python node API and can in fact see the benefits of the partial updates for an explicit node evaluation over a full node-tree evaluation per any specific change in the tree, which is the main issue this patch solves (not by itself, but when you combine it with the current exposed callbacks), not so hard to understand imho.

> There is still no example of a problem that can be solved knowing about deleting a link. A code example would be a full implementation of a custom node tree including all the logic for the topology evaluation and partial updates involving (mainly) Node property updates, insert_link, remove_link and copy. As I tried to say, the functionality by itself adds nothing more than the insert_link or the copy callbacks. You need a fully working node tree with partial updates in mind to understand why exposing this to the API is quite important for an efficient evaluation, that is, thinking outside the box. This in fact would require a BIG code example and a good understanding on the Python API since it would not be just a code fragment but a full complex project. Unfortunately I don't have such a simple example since I work over the node API with an abstraction layer I made that wraps the entire Blender API which would require you to learn and understand, so it's not just as simple as to say "There is still no example" if you don't quite understand the problem and the full scope with words, even less with a complex project, so I think it doesn't require such big example if you have a good **understanding of the Python node API and can in fact see the benefits of the partial updates for an explicit node evaluation over a full node-tree evaluation per any specific change in the tree, which is the main issue this patch solves (not by itself, but when you combine it with the current exposed callbacks), not so hard to understand imho.**

Right now, all blender nodes are fully updateded each time. Although I know that it would be better not to do this and the geometry nodes going to do this.
Are you sure the link reconnect update will be enough? Couldn't there be other updates? And if this update is not used in built-in nodes, only in custom ones?
Wouldn't it be easier to just build your infrastructure out of nodes, operators, data, and updates?

I just don’t see why the built-in nodes need it yet. Update nodes (NodeTreeMainUpdater) now uses flags. That is, again, the nodes themselves simply store data that someone else needs. More specifically, who wants to get an up-to-date view of the tree of nodes.

Right now, all blender nodes are fully updateded each time. Although I know that it would be better not to do this and the geometry nodes going to do this. Are you sure the link reconnect update will be enough? Couldn't there be other updates? And if this update is not used in built-in nodes, only in custom ones? Wouldn't it be easier to just build your infrastructure out of nodes, operators, data, and updates? I just don’t see why the built-in nodes need it yet. Update nodes (`NodeTreeMainUpdater`) now uses flags. That is, again, the nodes themselves simply store data that someone else needs. More specifically, who wants to get an up-to-date view of the tree of nodes.
Author
First-time contributor

Implementation is the same way as the rest of Node RNA kind callbacks per node type which imo makes it consistent, tho still would need a PythonAPI module member (eg. Sybren, Jacques Lucke, Campbell... https://projects.blender.org/blender/blender/wiki/Module:-Python-API-&-Text-Editor) to review this to wheter decide if the RNA expose implementation is really consistent with other callbacks as insert_link, init, copy, free, draw_buttons, draw_label, etc...
Even you could re-use the insert_link for this purpose by adding a boolean argument to indicate that it's remove instead (similar to the modal exit callback existing in gizmos which has a cancel boolean argument) but I think in this case it's better to be more explicit, create a different callback/ event trigger and avoid confusions and breaking existing functionalities for addons.

Implementation is the same way as the rest of Node RNA kind callbacks per node type which imo makes it consistent, tho still would need a PythonAPI module member (eg. Sybren, Jacques Lucke, Campbell... https://projects.blender.org/blender/blender/wiki/Module:-Python-API-&-Text-Editor) to review this to wheter decide if the RNA expose implementation is really consistent with other callbacks as insert_link, init, copy, free, draw_buttons, draw_label, etc... Even you could re-use the insert_link for this purpose by adding a boolean argument to indicate that it's remove instead (similar to the modal exit callback existing in gizmos which has a cancel boolean argument) but I think in this case it's better to be more explicit, create a different callback/ event trigger and avoid confusions and breaking existing functionalities for addons.
First-time contributor

As the developer of NodeScapes, a fully custom node tree editor, I would love this feature! Right now I do the exact thing mentioned. I keep track of all links and their nodes and compare the before and after each time there is an update to determine what actually happened. Making this more clear would be awesome.

As the developer of NodeScapes, a fully custom node tree editor, I would love this feature! Right now I do the exact thing mentioned. I keep track of all links and their nodes and compare the before and after each time there is an update to determine what actually happened. Making this more clear would be awesome.
Hans Goudey changed title from PythonAPI: add remove_link callback for custom node editors. to Python API: add remove_link callback for custom node editors.= 2023-03-21 15:52:22 +01:00
Hans Goudey changed title from Python API: add remove_link callback for custom node editors.= to Python API: add remove_link callback for custom node editors 2023-03-21 15:52:38 +01:00
Juanfran Matheu force-pushed node-remove-link-callback from 27f58e7e5c to 23d9749118 2023-03-22 13:43:19 +01:00 Compare
Juanfran Matheu added 1 commit 2023-04-07 17:58:49 +02:00
This pull request has changes conflicting with the target branch.
  • source/blender/blenkernel/BKE_node.h
  • source/blender/makesrna/intern/rna_nodetree.c

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u node-remove-link-callback:jfmatheu-node-remove-link-callback
git checkout jfmatheu-node-remove-link-callback
Sign in to join this conversation.
No reviewers
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 Assignees
3 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#105676
No description provided.