I noticed bNodeType
has a declaration_is_dynamic
field that make the declare
callback be called dynamically upon node instance creation and copy. But I could not find any existing node that is using it, so I tried to improvise.
My first issue was that the declare
callback only takes a NodeDeclarationBuilder
, so it has no way to behave differently during different calls. I thus added a node
field to the NodeDeclarationBuilder
class in NOD_node_declaration.hh
:
// 1. We add a new field holding a pointer to the node for which we are building the declaration.
const bNode *node_; /* Only available in dynamic declarations */
// 2. Add an extra argument to the constructor.
// Node that the node is null by default, namely when the declaration is not dynamic.
NodeDeclarationBuilder(NodeDeclaration &declaration, const bNode *node = nullptr);
// [...]
inline NodeDeclarationBuilder::NodeDeclarationBuilder(NodeDeclaration &declaration,
const bNode *node)
: declaration_(declaration), node_(node)
{
}
// 3. We add an accessor for this node
const bNode *node() const;
// [...]
inline const bNode *NodeDeclarationBuilder::node() const
{
return this->node_;
}
When using the builder in nodeDeclarationEnsureOnOutdatedNode
(node.cc
), we provide the current node to the constructor arguments:
blender::nodes::NodeDeclarationBuilder builder{*node->declaration, node};
Once we have this, we can use it in the declare
callback:
static void node_declare(NodeDeclarationBuilder &b)
{
if (b.node() == nullptr || b.node()->storage == nullptr)
return;
const NodeGeometryPizza &storage = node_storage(b.node());
// Now use storage to dynamically call b.add_input and b.add_output
// [...]
}
This was not enough to have the input/output effectively dynamically redefined, we also have to trigger it in the update
callback by calling this function:
static void force_redeclare(bNodeTree *ntree, bNode *node)
{
BLI_assert(node->typeinfo->declaration_is_dynamic);
if (node->declaration != nullptr)
{
delete node->declaration;
node->declaration = nullptr;
}
node_verify_sockets(ntree, node, true);
}
The naive solution consists in calling it all the time:
static void node_update(bNodeTree *ntree, bNode *node)
{
force_redeclare(ntree, node);
// [...]
}
This works! Even when saving/reloading a file etc. What I am wondering is:
- Is this idiomatic enough?
- Is there a document listing the intended plans for dynamic node declaration? It seems to be an ongoing work so I would understand it is still mostly in chat logs.
- I’d like to store runtime data to check whether to force redeclare the node, what would be the recommended way? A
runtime
field in meNodeGeometryPizza
DNA struct?
PS: A bit of context: I am doing this in the course of creating a node that loads an OpenMfx
plugin at runtime. As one can see in this guide the logic is close enough, there is a Describe
action that corresponds to Blender’s declare
and a Cook
that is geometry_node_execute
. I need the node to be redeclared whenever the path of the plugin changes.