2023-08-16 00:20:26 +10:00
|
|
|
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
2023-05-31 16:19:06 +02:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
2022-08-31 12:15:57 +02:00
|
|
|
|
2023-05-15 15:14:22 +02:00
|
|
|
#include "BKE_node.hh"
|
2022-08-31 12:15:57 +02:00
|
|
|
#include "BKE_node_runtime.hh"
|
|
|
|
|
|
|
|
|
|
#include "DNA_node_types.h"
|
|
|
|
|
|
|
|
|
|
#include "BLI_function_ref.hh"
|
2025-02-11 16:59:42 +01:00
|
|
|
#include "BLI_listbase.h"
|
2022-08-31 12:15:57 +02:00
|
|
|
#include "BLI_stack.hh"
|
|
|
|
|
#include "BLI_task.hh"
|
|
|
|
|
|
2022-09-13 08:44:26 +02:00
|
|
|
#include "NOD_geometry_nodes_lazy_function.hh"
|
2024-10-11 12:20:58 +02:00
|
|
|
#include "NOD_node_declaration.hh"
|
2025-01-21 12:53:24 +01:00
|
|
|
#include "NOD_socket_usage_inference.hh"
|
2022-09-13 08:44:26 +02:00
|
|
|
|
2022-08-31 12:15:57 +02:00
|
|
|
namespace blender::bke::node_tree_runtime {
|
|
|
|
|
|
2022-09-16 16:02:08 +02:00
|
|
|
void preprocess_geometry_node_tree_for_evaluation(bNodeTree &tree_cow)
|
2022-09-13 08:44:26 +02:00
|
|
|
{
|
2022-09-16 16:02:08 +02:00
|
|
|
BLI_assert(tree_cow.type == NTREE_GEOMETRY);
|
|
|
|
|
/* Rebuild geometry nodes lazy function graph. */
|
|
|
|
|
tree_cow.runtime->geometry_nodes_lazy_function_graph_info.reset();
|
|
|
|
|
blender::nodes::ensure_geometry_nodes_lazy_function_graph(tree_cow);
|
2022-09-13 08:44:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-31 12:15:57 +02:00
|
|
|
static void update_node_vector(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
2022-12-01 14:53:27 -06:00
|
|
|
const Span<bNode *> nodes = tree_runtime.nodes_by_id;
|
2022-09-13 08:44:26 +02:00
|
|
|
tree_runtime.group_nodes.clear();
|
2022-08-31 12:15:57 +02:00
|
|
|
tree_runtime.has_undefined_nodes_or_sockets = false;
|
2022-12-01 14:53:27 -06:00
|
|
|
for (const int i : nodes.index_range()) {
|
|
|
|
|
bNode &node = *nodes[i];
|
|
|
|
|
node.runtime->index_in_tree = i;
|
|
|
|
|
node.runtime->owner_tree = const_cast<bNodeTree *>(&ntree);
|
2025-02-07 13:40:26 +01:00
|
|
|
tree_runtime.has_undefined_nodes_or_sockets |= node.is_undefined();
|
2022-12-01 14:53:27 -06:00
|
|
|
if (node.is_group()) {
|
|
|
|
|
tree_runtime.group_nodes.append(&node);
|
2022-09-13 08:44:26 +02:00
|
|
|
}
|
2022-08-31 12:15:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_link_vector(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
|
|
|
|
tree_runtime.links.clear();
|
|
|
|
|
LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
|
2022-12-30 14:35:15 +01:00
|
|
|
/* Check that the link connects nodes within this tree. */
|
|
|
|
|
BLI_assert(tree_runtime.nodes_by_id.contains(link->fromnode));
|
|
|
|
|
BLI_assert(tree_runtime.nodes_by_id.contains(link->tonode));
|
|
|
|
|
|
2022-08-31 12:15:57 +02:00
|
|
|
tree_runtime.links.append(link);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_socket_vectors_and_owner_node(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
|
|
|
|
tree_runtime.sockets.clear();
|
|
|
|
|
tree_runtime.input_sockets.clear();
|
|
|
|
|
tree_runtime.output_sockets.clear();
|
2022-12-01 14:53:27 -06:00
|
|
|
for (bNode *node : tree_runtime.nodes_by_id) {
|
2022-08-31 12:15:57 +02:00
|
|
|
bNodeRuntime &node_runtime = *node->runtime;
|
|
|
|
|
node_runtime.inputs.clear();
|
|
|
|
|
node_runtime.outputs.clear();
|
|
|
|
|
LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) {
|
|
|
|
|
socket->runtime->index_in_node = node_runtime.inputs.append_and_get_index(socket);
|
|
|
|
|
socket->runtime->index_in_all_sockets = tree_runtime.sockets.append_and_get_index(socket);
|
|
|
|
|
socket->runtime->index_in_inout_sockets = tree_runtime.input_sockets.append_and_get_index(
|
|
|
|
|
socket);
|
|
|
|
|
socket->runtime->owner_node = node;
|
2023-05-15 15:14:22 +02:00
|
|
|
tree_runtime.has_undefined_nodes_or_sockets |= socket->typeinfo ==
|
|
|
|
|
&bke::NodeSocketTypeUndefined;
|
2022-08-31 12:15:57 +02:00
|
|
|
}
|
|
|
|
|
LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) {
|
|
|
|
|
socket->runtime->index_in_node = node_runtime.outputs.append_and_get_index(socket);
|
|
|
|
|
socket->runtime->index_in_all_sockets = tree_runtime.sockets.append_and_get_index(socket);
|
|
|
|
|
socket->runtime->index_in_inout_sockets = tree_runtime.output_sockets.append_and_get_index(
|
|
|
|
|
socket);
|
|
|
|
|
socket->runtime->owner_node = node;
|
2023-05-15 15:14:22 +02:00
|
|
|
tree_runtime.has_undefined_nodes_or_sockets |= socket->typeinfo ==
|
|
|
|
|
&bke::NodeSocketTypeUndefined;
|
2022-08-31 12:15:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:37:21 +02:00
|
|
|
static void update_panels(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
|
|
|
|
for (bNode *node : tree_runtime.nodes_by_id) {
|
|
|
|
|
bNodeRuntime &node_runtime = *node->runtime;
|
|
|
|
|
node_runtime.panels.reinitialize(node->num_panel_states);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 13:46:39 +01:00
|
|
|
static void update_internal_link_inputs(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
2022-12-01 14:53:27 -06:00
|
|
|
for (bNode *node : tree_runtime.nodes_by_id) {
|
2022-11-26 13:46:39 +01:00
|
|
|
for (bNodeSocket *socket : node->runtime->outputs) {
|
|
|
|
|
socket->runtime->internal_link_input = nullptr;
|
|
|
|
|
}
|
2023-01-09 23:29:58 -05:00
|
|
|
for (bNodeLink &link : node->runtime->internal_links) {
|
|
|
|
|
link.tosock->runtime->internal_link_input = link.fromsock;
|
2022-11-26 13:46:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-31 12:15:57 +02:00
|
|
|
static void update_directly_linked_links_and_sockets(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
2022-12-01 14:53:27 -06:00
|
|
|
for (bNode *node : tree_runtime.nodes_by_id) {
|
2022-08-31 12:15:57 +02:00
|
|
|
for (bNodeSocket *socket : node->runtime->inputs) {
|
|
|
|
|
socket->runtime->directly_linked_links.clear();
|
|
|
|
|
socket->runtime->directly_linked_sockets.clear();
|
|
|
|
|
}
|
|
|
|
|
for (bNodeSocket *socket : node->runtime->outputs) {
|
|
|
|
|
socket->runtime->directly_linked_links.clear();
|
|
|
|
|
socket->runtime->directly_linked_sockets.clear();
|
|
|
|
|
}
|
2022-09-20 13:21:03 +02:00
|
|
|
node->runtime->has_available_linked_inputs = false;
|
|
|
|
|
node->runtime->has_available_linked_outputs = false;
|
2022-08-31 12:15:57 +02:00
|
|
|
}
|
|
|
|
|
for (bNodeLink *link : tree_runtime.links) {
|
|
|
|
|
link->fromsock->runtime->directly_linked_links.append(link);
|
|
|
|
|
link->fromsock->runtime->directly_linked_sockets.append(link->tosock);
|
|
|
|
|
link->tosock->runtime->directly_linked_links.append(link);
|
2022-09-20 13:21:03 +02:00
|
|
|
if (link->is_available()) {
|
|
|
|
|
link->fromnode->runtime->has_available_linked_outputs = true;
|
|
|
|
|
link->tonode->runtime->has_available_linked_inputs = true;
|
|
|
|
|
}
|
2022-08-31 12:15:57 +02:00
|
|
|
}
|
|
|
|
|
for (bNodeSocket *socket : tree_runtime.input_sockets) {
|
|
|
|
|
if (socket->flag & SOCK_MULTI_INPUT) {
|
|
|
|
|
std::sort(socket->runtime->directly_linked_links.begin(),
|
|
|
|
|
socket->runtime->directly_linked_links.end(),
|
|
|
|
|
[&](const bNodeLink *a, const bNodeLink *b) {
|
2024-03-19 13:42:09 +01:00
|
|
|
return a->multi_input_sort_id > b->multi_input_sort_id;
|
2022-08-31 12:15:57 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (bNodeSocket *socket : tree_runtime.input_sockets) {
|
|
|
|
|
for (bNodeLink *link : socket->runtime->directly_linked_links) {
|
|
|
|
|
/* Do this after sorting the input links. */
|
|
|
|
|
socket->runtime->directly_linked_sockets.append(link->fromsock);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void find_logical_origins_for_socket_recursive(
|
|
|
|
|
bNodeSocket &input_socket,
|
|
|
|
|
bool only_follow_first_input_link,
|
|
|
|
|
Vector<bNodeSocket *, 16> &sockets_in_current_chain,
|
|
|
|
|
Vector<bNodeSocket *> &r_logical_origins,
|
|
|
|
|
Vector<bNodeSocket *> &r_skipped_origins)
|
|
|
|
|
{
|
|
|
|
|
if (sockets_in_current_chain.contains(&input_socket)) {
|
|
|
|
|
/* Protect against reroute recursions. */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
sockets_in_current_chain.append(&input_socket);
|
|
|
|
|
|
|
|
|
|
Span<bNodeLink *> links_to_check = input_socket.runtime->directly_linked_links;
|
|
|
|
|
if (only_follow_first_input_link) {
|
|
|
|
|
links_to_check = links_to_check.take_front(1);
|
|
|
|
|
}
|
|
|
|
|
for (bNodeLink *link : links_to_check) {
|
2022-09-20 13:21:03 +02:00
|
|
|
if (link->is_muted()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!link->is_available()) {
|
2022-08-31 12:15:57 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
bNodeSocket &origin_socket = *link->fromsock;
|
|
|
|
|
bNode &origin_node = *link->fromnode;
|
|
|
|
|
if (!origin_socket.is_available()) {
|
|
|
|
|
/* Non available sockets are ignored. */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-01-17 12:17:49 +01:00
|
|
|
if (origin_node.is_reroute()) {
|
2022-08-31 12:15:57 +02:00
|
|
|
bNodeSocket &reroute_input = *origin_node.runtime->inputs[0];
|
|
|
|
|
bNodeSocket &reroute_output = *origin_node.runtime->outputs[0];
|
|
|
|
|
r_skipped_origins.append(&reroute_input);
|
|
|
|
|
r_skipped_origins.append(&reroute_output);
|
|
|
|
|
find_logical_origins_for_socket_recursive(
|
|
|
|
|
reroute_input, false, sockets_in_current_chain, r_logical_origins, r_skipped_origins);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (origin_node.is_muted()) {
|
|
|
|
|
if (bNodeSocket *mute_input = origin_socket.runtime->internal_link_input) {
|
|
|
|
|
r_skipped_origins.append(&origin_socket);
|
|
|
|
|
r_skipped_origins.append(mute_input);
|
|
|
|
|
find_logical_origins_for_socket_recursive(
|
|
|
|
|
*mute_input, true, sockets_in_current_chain, r_logical_origins, r_skipped_origins);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
r_logical_origins.append(&origin_socket);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sockets_in_current_chain.pop_last();
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-23 18:31:49 +02:00
|
|
|
static void update_logically_linked_sockets(const bNodeTree &ntree)
|
2022-08-31 12:15:57 +02:00
|
|
|
{
|
2023-06-23 18:31:49 +02:00
|
|
|
/* Compute logically linked sockets to inputs. */
|
2022-08-31 12:15:57 +02:00
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
2022-12-01 14:53:27 -06:00
|
|
|
Span<bNode *> nodes = tree_runtime.nodes_by_id;
|
|
|
|
|
threading::parallel_for(nodes.index_range(), 128, [&](const IndexRange range) {
|
2022-08-31 12:15:57 +02:00
|
|
|
for (const int i : range) {
|
2022-12-01 14:53:27 -06:00
|
|
|
bNode &node = *nodes[i];
|
2022-08-31 12:15:57 +02:00
|
|
|
for (bNodeSocket *socket : node.runtime->inputs) {
|
|
|
|
|
Vector<bNodeSocket *, 16> sockets_in_current_chain;
|
2022-08-31 13:56:56 +02:00
|
|
|
socket->runtime->logically_linked_sockets.clear();
|
|
|
|
|
socket->runtime->logically_linked_skipped_sockets.clear();
|
2022-08-31 12:15:57 +02:00
|
|
|
find_logical_origins_for_socket_recursive(
|
|
|
|
|
*socket,
|
|
|
|
|
false,
|
|
|
|
|
sockets_in_current_chain,
|
|
|
|
|
socket->runtime->logically_linked_sockets,
|
|
|
|
|
socket->runtime->logically_linked_skipped_sockets);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-06-23 18:31:49 +02:00
|
|
|
|
|
|
|
|
/* Clear logically linked sockets to outputs. */
|
|
|
|
|
threading::parallel_for(nodes.index_range(), 128, [&](const IndexRange range) {
|
|
|
|
|
for (const int i : range) {
|
|
|
|
|
bNode &node = *nodes[i];
|
|
|
|
|
for (bNodeSocket *socket : node.runtime->outputs) {
|
|
|
|
|
socket->runtime->logically_linked_sockets.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Compute logically linked sockets to outputs using the previously computed logically linked
|
|
|
|
|
* sockets to inputs. */
|
|
|
|
|
for (const bNode *node : nodes) {
|
|
|
|
|
for (bNodeSocket *input_socket : node->runtime->inputs) {
|
|
|
|
|
for (bNodeSocket *output_socket : input_socket->runtime->logically_linked_sockets) {
|
|
|
|
|
output_socket->runtime->logically_linked_sockets.append(input_socket);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-31 12:15:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_nodes_by_type(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
|
|
|
|
tree_runtime.nodes_by_type.clear();
|
2022-12-01 14:53:27 -06:00
|
|
|
for (bNode *node : tree_runtime.nodes_by_id) {
|
2022-08-31 12:15:57 +02:00
|
|
|
tree_runtime.nodes_by_type.add(node->typeinfo, node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_sockets_by_identifier(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
2022-12-01 14:53:27 -06:00
|
|
|
Span<bNode *> nodes = tree_runtime.nodes_by_id;
|
|
|
|
|
threading::parallel_for(nodes.index_range(), 128, [&](const IndexRange range) {
|
|
|
|
|
for (bNode *node : nodes.slice(range)) {
|
2022-08-31 12:15:57 +02:00
|
|
|
node->runtime->inputs_by_identifier.clear();
|
|
|
|
|
node->runtime->outputs_by_identifier.clear();
|
|
|
|
|
for (bNodeSocket *socket : node->runtime->inputs) {
|
|
|
|
|
node->runtime->inputs_by_identifier.add_new(socket->identifier, socket);
|
|
|
|
|
}
|
|
|
|
|
for (bNodeSocket *socket : node->runtime->outputs) {
|
|
|
|
|
node->runtime->outputs_by_identifier.add_new(socket->identifier, socket);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum class ToposortDirection {
|
|
|
|
|
LeftToRight,
|
|
|
|
|
RightToLeft,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ToposortNodeState {
|
|
|
|
|
bool is_done = false;
|
|
|
|
|
bool is_in_stack = false;
|
|
|
|
|
};
|
|
|
|
|
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
static Vector<const bNode *> get_implicit_origin_nodes(const bNodeTree &ntree, bNode &node)
|
|
|
|
|
{
|
|
|
|
|
Vector<const bNode *> origin_nodes;
|
2025-01-09 15:28:57 +01:00
|
|
|
if (all_zone_output_node_types().contains(node.type_legacy)) {
|
|
|
|
|
const bNodeZoneType &zone_type = *zone_type_by_node_type(node.type_legacy);
|
2023-09-20 18:03:10 +02:00
|
|
|
/* Can't use #zone_type.get_corresponding_input because that expects the topology cache to be
|
|
|
|
|
* build already, but we are still building it here. */
|
|
|
|
|
for (const bNode *input_node :
|
2024-08-19 20:27:37 +02:00
|
|
|
ntree.runtime->nodes_by_type.lookup(bke::node_type_find(zone_type.input_idname.c_str())))
|
2023-09-20 18:03:10 +02:00
|
|
|
{
|
|
|
|
|
if (zone_type.get_corresponding_output_id(*input_node) == node.identifier) {
|
|
|
|
|
origin_nodes.append(input_node);
|
|
|
|
|
}
|
2023-07-11 22:36:10 +02:00
|
|
|
}
|
|
|
|
|
}
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
return origin_nodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Vector<const bNode *> get_implicit_target_nodes(const bNodeTree &ntree, bNode &node)
|
|
|
|
|
{
|
|
|
|
|
Vector<const bNode *> target_nodes;
|
2025-01-09 15:28:57 +01:00
|
|
|
if (all_zone_input_node_types().contains(node.type_legacy)) {
|
|
|
|
|
const bNodeZoneType &zone_type = *zone_type_by_node_type(node.type_legacy);
|
2023-09-20 14:40:56 +02:00
|
|
|
if (const bNode *output_node = zone_type.get_corresponding_output(ntree, node)) {
|
|
|
|
|
target_nodes.append(output_node);
|
2023-07-11 22:36:10 +02:00
|
|
|
}
|
|
|
|
|
}
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
return target_nodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void toposort_from_start_node(const bNodeTree &ntree,
|
|
|
|
|
const ToposortDirection direction,
|
2022-08-31 12:15:57 +02:00
|
|
|
bNode &start_node,
|
|
|
|
|
MutableSpan<ToposortNodeState> node_states,
|
|
|
|
|
Vector<bNode *> &r_sorted_nodes,
|
|
|
|
|
bool &r_cycle_detected)
|
|
|
|
|
{
|
|
|
|
|
struct Item {
|
|
|
|
|
bNode *node;
|
|
|
|
|
int socket_index = 0;
|
|
|
|
|
int link_index = 0;
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
int implicit_link_index = 0;
|
2022-08-31 12:15:57 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Stack<Item, 64> nodes_to_check;
|
|
|
|
|
nodes_to_check.push({&start_node});
|
2022-12-11 20:23:18 -06:00
|
|
|
node_states[start_node.index()].is_in_stack = true;
|
2022-08-31 12:15:57 +02:00
|
|
|
while (!nodes_to_check.is_empty()) {
|
|
|
|
|
Item &item = nodes_to_check.peek();
|
|
|
|
|
bNode &node = *item.node;
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
bool pushed_node = false;
|
|
|
|
|
|
|
|
|
|
auto handle_linked_node = [&](bNode &linked_node) {
|
|
|
|
|
ToposortNodeState &linked_node_state = node_states[linked_node.index()];
|
|
|
|
|
if (linked_node_state.is_done) {
|
|
|
|
|
/* The linked node has already been visited. */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (linked_node_state.is_in_stack) {
|
|
|
|
|
r_cycle_detected = true;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
nodes_to_check.push({&linked_node});
|
|
|
|
|
linked_node_state.is_in_stack = true;
|
|
|
|
|
pushed_node = true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2022-08-31 12:15:57 +02:00
|
|
|
const Span<bNodeSocket *> sockets = (direction == ToposortDirection::LeftToRight) ?
|
|
|
|
|
node.runtime->inputs :
|
|
|
|
|
node.runtime->outputs;
|
|
|
|
|
while (true) {
|
|
|
|
|
if (item.socket_index == sockets.size()) {
|
|
|
|
|
/* All sockets have already been visited. */
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
bNodeSocket &socket = *sockets[item.socket_index];
|
2022-09-20 13:21:03 +02:00
|
|
|
const Span<bNodeLink *> linked_links = socket.runtime->directly_linked_links;
|
|
|
|
|
if (item.link_index == linked_links.size()) {
|
2022-08-31 12:15:57 +02:00
|
|
|
/* All links connected to this socket have already been visited. */
|
|
|
|
|
item.socket_index++;
|
|
|
|
|
item.link_index = 0;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-09-20 13:21:03 +02:00
|
|
|
bNodeLink &link = *linked_links[item.link_index];
|
|
|
|
|
if (!link.is_available()) {
|
|
|
|
|
/* Ignore unavailable links. */
|
|
|
|
|
item.link_index++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
bNodeSocket &linked_socket = *socket.runtime->directly_linked_sockets[item.link_index];
|
2022-08-31 12:15:57 +02:00
|
|
|
bNode &linked_node = *linked_socket.runtime->owner_node;
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
if (handle_linked_node(linked_node)) {
|
2022-08-31 12:15:57 +02:00
|
|
|
/* The linked node has already been visited. */
|
|
|
|
|
item.link_index++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
if (!pushed_node) {
|
|
|
|
|
/* Some nodes are internally linked without an explicit `bNodeLink`. The toposort should
|
|
|
|
|
* still order them correctly and find cycles. */
|
|
|
|
|
const Vector<const bNode *> implicitly_linked_nodes =
|
|
|
|
|
(direction == ToposortDirection::LeftToRight) ? get_implicit_origin_nodes(ntree, node) :
|
|
|
|
|
get_implicit_target_nodes(ntree, node);
|
|
|
|
|
while (true) {
|
|
|
|
|
if (item.implicit_link_index == implicitly_linked_nodes.size()) {
|
|
|
|
|
/* All implicitly linked nodes have already been visited. */
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
const bNode &linked_node = *implicitly_linked_nodes[item.implicit_link_index];
|
|
|
|
|
if (handle_linked_node(const_cast<bNode &>(linked_node))) {
|
|
|
|
|
/* The implicitly linked node has already been visited. */
|
|
|
|
|
item.implicit_link_index++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If no other element has been pushed, the current node can be pushed to the sorted list.
|
|
|
|
|
*/
|
|
|
|
|
if (!pushed_node) {
|
2022-12-11 20:23:18 -06:00
|
|
|
ToposortNodeState &node_state = node_states[node.index()];
|
2022-08-31 12:15:57 +02:00
|
|
|
node_state.is_done = true;
|
|
|
|
|
node_state.is_in_stack = false;
|
|
|
|
|
r_sorted_nodes.append(&node);
|
|
|
|
|
nodes_to_check.pop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_toposort(const bNodeTree &ntree,
|
|
|
|
|
const ToposortDirection direction,
|
|
|
|
|
Vector<bNode *> &r_sorted_nodes,
|
|
|
|
|
bool &r_cycle_detected)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
|
|
|
|
r_sorted_nodes.clear();
|
2022-12-01 14:53:27 -06:00
|
|
|
r_sorted_nodes.reserve(tree_runtime.nodes_by_id.size());
|
2022-08-31 12:15:57 +02:00
|
|
|
r_cycle_detected = false;
|
|
|
|
|
|
2022-12-01 14:53:27 -06:00
|
|
|
Array<ToposortNodeState> node_states(tree_runtime.nodes_by_id.size());
|
|
|
|
|
for (bNode *node : tree_runtime.nodes_by_id) {
|
2022-12-11 20:23:18 -06:00
|
|
|
if (node_states[node->index()].is_done) {
|
2022-08-31 12:15:57 +02:00
|
|
|
/* Ignore nodes that are done already. */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-09-20 13:21:03 +02:00
|
|
|
if ((direction == ToposortDirection::LeftToRight) ?
|
|
|
|
|
node->runtime->has_available_linked_outputs :
|
|
|
|
|
node->runtime->has_available_linked_inputs)
|
|
|
|
|
{
|
2022-08-31 12:15:57 +02:00
|
|
|
/* Ignore non-start nodes. */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
toposort_from_start_node(
|
|
|
|
|
ntree, direction, *node, node_states, r_sorted_nodes, r_cycle_detected);
|
2022-08-31 12:15:57 +02:00
|
|
|
}
|
|
|
|
|
|
2022-12-01 14:53:27 -06:00
|
|
|
if (r_sorted_nodes.size() < tree_runtime.nodes_by_id.size()) {
|
2022-08-31 12:15:57 +02:00
|
|
|
r_cycle_detected = true;
|
2022-12-01 14:53:27 -06:00
|
|
|
for (bNode *node : tree_runtime.nodes_by_id) {
|
2022-12-11 20:23:18 -06:00
|
|
|
if (node_states[node->index()].is_done) {
|
2022-08-31 12:15:57 +02:00
|
|
|
/* Ignore nodes that are done already. */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* Start toposort at this node which is somewhere in the middle of a loop. */
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
toposort_from_start_node(
|
|
|
|
|
ntree, direction, *node, node_states, r_sorted_nodes, r_cycle_detected);
|
2022-08-31 12:15:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-01 14:53:27 -06:00
|
|
|
BLI_assert(tree_runtime.nodes_by_id.size() == r_sorted_nodes.size());
|
2022-08-31 12:15:57 +02:00
|
|
|
}
|
|
|
|
|
|
2022-11-18 11:20:13 +01:00
|
|
|
static void update_root_frames(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
2022-12-01 14:53:27 -06:00
|
|
|
Span<bNode *> nodes = tree_runtime.nodes_by_id;
|
2022-11-18 11:20:13 +01:00
|
|
|
|
|
|
|
|
tree_runtime.root_frames.clear();
|
|
|
|
|
|
|
|
|
|
for (bNode *node : nodes) {
|
|
|
|
|
if (!node->parent && node->is_frame()) {
|
|
|
|
|
tree_runtime.root_frames.append(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_direct_frames_childrens(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
2022-12-01 14:53:27 -06:00
|
|
|
Span<bNode *> nodes = tree_runtime.nodes_by_id;
|
2022-11-18 11:20:13 +01:00
|
|
|
|
|
|
|
|
for (bNode *node : nodes) {
|
|
|
|
|
node->runtime->direct_children_in_frame.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (bNode *node : nodes) {
|
|
|
|
|
if (const bNode *frame = node->parent) {
|
|
|
|
|
frame->runtime->direct_children_in_frame.append(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-31 12:15:57 +02:00
|
|
|
static void update_group_output_node(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
2024-08-19 20:27:37 +02:00
|
|
|
const bke::bNodeType *node_type = bke::node_type_find("NodeGroupOutput");
|
2022-08-31 12:15:57 +02:00
|
|
|
const Span<bNode *> group_output_nodes = tree_runtime.nodes_by_type.lookup(node_type);
|
|
|
|
|
if (group_output_nodes.is_empty()) {
|
|
|
|
|
tree_runtime.group_output_node = nullptr;
|
|
|
|
|
}
|
|
|
|
|
else if (group_output_nodes.size() == 1) {
|
|
|
|
|
tree_runtime.group_output_node = group_output_nodes[0];
|
|
|
|
|
}
|
|
|
|
|
else {
|
2025-03-25 08:23:52 +01:00
|
|
|
tree_runtime.group_output_node = nullptr;
|
2022-08-31 12:15:57 +02:00
|
|
|
for (bNode *group_output : group_output_nodes) {
|
|
|
|
|
if (group_output->flag & NODE_DO_OUTPUT) {
|
|
|
|
|
tree_runtime.group_output_node = group_output;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-08 16:19:40 +02:00
|
|
|
static void update_dangling_reroute_nodes(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
for (const bNode *node : ntree.runtime->toposort_left_to_right) {
|
|
|
|
|
bNodeRuntime &node_runtime = *node->runtime;
|
|
|
|
|
if (!node->is_reroute()) {
|
|
|
|
|
node_runtime.is_dangling_reroute = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const Span<const bNodeLink *> links = node_runtime.inputs[0]->runtime->directly_linked_links;
|
|
|
|
|
if (links.is_empty()) {
|
|
|
|
|
node_runtime.is_dangling_reroute = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
BLI_assert(links.size() == 1);
|
|
|
|
|
const bNode &source_node = *links.first()->fromnode;
|
|
|
|
|
node_runtime.is_dangling_reroute = source_node.runtime->is_dangling_reroute;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-31 12:15:57 +02:00
|
|
|
static void ensure_topology_cache(const bNodeTree &ntree)
|
|
|
|
|
{
|
|
|
|
|
bNodeTreeRuntime &tree_runtime = *ntree.runtime;
|
2022-12-20 13:05:02 +01:00
|
|
|
tree_runtime.topology_cache_mutex.ensure([&]() {
|
|
|
|
|
update_node_vector(ntree);
|
|
|
|
|
update_link_vector(ntree);
|
|
|
|
|
update_socket_vectors_and_owner_node(ntree);
|
2023-08-30 12:37:21 +02:00
|
|
|
update_panels(ntree);
|
2022-12-20 13:05:02 +01:00
|
|
|
update_internal_link_inputs(ntree);
|
|
|
|
|
update_directly_linked_links_and_sockets(ntree);
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
update_nodes_by_type(ntree);
|
2022-12-20 13:05:02 +01:00
|
|
|
threading::parallel_invoke(
|
|
|
|
|
tree_runtime.nodes_by_id.size() > 32,
|
2023-06-23 18:31:49 +02:00
|
|
|
[&]() { update_logically_linked_sockets(ntree); },
|
2022-12-20 13:05:02 +01:00
|
|
|
[&]() { update_sockets_by_identifier(ntree); },
|
|
|
|
|
[&]() {
|
|
|
|
|
update_toposort(ntree,
|
|
|
|
|
ToposortDirection::LeftToRight,
|
|
|
|
|
tree_runtime.toposort_left_to_right,
|
|
|
|
|
tree_runtime.has_available_link_cycle);
|
2023-06-16 10:37:18 +02:00
|
|
|
for (const int i : tree_runtime.toposort_left_to_right.index_range()) {
|
|
|
|
|
const bNode &node = *tree_runtime.toposort_left_to_right[i];
|
|
|
|
|
node.runtime->toposort_left_to_right_index = i;
|
|
|
|
|
}
|
2022-12-20 13:05:02 +01:00
|
|
|
},
|
|
|
|
|
[&]() {
|
|
|
|
|
bool dummy;
|
|
|
|
|
update_toposort(
|
|
|
|
|
ntree, ToposortDirection::RightToLeft, tree_runtime.toposort_right_to_left, dummy);
|
2023-06-16 10:37:18 +02:00
|
|
|
for (const int i : tree_runtime.toposort_right_to_left.index_range()) {
|
|
|
|
|
const bNode &node = *tree_runtime.toposort_right_to_left[i];
|
|
|
|
|
node.runtime->toposort_right_to_left_index = i;
|
|
|
|
|
}
|
2022-12-20 13:05:02 +01:00
|
|
|
},
|
|
|
|
|
[&]() { update_root_frames(ntree); },
|
|
|
|
|
[&]() { update_direct_frames_childrens(ntree); });
|
|
|
|
|
update_group_output_node(ntree);
|
2024-04-08 16:19:40 +02:00
|
|
|
update_dangling_reroute_nodes(ntree);
|
2022-12-20 13:05:02 +01:00
|
|
|
tree_runtime.topology_cache_exists = true;
|
|
|
|
|
});
|
2022-08-31 12:15:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace blender::bke::node_tree_runtime
|
|
|
|
|
|
2025-05-09 04:06:00 +02:00
|
|
|
namespace blender::bke {
|
|
|
|
|
|
|
|
|
|
NodeLinkKey::NodeLinkKey(const bNodeLink &link)
|
|
|
|
|
{
|
|
|
|
|
to_node_id_ = link.tonode->identifier;
|
|
|
|
|
input_socket_index_ = link.tosock->index();
|
|
|
|
|
input_link_index_ =
|
|
|
|
|
const_cast<const bNodeSocket *>(link.tosock)->directly_linked_links().first_index(&link);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bNodeLink *NodeLinkKey::try_find(bNodeTree &ntree) const
|
|
|
|
|
{
|
|
|
|
|
return const_cast<bNodeLink *>(this->try_find(const_cast<const bNodeTree &>(ntree)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bNodeLink *NodeLinkKey::try_find(const bNodeTree &ntree) const
|
|
|
|
|
{
|
|
|
|
|
const bNode *to_node = ntree.node_by_id(to_node_id_);
|
|
|
|
|
if (!to_node) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (input_socket_index_ >= to_node->input_sockets().size()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
const bNodeSocket &input_socket = to_node->input_socket(input_socket_index_);
|
|
|
|
|
if (input_link_index_ >= input_socket.directly_linked_links().size()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
return input_socket.directly_linked_links()[input_link_index_];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace blender::bke
|
|
|
|
|
|
2022-08-31 12:15:57 +02:00
|
|
|
void bNodeTree::ensure_topology_cache() const
|
|
|
|
|
{
|
|
|
|
|
blender::bke::node_tree_runtime::ensure_topology_cache(*this);
|
|
|
|
|
}
|
Nodes: add nested node ids and use them for simulation state
The simulation state used by simulation nodes is owned by the modifier. Since a
geometry nodes setup can contain an arbitrary number of simulations, the modifier
has a mapping from `SimulationZoneID` to `SimulationZoneState`. This patch changes
what is used as `SimulationZoneID`.
Previously, the `SimulationZoneID` contained a list of `bNode::identifier` that described
the path from the root node tree to the simulation output node. This works ok in many
cases, but also has a significant problem: The `SimulationZoneID` changes when moving
the simulation zone into or out of a node group. This implies that any of these operations
loses the mapping from zone to simulation state, invalidating the cache or even baked data.
The goal of this patch is to introduce a single-integer ID that identifies a (nested) simulation
zone and is stable even when grouping and un-grouping. The ID should be stable even if the
node group containing the (nested) simulation zone is in a separate linked .blend file and
that linked file is changed.
In the future, the same kind of ID can be used to store e.g. checkpoint/baked/frozen data
in the modifier.
To achieve the described goal, node trees can now store an arbitrary number of nested node
references (an array of `bNestedNodeRef`). Each nested node reference has an ID that is
unique within the current node tree. The node tree does not store the entire path to the
nested node. Instead it only know which group node the nested node is in, and what the
nested node ID of the node is within that group. Grouping and un-grouping operations
have to update the nested node references to keep the IDs stable. Importantly though,
these operations only have to care about the two node groups that are affected. IDs in
higher level node groups remain unchanged by design.
A consequence of this design is that every `bNodeTree` now has a `bNestedNodeRef`
for every (nested) simulation zone. Two instances of the same simulation zone (because
a node group is reused) are referenced by two separate `bNestedNodeRef`. This is
important to keep in mind, because it also means that this solution doesn't scale well if
we wanted to use it to keep stable references to *all* nested nodes. I can't think of a
solution that fulfills the described requirements but scales better with more nodes. For
that reason, this solution should only be used when we want to store data for each
referenced nested node at the top level (like we do for simulations).
This is not a replacement for `ViewerPath` which can store a path to data in a node tree
without changing the node tree. Also `ViewerPath` can contain information like the loop
iteration that should be viewed (#109164). `bNestedNodeRef` can't differentiate between
different iterations of a loop. This also means that simulations can't be used inside of a
loop (loops inside of a simulation work fine though).
When baking, the new stable ID is now written to disk, which means that baked data is
not invalidated by grouping/un-grouping operations. Backward compatibility for baked
data is provided, but only works as long as the simulation zone has not been moved to
a different node group yet. Forward compatibility for the baked data is not provided
(so older versions can't load the data baked with a newer version of Blender).
Pull Request: https://projects.blender.org/blender/blender/pulls/109444
2023-07-01 11:54:32 +02:00
|
|
|
|
|
|
|
|
const bNestedNodeRef *bNodeTree::find_nested_node_ref(const int32_t nested_node_id) const
|
|
|
|
|
{
|
|
|
|
|
for (const bNestedNodeRef &ref : this->nested_node_refs_span()) {
|
|
|
|
|
if (ref.id == nested_node_id) {
|
|
|
|
|
return &ref;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bNestedNodeRef *bNodeTree::nested_node_ref_from_node_id_path(
|
|
|
|
|
const blender::Span<int32_t> node_ids) const
|
|
|
|
|
{
|
|
|
|
|
if (node_ids.is_empty()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
for (const bNestedNodeRef &ref : this->nested_node_refs_span()) {
|
|
|
|
|
blender::Vector<int> current_node_ids;
|
|
|
|
|
if (this->node_id_path_from_nested_node_ref(ref.id, current_node_ids)) {
|
|
|
|
|
if (current_node_ids.as_span() == node_ids) {
|
|
|
|
|
return &ref;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool bNodeTree::node_id_path_from_nested_node_ref(const int32_t nested_node_id,
|
|
|
|
|
blender::Vector<int> &r_node_ids) const
|
|
|
|
|
{
|
|
|
|
|
const bNestedNodeRef *ref = this->find_nested_node_ref(nested_node_id);
|
|
|
|
|
if (ref == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const int32_t node_id = ref->path.node_id;
|
|
|
|
|
const bNode *node = this->node_by_id(node_id);
|
|
|
|
|
if (node == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
r_node_ids.append(node_id);
|
|
|
|
|
if (!node->is_group()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node->id);
|
|
|
|
|
if (group == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return group->node_id_path_from_nested_node_ref(ref->path.id_in_node, r_node_ids);
|
|
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
|
2024-01-17 11:48:06 +01:00
|
|
|
const bNode *bNodeTree::find_nested_node(const int32_t nested_node_id,
|
|
|
|
|
const bNodeTree **r_tree) const
|
2023-09-26 20:30:46 +02:00
|
|
|
{
|
|
|
|
|
const bNestedNodeRef *ref = this->find_nested_node_ref(nested_node_id);
|
|
|
|
|
if (ref == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
const int32_t node_id = ref->path.node_id;
|
|
|
|
|
const bNode *node = this->node_by_id(node_id);
|
|
|
|
|
if (node == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (!node->is_group()) {
|
2024-01-17 11:48:06 +01:00
|
|
|
if (r_tree) {
|
|
|
|
|
*r_tree = this;
|
|
|
|
|
}
|
2023-09-26 20:30:46 +02:00
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
|
const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node->id);
|
|
|
|
|
if (group == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2024-01-17 11:48:06 +01:00
|
|
|
return group->find_nested_node(ref->path.id_in_node, r_tree);
|
2023-09-26 20:30:46 +02:00
|
|
|
}
|
2024-10-11 12:20:58 +02:00
|
|
|
|
|
|
|
|
const bNodeSocket &bNode::socket_by_decl(const blender::nodes::SocketDeclaration &decl) const
|
|
|
|
|
{
|
|
|
|
|
return decl.in_out == SOCK_IN ? this->input_socket(decl.index) : this->output_socket(decl.index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bNodeSocket &bNode::socket_by_decl(const blender::nodes::SocketDeclaration &decl)
|
|
|
|
|
{
|
|
|
|
|
return decl.in_out == SOCK_IN ? this->input_socket(decl.index) : this->output_socket(decl.index);
|
|
|
|
|
}
|
2025-01-21 12:53:24 +01:00
|
|
|
|
2025-05-22 04:48:43 +02:00
|
|
|
static void ensure_inference_usage_cache(const bNodeTree &tree)
|
|
|
|
|
{
|
|
|
|
|
tree.runtime->inferenced_input_socket_usage_mutex.ensure([&]() {
|
|
|
|
|
tree.runtime->inferenced_input_socket_usage =
|
|
|
|
|
blender::nodes::socket_usage_inference::infer_all_input_sockets_usage(tree);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-21 12:53:24 +01:00
|
|
|
bool bNodeSocket::affects_node_output() const
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(this->is_input());
|
|
|
|
|
BLI_assert(blender::bke::node_tree_runtime::topology_cache_is_available(*this));
|
|
|
|
|
const bNodeTree &tree = this->owner_tree();
|
2025-05-22 04:48:43 +02:00
|
|
|
ensure_inference_usage_cache(tree);
|
|
|
|
|
return tree.runtime->inferenced_input_socket_usage[this->index_in_all_inputs()].is_used;
|
|
|
|
|
}
|
2025-01-21 12:53:24 +01:00
|
|
|
|
2025-05-22 04:48:43 +02:00
|
|
|
bool bNodeSocket::inferred_input_socket_visibility() const
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(this->is_input());
|
|
|
|
|
BLI_assert(blender::bke::node_tree_runtime::topology_cache_is_available(*this));
|
|
|
|
|
const bNode &node = this->owner_node();
|
|
|
|
|
if (node.typeinfo->ignore_inferred_input_socket_visibility) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
const bNodeTree &tree = this->owner_tree();
|
2025-01-21 12:53:24 +01:00
|
|
|
|
2025-05-22 04:48:43 +02:00
|
|
|
ensure_inference_usage_cache(tree);
|
|
|
|
|
return tree.runtime->inferenced_input_socket_usage[this->index_in_all_inputs()].is_visible;
|
2025-01-21 12:53:24 +01:00
|
|
|
}
|