Fix #140018: no warning when using bake node or simulation in zone

Baking and storing simulation state within loops or closures is not supported.
Previously, attempting to use the bake node or simulation zone in such a zone
would just silently fail. Now there is an error on the node and the bake
settings are grayed out.

Pull Request: https://projects.blender.org/blender/blender/pulls/140041
This commit is contained in:
Jacques Lucke
2025-06-09 12:08:24 +02:00
parent a8e461f2de
commit 9efc23bb69
9 changed files with 112 additions and 84 deletions

View File

@@ -14,6 +14,7 @@
#include "BKE_compute_context_cache_fwd.hh"
#include "NOD_geometry_nodes_closure_location.hh"
#include "NOD_nested_node_id.hh"
#include "ED_node_c.hh"
@@ -82,7 +83,10 @@ void std_node_socket_colors_get(int socket_type, float *r_color);
/**
* Find the nested node id of a currently visible node in the root tree.
*/
std::optional<int32_t> find_nested_node_id_in_root(const SpaceNode &snode, const bNode &node);
std::optional<nodes::FoundNestedNodeID> find_nested_node_id_in_root(const SpaceNode &snode,
const bNode &node);
std::optional<nodes::FoundNestedNodeID> find_nested_node_id_in_root(
const bNodeTree &root_tree, const ComputeContext *compute_context, const int node_id);
struct ObjectAndModifier {
const Object *object;
@@ -112,6 +116,9 @@ bool node_editor_is_for_geometry_nodes_modifier(const SpaceNode &snode,
bke::ComputeContextCache &compute_context_cache,
const bNodeSocket &socket);
[[nodiscard]] const ComputeContext *compute_context_for_edittree_node(
const SpaceNode &snode, bke::ComputeContextCache &compute_context_cache, const bNode &node);
/**
* Attempts to find a compute context that the closure is evaluated in. If none is found, null is
* returned. If multiple are found, it currently picks the first one it finds which is somewhat

View File

@@ -268,51 +268,53 @@ float2 space_node_group_offset(const SpaceNode &snode)
return float2(0);
}
static const bNode *group_node_by_name(const bNodeTree &ntree, StringRef name)
{
for (const bNode *node : ntree.group_nodes()) {
if (node->name == name) {
return node;
}
}
return nullptr;
}
std::optional<int32_t> find_nested_node_id_in_root(const SpaceNode &snode, const bNode &query_node)
std::optional<nodes::FoundNestedNodeID> find_nested_node_id_in_root(const SpaceNode &snode,
const bNode &query_node)
{
BLI_assert(snode.edittree->runtime->nodes_by_id.contains(const_cast<bNode *>(&query_node)));
bke::ComputeContextCache compute_context_cache;
const ComputeContext *compute_context = compute_context_for_edittree_node(
snode, compute_context_cache, query_node);
if (!compute_context) {
return {};
}
return find_nested_node_id_in_root(*snode.nodetree, compute_context, query_node.identifier);
}
std::optional<int32_t> id_in_node;
const char *group_node_name = nullptr;
const bNode *node = &query_node;
LISTBASE_FOREACH_BACKWARD (const bNodeTreePath *, path, &snode.treepath) {
const bNodeTree *ntree = path->nodetree;
ntree->ensure_topology_cache();
if (group_node_name) {
node = group_node_by_name(*ntree, group_node_name);
std::optional<nodes::FoundNestedNodeID> find_nested_node_id_in_root(
const bNodeTree &root_tree, const ComputeContext *compute_context, const int node_id)
{
nodes::FoundNestedNodeID found;
Vector<int> node_ids;
for (const ComputeContext *context = compute_context; context != nullptr;
context = context->parent())
{
if (const auto *node_context = dynamic_cast<const bke::GroupNodeComputeContext *>(context)) {
node_ids.append(node_context->node_id());
}
bool found = false;
for (const bNestedNodeRef &ref : ntree->nested_node_refs_span()) {
if (node->is_group()) {
if (ref.path.node_id == node->identifier && ref.path.id_in_node == id_in_node) {
group_node_name = path->node_name;
id_in_node = ref.id;
found = true;
break;
}
}
else if (ref.path.node_id == node->identifier) {
group_node_name = path->node_name;
id_in_node = ref.id;
found = true;
break;
}
else if (dynamic_cast<const bke::RepeatZoneComputeContext *>(context) != nullptr) {
found.is_in_loop = true;
}
if (!found) {
return std::nullopt;
else if (dynamic_cast<const bke::SimulationZoneComputeContext *>(context) != nullptr) {
found.is_in_simulation = true;
}
else if (dynamic_cast<const bke::ForeachGeometryElementZoneComputeContext *>(context) !=
nullptr)
{
found.is_in_loop = true;
}
else if (dynamic_cast<const bke::EvaluateClosureComputeContext *>(context) != nullptr) {
found.is_in_closure = true;
}
}
return id_in_node;
std::reverse(node_ids.begin(), node_ids.end());
node_ids.append(node_id);
const bNestedNodeRef *nested_node_ref = root_tree.nested_node_ref_from_node_id_path(node_ids);
if (nested_node_ref == nullptr) {
return std::nullopt;
}
found.id = nested_node_ref->id;
return found;
}
std::optional<ObjectAndModifier> get_modifier_for_node_editor(const SpaceNode &snode)
@@ -645,6 +647,22 @@ const ComputeContext *compute_context_for_edittree_socket(
return compute_context_for_zones(zone_stack, compute_context_cache, context);
}
const ComputeContext *compute_context_for_edittree_node(
const SpaceNode &snode, bke::ComputeContextCache &compute_context_cache, const bNode &node)
{
const ComputeContext *context = compute_context_for_edittree(snode, compute_context_cache);
if (!context) {
return nullptr;
}
const bke::bNodeTreeZones *zones = snode.edittree->zones();
if (!zones) {
return nullptr;
}
const bke::bNodeTreeZone *zone = zones->get_zone_by_node(node.identifier);
const Vector<const bke::bNodeTreeZone *> zone_stack = zones->get_zones_to_enter_from_root(zone);
return compute_context_for_zones(zone_stack, compute_context_cache, context);
}
/* ******************** default callbacks for node space ***************** */
static SpaceLink *node_create(const ScrArea * /*area*/, const Scene * /*scene*/)

View File

@@ -122,6 +122,7 @@ set(SRC
NOD_inverse_eval_run.hh
NOD_math_functions.hh
NOD_multi_function.hh
NOD_nested_node_id.hh
NOD_node_declaration.hh
NOD_node_extra_info.hh
NOD_node_in_compute_context.hh

View File

@@ -27,6 +27,7 @@
#include "NOD_geometry_nodes_log.hh"
#include "NOD_multi_function.hh"
#include "NOD_nested_node_id.hh"
#include "BLI_compute_context.hh"
#include "BLI_math_quaternion_types.hh"
@@ -461,13 +462,6 @@ std::string make_anonymous_attribute_socket_inspection_string(const bNodeSocket
std::string make_anonymous_attribute_socket_inspection_string(StringRef node_name,
StringRef socket_name);
struct FoundNestedNodeID {
int id;
bool is_in_simulation = false;
bool is_in_loop = false;
bool is_in_closure = false;
};
std::optional<FoundNestedNodeID> find_nested_node_id(const GeoNodesUserData &user_data,
const int node_id);

View File

@@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
namespace blender::nodes {
/**
* Utility struct to store information about a nested node id. Also see #bNestedNodeRef.
* Sometimes these IDs can only be used when they are at the top level and not within zones.
*/
struct FoundNestedNodeID {
int id;
bool is_in_simulation = false;
bool is_in_loop = false;
bool is_in_closure = false;
};
} // namespace blender::nodes

View File

@@ -111,6 +111,7 @@ struct BakeDrawContext {
bool bake_still;
bool is_baked;
std::optional<NodesModifierBakeTarget> bake_target;
bool is_bakeable_in_current_context;
};
[[nodiscard]] bool get_bake_draw_context(const bContext *C,

View File

@@ -489,6 +489,12 @@ static void node_extra_info(NodeExtraInfoParams &params)
if (!get_bake_draw_context(&params.C, params.node, ctx)) {
return;
}
if (!ctx.is_bakeable_in_current_context) {
NodeExtraInfoRow row;
row.text = TIP_("Can't bake in zone");
row.icon = ICON_ERROR;
params.rows.append(std::move(row));
}
if (ctx.is_baked) {
NodeExtraInfoRow row;
row.text = get_baked_string(ctx);
@@ -503,7 +509,7 @@ static void node_layout(uiLayout *layout, bContext *C, PointerRNA *ptr)
if (!get_bake_draw_context(C, node, ctx)) {
return;
}
uiLayoutSetActive(layout, ctx.is_bakeable_in_current_context);
uiLayoutSetEnabled(layout, ID_IS_EDITABLE(ctx.object));
uiLayout *col = &layout->column(false);
{
@@ -524,6 +530,7 @@ static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
return;
}
uiLayoutSetActive(layout, ctx.is_bakeable_in_current_context);
uiLayoutSetEnabled(layout, ID_IS_EDITABLE(ctx.object));
{
@@ -631,14 +638,15 @@ bool get_bake_draw_context(const bContext *C, const bNode &node, BakeDrawContext
}
r_ctx.object = object_and_modifier->object;
r_ctx.nmd = object_and_modifier->nmd;
const std::optional<int32_t> bake_id = ed::space_node::find_nested_node_id_in_root(*r_ctx.snode,
*r_ctx.node);
const std::optional<FoundNestedNodeID> bake_id = ed::space_node::find_nested_node_id_in_root(
*r_ctx.snode, *r_ctx.node);
if (!bake_id) {
return false;
}
r_ctx.is_bakeable_in_current_context = !bake_id->is_in_loop && !bake_id->is_in_closure;
r_ctx.bake = nullptr;
for (const NodesModifierBake &iter_bake : Span(r_ctx.nmd->bakes, r_ctx.nmd->bakes_num)) {
if (iter_bake.id == *bake_id) {
if (iter_bake.id == bake_id->id) {
r_ctx.bake = &iter_bake;
break;
}
@@ -653,7 +661,7 @@ bool get_bake_draw_context(const bContext *C, const bNode &node, BakeDrawContext
const bke::bake::ModifierCache &cache = *r_ctx.nmd->runtime->cache;
std::lock_guard lock{cache.mutex};
if (const std::unique_ptr<bke::bake::BakeNodeCache> *node_cache_ptr =
cache.bake_cache_by_id.lookup_ptr(*bake_id))
cache.bake_cache_by_id.lookup_ptr(bake_id->id))
{
const bke::bake::BakeNodeCache &node_cache = **node_cache_ptr;
if (!node_cache.bake.frames.is_empty()) {
@@ -663,7 +671,7 @@ bool get_bake_draw_context(const bContext *C, const bNode &node, BakeDrawContext
}
}
else if (const std::unique_ptr<bke::bake::SimulationNodeCache> *node_cache_ptr =
cache.simulation_cache_by_id.lookup_ptr(*bake_id))
cache.simulation_cache_by_id.lookup_ptr(bake_id->id))
{
const bke::bake::SimulationNodeCache &node_cache = **node_cache_ptr;
if (!node_cache.bake.frames.is_empty() &&

View File

@@ -225,6 +225,7 @@ static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_no
if (!get_bake_draw_context(C, output_node, ctx)) {
return;
}
uiLayoutSetActive(layout, ctx.is_bakeable_in_current_context);
draw_simulation_state(C, layout, ntree, output_node);
@@ -865,6 +866,12 @@ static void node_extra_info(NodeExtraInfoParams &params)
if (!get_bake_draw_context(&params.C, params.node, ctx)) {
return;
}
if (!ctx.is_bakeable_in_current_context) {
NodeExtraInfoRow row;
row.text = TIP_("Can't bake in zone");
row.icon = ICON_ERROR;
params.rows.append(std::move(row));
}
if (ctx.is_baked) {
NodeExtraInfoRow row;
row.text = get_baked_string(ctx);

View File

@@ -47,6 +47,8 @@
#include "BKE_node_tree_zones.hh"
#include "BKE_type_conversions.hh"
#include "ED_node.hh"
#include "FN_lazy_function_graph_executor.hh"
#include "DEG_depsgraph_query.hh"
@@ -4302,38 +4304,8 @@ void GeoNodesLocalUserData::ensure_tree_logger(const GeoNodesUserData &user_data
std::optional<FoundNestedNodeID> find_nested_node_id(const GeoNodesUserData &user_data,
const int node_id)
{
FoundNestedNodeID found;
Vector<int> node_ids;
for (const ComputeContext *context = user_data.compute_context; context != nullptr;
context = context->parent())
{
if (const auto *node_context = dynamic_cast<const bke::GroupNodeComputeContext *>(context)) {
node_ids.append(node_context->node_id());
}
else if (dynamic_cast<const bke::RepeatZoneComputeContext *>(context) != nullptr) {
found.is_in_loop = true;
}
else if (dynamic_cast<const bke::SimulationZoneComputeContext *>(context) != nullptr) {
found.is_in_simulation = true;
}
else if (dynamic_cast<const bke::ForeachGeometryElementZoneComputeContext *>(context) !=
nullptr)
{
found.is_in_loop = true;
}
else if (dynamic_cast<const bke::EvaluateClosureComputeContext *>(context) != nullptr) {
found.is_in_closure = true;
}
}
std::reverse(node_ids.begin(), node_ids.end());
node_ids.append(node_id);
const bNestedNodeRef *nested_node_ref =
user_data.call_data->root_ntree->nested_node_ref_from_node_id_path(node_ids);
if (nested_node_ref == nullptr) {
return std::nullopt;
}
found.id = nested_node_ref->id;
return found;
return ed::space_node::find_nested_node_id_in_root(
*user_data.call_data->root_ntree, user_data.compute_context, node_id);
}
GeoNodesOperatorDepsgraphs::~GeoNodesOperatorDepsgraphs()