4
$\begingroup$

I want to create custom Nodes for my plugin, and I've found a few places on the web that say it can't be done, and a few places I've seen people say yes, but haven't explained how. So what is the truth, is it possible or not? And if so, how easy is it to do it? Any examples?

$\endgroup$

1 Answer 1

4
$\begingroup$

Blender analyzes a node tree by starting with the output and checking what is connected to it. For each connected node, it looks up the node's type, and reads from a mapping (hard-coded in Blender source) what function is assigned to each node. Then it calls those functions, implemented in the Blender source, and use the values returned by these functions to obtain the end-result. Obviously, each of those nodes may also be connected to more nodes, and these will be called recursively.

Also, in case of a shader the "end-result" is simply a shader code, that is sent to the GPU, and the end-end-result is the actual rendered image or viewport.

So there it is, Blender source, written in C++. If you add a custom group, that's just another node implemented in C++ source, which simply recursively does the entire process, starting at the group's output.

So what are custom nodes? You can define a node, with some custom looks, and custom behaviors. The node can dance Macarena inside the node tree, it may automatically reconnect links connected to it, it can even change things outside of the node tree, for example you could have a node with a dropbox, in which you change the render engine setting.

What you can't do, is write code that is injected inside the C++ code responsible for executing the node tree. This is because the C++ source at no point has a function that reads some kind of a string input with a path to a .py file, to then execute a Python interpreter process, sending some data to it and receiving the processed data. It's just not implemented currently. So you can't, in your Python code access data passed through links, be it reading from input sockets or writing to output sockets. You can, from Python, check what is the other end of the link, and then, in case of going left, if there's a constant value (e.g. an "Integer" node), you can just read the default_value of that socket. When going right, you can "behave as" (extend a class) a standard node, and set own default_value to some value. Just keep in mind it will be a single value, so e.g. a single color for the entire render. Of course, instead, you could "Behave as" a custom group, and insert inside e.g. $32×32 = 1024$ Value nodes, and a similar number of Mix nodes, to output a different value for each pixel of a very small render. Or you could be smarter and generate, inside your code, a texture, and then just "behave" like an Image Texture, using this texture.

So there are ways, but you need to be creative. It's hard then, to say something is impossible. After all, you could dynamically modify the memory contents, to alter the machine code of Blender and inject additional code containing the machine code for your custom node, as if you have modified Blender source. But I don't think anyone has done that, and I'm certainly not up for writing a tutorial on how to do this...

One more consideration is: when does your Python code run? In case of a shader it runs either before the shader is executed on the GPU - which means you cannot possibly read the color generated by previous nodes, because they weren't yet executed - or after - in which case it's too late to alter the rendered color, unless you want to render twice. For all intents and purposes it's the same for geonodes, your Python code will be run either (typically) before the C++ execution of the modifier, or after it. So you could hack a solution, in which the geometry nodes tree is split at each Python-node, the object is then evaluated with the leftmost fragment, your Python code then reads the evaluated data, changes the node tree to the next fragment with some input added to it, evaluates again... And so on. It would be tricky to obscure this process from the user as something that happens just behind the scenes, but not impossible.

Disclaimer: I'm not a Blender source contributor, I've read some of it but not much and not thoroughly and so I'd expect this answer to have some factual errors. I also intentionally committed quite some simplifications and omissions: as you can see the answer turned out rather long anyway. However, I think I fairly explained how this all works in principle.

Here's an example of a workaround:

Creating custom node types for geometry nodes

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .