Geometry Nodes: support custom warnings in node groups

This implements the `Warning` node that allows node groups to communicate
expectations about input values to the user.

By default, the `Warning` node is only evaluated if the node group that contains
it is evaluated in any way. This is better than always evaluating it, because
that could trigger lots of unnecessary evaluation in parts of the potentially
large node tree which should be ignored. In this basic mode, the output of the
node should not be connected to anything and it must not be in a zone.

For more fine-grained control for when the `Warning` node should be evaluated,
one can use the boolean output which is just a pass-through of the `Show` input.
If this output is used, the `Warning` node will only be evaluated if its output
is used. A simple way to use it is to control a Switch node with it that e.g.
"disables" a specific output when the inputs are invalid. In this case, the
`Warning` node may also be in a zone.

The node allows the user to choose between 3 severity levels: Error, Warning and
Info. Those are the same levels that we use internally. Currently, the error and
warning mode are pretty much the same, but that may change in the future.

Pull Request: https://projects.blender.org/blender/blender/pulls/125544
This commit is contained in:
Jacques Lucke
2024-08-29 16:03:25 +02:00
parent 4449fba2f3
commit e8b81065bc
9 changed files with 209 additions and 7 deletions

View File

@@ -504,6 +504,7 @@ class NODE_MT_category_GEO_OUTPUT(Menu):
layout = self.layout
node_add_menu.add_node_type(layout, "NodeGroupOutput")
node_add_menu.add_node_type(layout, "GeometryNodeViewer")
node_add_menu.add_node_type(layout, "GeometryNodeWarning")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)

View File

@@ -1375,6 +1375,7 @@ void node_tree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index);
#define GEO_NODE_CURVES_TO_GREASE_PENCIL 2144
#define GEO_NODE_GREASE_PENCIL_TO_CURVES 2145
#define GEO_NODE_IMPORT_PLY 2146
#define GEO_NODE_WARNING 2147
/** \} */

View File

@@ -1278,6 +1278,9 @@ class NodeTreeMainUpdater {
if (node.type == NODE_GROUP_OUTPUT) {
return true;
}
if (node.type == GEO_NODE_WARNING) {
return true;
}
if (nodes::gizmos::is_builtin_gizmo_node(node)) {
return true;
}

View File

@@ -439,6 +439,7 @@ std::unique_ptr<LazyFunction> get_bake_lazy_function(
std::unique_ptr<LazyFunction> get_menu_switch_node_lazy_function(
const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info);
std::unique_ptr<LazyFunction> get_menu_switch_node_socket_usage_lazy_function(const bNode &node);
std::unique_ptr<LazyFunction> get_warning_node_lazy_function(const bNode &node);
/**
* Outputs the default value of each output socket that has not been output yet. This needs the

View File

@@ -52,10 +52,11 @@ namespace blender::nodes::geo_eval_log {
using fn::GField;
/** These values are also written to .blend files, so don't change them lightly. */
enum class NodeWarningType {
Error,
Warning,
Info,
Error = 0,
Warning = 1,
Info = 2,
};
struct NodeWarning {

View File

@@ -501,6 +501,7 @@ DefNode(GeometryNode, GEO_NODE_UV_UNWRAP, 0, "UV_UNWRAP", UVUnwrap, "UV Unwrap",
DefNode(GeometryNode, GEO_NODE_VIEWER, 0, "VIEWER", Viewer, "Viewer", "Display the input data in the Spreadsheet Editor")
DefNode(GeometryNode, GEO_NODE_VOLUME_CUBE, 0, "VOLUME_CUBE", VolumeCube, "Volume Cube", "Generate a dense volume with a field that controls the density at each grid voxel based on its position")
DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, 0, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "Generate a mesh on the \"surface\" of a volume")
DefNode(GeometryNode, GEO_NODE_WARNING, 0, "WARNING", Warning, "Warning", "Create custom warnings in node groups")
/* undefine macros */
#undef DefNode

View File

@@ -226,6 +226,7 @@ set(SRC
nodes/node_geo_viewport_transform.cc
nodes/node_geo_volume_cube.cc
nodes/node_geo_volume_to_mesh.cc
nodes/node_geo_warning.cc
include/NOD_geo_bake.hh
include/NOD_geo_capture_attribute.hh

View File

@@ -0,0 +1,113 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
namespace blender::nodes::node_geo_warning_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_input<decl::Bool>("Show").default_value(true).hide_value();
b.add_output<decl::Bool>("Show").align_with_previous();
b.add_input<decl::String>("Message").hide_label();
}
class LazyFunctionForWarningNode : public LazyFunction {
const bNode &node_;
public:
LazyFunctionForWarningNode(const bNode &node) : node_(node)
{
const CPPType &type = CPPType::get<SocketValueVariant>();
inputs_.append_as("Show", type, lf::ValueUsage::Used);
inputs_.append_as("Message", type);
outputs_.append_as("Show", type);
}
void execute_impl(lf::Params &params, const lf::Context &context) const override
{
const SocketValueVariant show_variant = params.get_input<SocketValueVariant>(0);
const bool show = show_variant.get<bool>();
if (!show) {
params.set_output(0, show_variant);
return;
}
SocketValueVariant *message_variant =
params.try_get_input_data_ptr_or_request<SocketValueVariant>(1);
if (!message_variant) {
/* Wait for the message to be computed. */
return;
}
std::string message = message_variant->extract<std::string>();
GeoNodesLFUserData &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
GeoNodesLFLocalUserData &local_user_data = *static_cast<GeoNodesLFLocalUserData *>(
context.local_user_data);
if (geo_eval_log::GeoTreeLogger *tree_logger = local_user_data.try_get_tree_logger(user_data))
{
tree_logger->node_warnings.append(
*tree_logger->allocator,
{node_.identifier, {NodeWarningType(node_.custom1), std::move(message)}});
}
/* Only set output in the end so that this node is not finished before the warning is set. */
params.set_output(0, show_variant);
}
};
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiItemR(layout, ptr, "warning_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_rna(StructRNA *srna)
{
static EnumPropertyItem warning_type_items[] = {
{int(NodeWarningType::Error), "ERROR", 0, "Error", ""},
{int(NodeWarningType::Warning), "WARNING", 0, "Warning", ""},
{int(NodeWarningType::Info), "INFO", 0, "Info", ""},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"warning_type",
"Warning Type",
"",
warning_type_items,
NOD_inline_enum_accessors(custom1));
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_WARNING, "Warning", NODE_CLASS_INTERFACE);
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
blender::bke::node_register_type(&ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_warning_cc
namespace blender::nodes {
std::unique_ptr<LazyFunction> get_warning_node_lazy_function(const bNode &node)
{
using namespace node_geo_warning_cc;
BLI_assert(node.type == GEO_NODE_WARNING);
return std::make_unique<LazyFunctionForWarningNode>(node);
}
} // namespace blender::nodes

View File

@@ -1598,6 +1598,8 @@ struct BuildGraphParams {
/** */
/** Cache to avoid building the same socket combinations multiple times. */
Map<Vector<lf::OutputSocket *>, lf::OutputSocket *> socket_usages_combination_cache;
BuildGraphParams(lf::Graph &lf_graph) : lf_graph(lf_graph) {}
};
struct ZoneFunctionIndices {
@@ -2181,7 +2183,16 @@ class GeometryNodesLazyFunctionLogger : public lf::GraphExecutor::Logger {
* another (depending on e.g. which tree path is currently viewed in the node editor).
*/
class GeometryNodesLazyFunctionSideEffectProvider : public lf::GraphExecutor::SideEffectProvider {
private:
Span<const lf::FunctionNode *> local_side_effect_nodes_;
public:
GeometryNodesLazyFunctionSideEffectProvider(
Span<const lf::FunctionNode *> local_side_effect_nodes = {})
: local_side_effect_nodes_(local_side_effect_nodes)
{
}
Vector<const lf::FunctionNode *> get_nodes_with_side_effects(
const lf::Context &context) const override
{
@@ -2192,7 +2203,10 @@ class GeometryNodesLazyFunctionSideEffectProvider : public lf::GraphExecutor::Si
return {};
}
const ComputeContextHash &context_hash = user_data->compute_context->hash();
return call_data.side_effect_nodes->nodes_by_context.lookup(context_hash);
Vector<const lf::FunctionNode *> side_effect_nodes =
call_data.side_effect_nodes->nodes_by_context.lookup(context_hash);
side_effect_nodes.extend(local_side_effect_nodes_);
return side_effect_nodes;
}
};
@@ -2219,6 +2233,8 @@ struct GeometryNodesLazyFunctionBuilder {
const bNodeTreeZones *tree_zones_;
MutableSpan<ZoneBuildInfo> zone_build_infos_;
std::optional<BuildGraphParams> root_graph_build_params_;
/**
* The inputs sockets in the graph. Multiple group input nodes are combined into one in the
* lazy-function graph.
@@ -2233,7 +2249,7 @@ struct GeometryNodesLazyFunctionBuilder {
* Interface boolean sockets that have to be passed in from the outside and indicate whether a
* specific output will be used.
*/
Vector<const lf::GraphInputSocket *> group_output_used_sockets_;
Vector<lf::GraphInputSocket *> group_output_used_sockets_;
/**
* Interface boolean sockets that can be used as group output that indicate whether a specific
* input will be used (this may depend on the used outputs as well as other inputs).
@@ -2720,7 +2736,7 @@ struct GeometryNodesLazyFunctionBuilder {
lf_output_usages.append(&lf_socket);
}
BuildGraphParams graph_params{lf_graph};
BuildGraphParams &graph_params = root_graph_build_params_.emplace(lf_graph);
if (const bNode *group_output_bnode = btree_.group_output_node()) {
for (const bNodeSocket *bsocket : group_output_bnode->input_sockets().drop_back(1)) {
graph_params.usage_by_bsocket.add(bsocket, lf_output_usages[bsocket->index()]);
@@ -2790,12 +2806,33 @@ struct GeometryNodesLazyFunctionBuilder {
function.outputs.input_usages = lf_graph_outputs.index_range().take_back(
group_input_usage_sockets_.size());
Vector<const lf::FunctionNode *> &local_side_effect_nodes =
scope_.construct<Vector<const lf::FunctionNode *>>();
for (const bNode *bnode : btree_.nodes_by_type("GeometryNodeWarning")) {
if (bnode->output_socket(0).is_directly_linked()) {
/* The warning node is not a side-effect node. Instead, the user explicitly used the output
* socket to specify when the warning node should be used. */
continue;
}
if (tree_zones_->get_zone_by_node(bnode->identifier)) {
/* "Global" warning nodes that are evaluated whenever the node group is evaluated must not
* be in a zone. */
continue;
}
/* Add warning node as side-effect node so that it is always evaluated if the node group is
* evaluated. */
const lf::Socket *lf_socket = root_graph_build_params_->lf_inputs_by_bsocket.lookup(
&bnode->input_socket(0))[0];
const lf::FunctionNode &lf_node = static_cast<const lf::FunctionNode &>(lf_socket->node());
local_side_effect_nodes.append(&lf_node);
}
function.function = &scope_.construct<lf::GraphExecutor>(
lf_graph_info_->graph,
std::move(lf_graph_inputs),
std::move(lf_graph_outputs),
&scope_.construct<GeometryNodesLazyFunctionLogger>(*lf_graph_info_),
&scope_.construct<GeometryNodesLazyFunctionSideEffectProvider>(),
&scope_.construct<GeometryNodesLazyFunctionSideEffectProvider>(local_side_effect_nodes),
nullptr);
}
@@ -3237,6 +3274,10 @@ struct GeometryNodesLazyFunctionBuilder {
this->build_index_switch_node(bnode, graph_params);
break;
}
case GEO_NODE_WARNING: {
this->build_warning_node(bnode, graph_params);
break;
}
case GEO_NODE_GIZMO_LINEAR:
case GEO_NODE_GIZMO_DIAL:
case GEO_NODE_GIZMO_TRANSFORM: {
@@ -3894,6 +3935,45 @@ struct GeometryNodesLazyFunctionBuilder {
}
}
void build_warning_node(const bNode &bnode, BuildGraphParams &graph_params)
{
auto lazy_function_ptr = get_warning_node_lazy_function(bnode);
LazyFunction &lazy_function = *lazy_function_ptr;
scope_.add(std::move(lazy_function_ptr));
lf::Node &lf_node = graph_params.lf_graph.add_function(lazy_function);
for (const int i : bnode.input_sockets().index_range()) {
const bNodeSocket &bsocket = bnode.input_socket(i);
lf::InputSocket &lf_socket = lf_node.input(i);
graph_params.lf_inputs_by_bsocket.add(&bsocket, &lf_socket);
mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket);
}
for (const int i : bnode.output_sockets().index_range()) {
const bNodeSocket &bsocket = bnode.output_socket(i);
lf::OutputSocket &lf_socket = lf_node.output(i);
graph_params.lf_output_by_bsocket.add(&bsocket, &lf_socket);
mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket);
}
const bNodeSocket &output_bsocket = bnode.output_socket(0);
lf::OutputSocket *lf_usage = nullptr;
if (output_bsocket.is_directly_linked()) {
/* The warning node is only used if the output socket is used. */
lf_usage = graph_params.usage_by_bsocket.lookup_default(&output_bsocket, nullptr);
}
else {
/* The warning node is used if any of the output sockets is used. */
lf_usage = this->or_socket_usages(group_output_used_sockets_, graph_params);
}
if (lf_usage) {
for (const bNodeSocket *socket : bnode.input_sockets()) {
graph_params.usage_by_bsocket.add(socket, lf_usage);
}
}
}
void build_menu_switch_node(const bNode &bnode, BuildGraphParams &graph_params)
{
std::unique_ptr<LazyFunction> lazy_function = get_menu_switch_node_lazy_function(