Geometry Nodes: show used named attributes in nodes

This adds a new node editor overlay that helps users to see where
named attributes are used. This is important, because named
attributes can have name collisions between independent node
groups which can lead to hard to find issues.

Differential Revision: https://developer.blender.org/D14618
This commit is contained in:
Jacques Lucke 2022-04-14 16:31:09 +02:00
parent a9b94e5f81
commit c71013082d
Notes: blender-bot 2023-02-14 03:31:57 +01:00
Referenced by issue #96275, Named attribute node group visualization
12 changed files with 230 additions and 18 deletions

View File

@ -709,6 +709,7 @@ class NODE_PT_overlay(Panel):
if snode.tree_type == 'GeometryNodeTree':
col.separator()
col.prop(overlay, "show_timing", text="Timings")
col.prop(overlay, "show_named_attributes", text="Named Attributes")
class NODE_UL_interface_sockets(bpy.types.UIList):

View File

@ -2677,5 +2677,17 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
*/
{
/* Keep this block, even when empty. */
/* Enable named attributes overlay in node editor. */
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, space, &area->spacedata) {
if (space->spacetype == SPACE_NODE) {
SpaceNode *snode = (SpaceNode *)space;
snode->overlay.flag |= SN_OVERLAY_SHOW_NAMED_ATTRIBUTES;
}
}
}
}
}
}

View File

@ -75,6 +75,7 @@
using blender::GPointer;
using blender::fn::GField;
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
using geo_log::NamedAttributeUsage;
extern "C" {
/* XXX interface.h */
@ -1684,10 +1685,127 @@ static std::string node_get_execution_time_label(const SpaceNode &snode, const b
struct NodeExtraInfoRow {
std::string text;
const char *tooltip;
int icon;
const char *tooltip = nullptr;
uiButToolTipFunc tooltip_fn = nullptr;
void *tooltip_fn_arg = nullptr;
void (*tooltip_fn_free_arg)(void *) = nullptr;
};
struct NamedAttributeTooltipArg {
Map<std::string, NamedAttributeUsage> usage_by_attribute;
};
static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char *UNUSED(tip))
{
NamedAttributeTooltipArg &arg = *static_cast<NamedAttributeTooltipArg *>(argN);
std::stringstream ss;
ss << TIP_("Accessed attribute names:\n");
Vector<std::pair<StringRefNull, NamedAttributeUsage>> sorted_used_attribute;
for (auto &&item : arg.usage_by_attribute.items()) {
sorted_used_attribute.append({item.key, item.value});
}
std::sort(sorted_used_attribute.begin(), sorted_used_attribute.end());
for (const std::pair<StringRefNull, NamedAttributeUsage> &attribute : sorted_used_attribute) {
const StringRefNull name = attribute.first;
const NamedAttributeUsage usage = attribute.second;
ss << " \u2022 \"" << name << "\": ";
Vector<std::string> usages;
if ((usage & NamedAttributeUsage::Read) != NamedAttributeUsage::None) {
usages.append(TIP_("read"));
}
if ((usage & NamedAttributeUsage::Write) != NamedAttributeUsage::None) {
usages.append(TIP_("write"));
}
if ((usage & NamedAttributeUsage::Remove) != NamedAttributeUsage::None) {
usages.append(TIP_("remove"));
}
for (const int i : usages.index_range()) {
ss << usages[i];
if (i < usages.size() - 1) {
ss << ", ";
}
}
ss << "\n";
}
ss << "\n";
ss << TIP_(
"Attributes with these names used within the group may conflict with existing attributes");
return BLI_strdup(ss.str().c_str());
}
static NodeExtraInfoRow row_from_used_named_attribute(
const Map<std::string, NamedAttributeUsage> &usage_by_attribute_name)
{
NodeExtraInfoRow row;
row.text = TIP_("Attributes");
row.icon = ICON_SPREADSHEET;
row.tooltip_fn = named_attribute_tooltip;
row.tooltip_fn_arg = new NamedAttributeTooltipArg{usage_by_attribute_name};
row.tooltip_fn_free_arg = [](void *arg) { delete static_cast<NamedAttributeTooltipArg *>(arg); };
return row;
}
static std::optional<NodeExtraInfoRow> node_get_accessed_attributes_row(const SpaceNode &snode,
const bNode &node)
{
if (node.type == NODE_GROUP) {
const geo_log::TreeLog *root_tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context(
snode);
if (root_tree_log == nullptr) {
return std::nullopt;
}
const geo_log::TreeLog *tree_log = root_tree_log->lookup_child_log(node.name);
if (tree_log == nullptr) {
return std::nullopt;
}
Map<std::string, NamedAttributeUsage> usage_by_attribute;
tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) {
for (const geo_log::UsedNamedAttribute &used_attribute : node_log.used_named_attributes()) {
usage_by_attribute.lookup_or_add_as(used_attribute.name,
used_attribute.usage) |= used_attribute.usage;
}
});
if (usage_by_attribute.is_empty()) {
return std::nullopt;
}
return row_from_used_named_attribute(usage_by_attribute);
}
if (ELEM(node.type,
GEO_NODE_STORE_NAMED_ATTRIBUTE,
GEO_NODE_REMOVE_ATTRIBUTE,
GEO_NODE_INPUT_NAMED_ATTRIBUTE)) {
/* Only show the overlay when the name is passed in from somewhere else. */
LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) {
if (STREQ(socket->name, "Name")) {
if ((socket->flag & SOCK_IN_USE) == 0) {
return std::nullopt;
}
}
}
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(
snode, node.name);
if (node_log == nullptr) {
return std::nullopt;
}
Map<std::string, NamedAttributeUsage> usage_by_attribute;
for (const geo_log::UsedNamedAttribute &used_attribute : node_log->used_named_attributes()) {
usage_by_attribute.lookup_or_add_as(used_attribute.name,
used_attribute.usage) |= used_attribute.usage;
}
if (usage_by_attribute.is_empty()) {
return std::nullopt;
}
return row_from_used_named_attribute(usage_by_attribute);
}
return std::nullopt;
}
static Vector<NodeExtraInfoRow> node_get_extra_info(const SpaceNode &snode, const bNode &node)
{
Vector<NodeExtraInfoRow> rows;
@ -1718,6 +1836,14 @@ static Vector<NodeExtraInfoRow> node_get_extra_info(const SpaceNode &snode, cons
rows.append(std::move(row));
}
}
if (snode.overlay.flag & SN_OVERLAY_SHOW_NAMED_ATTRIBUTES &&
snode.edittree->type == NTREE_GEOMETRY) {
if (std::optional<NodeExtraInfoRow> row = node_get_accessed_attributes_row(snode, node)) {
rows.append(std::move(*row));
}
}
return rows;
}
@ -1727,20 +1853,20 @@ static void node_draw_extra_info_row(const bNode &node,
const int row,
const NodeExtraInfoRow &extra_info_row)
{
uiBut *but_timing = uiDefBut(&block,
UI_BTYPE_LABEL,
0,
extra_info_row.text.c_str(),
(int)(rect.xmin + 4.0f * U.dpi_fac + NODE_MARGIN_X + 0.4f),
(int)(rect.ymin + row * (20.0f * U.dpi_fac)),
(short)(rect.xmax - rect.xmin),
(short)NODE_DY,
nullptr,
0,
0,
0,
0,
"");
uiBut *but_text = uiDefBut(&block,
UI_BTYPE_LABEL,
0,
extra_info_row.text.c_str(),
(int)(rect.xmin + 4.0f * U.dpi_fac + NODE_MARGIN_X + 0.4f),
(int)(rect.ymin + row * (20.0f * U.dpi_fac)),
(short)(rect.xmax - rect.xmin),
(short)NODE_DY,
nullptr,
0,
0,
0,
0,
"");
UI_block_emboss_set(&block, UI_EMBOSS_NONE);
uiBut *but_icon = uiDefIconBut(&block,
UI_BTYPE_BUT,
@ -1756,9 +1882,15 @@ static void node_draw_extra_info_row(const bNode &node,
0,
0,
extra_info_row.tooltip);
if (extra_info_row.tooltip_fn != NULL) {
UI_but_func_tooltip_set(but_icon,
extra_info_row.tooltip_fn,
extra_info_row.tooltip_fn_arg,
extra_info_row.tooltip_fn_free_arg);
}
UI_block_emboss_set(&block, UI_EMBOSS);
if (node.flag & NODE_MUTED) {
UI_but_flag_enable(but_timing, UI_BUT_INACTIVE);
UI_but_flag_enable(but_text, UI_BUT_INACTIVE);
UI_but_flag_enable(but_icon, UI_BUT_INACTIVE);
}
}

View File

@ -1513,6 +1513,7 @@ typedef enum eSpaceNodeOverlay_Flag {
SN_OVERLAY_SHOW_WIRE_COLORS = (1 << 2),
SN_OVERLAY_SHOW_TIMINGS = (1 << 3),
SN_OVERLAY_SHOW_PATH = (1 << 4),
SN_OVERLAY_SHOW_NAMED_ATTRIBUTES = (1 << 5),
} eSpaceNodeOverlay_Flag;
typedef struct SpaceNode {

View File

@ -7143,6 +7143,13 @@ static void rna_def_space_node_overlay(BlenderRNA *brna)
RNA_def_property_boolean_default(prop, true);
RNA_def_property_ui_text(prop, "Show Tree Path", "Display breadcrumbs for the editor's context");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_NODE, NULL);
prop = RNA_def_property(srna, "show_named_attributes", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlay.flag", SN_OVERLAY_SHOW_NAMED_ATTRIBUTES);
RNA_def_property_boolean_default(prop, true);
RNA_def_property_ui_text(
prop, "Show Named Attributes", "Show when nodes are using named attributes");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_NODE, NULL);
}
static void rna_def_space_node(BlenderRNA *brna)

View File

@ -40,6 +40,7 @@ using fn::FieldInput;
using fn::FieldOperation;
using fn::GField;
using fn::ValueOrField;
using geometry_nodes_eval_log::NamedAttributeUsage;
using geometry_nodes_eval_log::NodeWarningType;
/**
@ -342,6 +343,8 @@ class GeoNodeExecParams {
void set_default_remaining_outputs();
void used_named_attribute(std::string attribute_name, NamedAttributeUsage usage);
private:
/* Utilities for detecting common errors at when using this class. */
void check_input_access(StringRef identifier, const CPPType *requested_type = nullptr) const;

View File

@ -170,6 +170,24 @@ struct ValueOfSockets {
destruct_ptr<ValueLog> value;
};
enum class NamedAttributeUsage {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Remove = 1 << 2,
};
ENUM_OPERATORS(NamedAttributeUsage, NamedAttributeUsage::Remove);
struct UsedNamedAttribute {
std::string name;
NamedAttributeUsage usage;
};
struct NodeWithUsedNamedAttribute {
DNode node;
UsedNamedAttribute attribute;
};
class GeoLogger;
class ModifierLog;
@ -186,6 +204,7 @@ class LocalGeoLogger {
Vector<NodeWithWarning> node_warnings_;
Vector<NodeWithExecutionTime> node_exec_times_;
Vector<NodeWithDebugMessage> node_debug_messages_;
Vector<NodeWithUsedNamedAttribute> used_named_attributes_;
friend ModifierLog;
@ -199,6 +218,7 @@ class LocalGeoLogger {
void log_multi_value_socket(DSocket socket, Span<GPointer> values);
void log_node_warning(DNode node, NodeWarningType type, std::string message);
void log_execution_time(DNode node, std::chrono::microseconds exec_time);
void log_used_named_attribute(DNode node, std::string attribute_name, NamedAttributeUsage usage);
/**
* Log a message that will be displayed in the node editor next to the node.
* This should only be used for debugging purposes and not to display information to users.
@ -278,6 +298,7 @@ class NodeLog {
Vector<SocketLog> output_logs_;
Vector<NodeWarning, 0> warnings_;
Vector<std::string, 0> debug_messages_;
Vector<UsedNamedAttribute, 0> used_named_attributes_;
std::chrono::microseconds exec_time_;
friend ModifierLog;
@ -307,6 +328,11 @@ class NodeLog {
return debug_messages_;
}
Span<UsedNamedAttribute> used_named_attributes() const
{
return used_named_attributes_;
}
std::chrono::microseconds execution_time() const
{
return exec_time_;

View File

@ -81,11 +81,13 @@ static void node_geo_exec(GeoNodeExecParams params)
const std::string name = params.extract_input<std::string>("Name");
if (!U.experimental.use_named_attribute_nodes) {
if (!U.experimental.use_named_attribute_nodes || name.empty()) {
params.set_default_remaining_outputs();
return;
}
params.used_named_attribute(name, NamedAttributeUsage::Read);
switch (data_type) {
case CD_PROP_FLOAT:
params.set_output("Attribute_Float", AttributeFieldInput::Create<float>(std::move(name)));

View File

@ -32,6 +32,8 @@ static void node_geo_exec(GeoNodeExecParams params)
return;
}
params.used_named_attribute(name, NamedAttributeUsage::Remove);
std::atomic<bool> attribute_exists = false;
std::atomic<bool> cannot_delete = false;

View File

@ -127,11 +127,13 @@ static void node_geo_exec(GeoNodeExecParams params)
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
std::string name = params.extract_input<std::string>("Name");
if (!U.experimental.use_named_attribute_nodes) {
if (!U.experimental.use_named_attribute_nodes || name.empty()) {
params.set_output("Geometry", std::move(geometry_set));
return;
}
params.used_named_attribute(name, NamedAttributeUsage::Write);
const NodeGeometryStoreNamedAttribute &storage = node_storage(params.node());
const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type);
const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain);

View File

@ -62,6 +62,13 @@ ModifierLog::ModifierLog(GeoLogger &logger)
NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context, debug_message.node);
node_log.debug_messages_.append(debug_message.message);
}
for (NodeWithUsedNamedAttribute &node_with_attribute_name :
local_logger.used_named_attributes_) {
NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context,
node_with_attribute_name.node);
node_log.used_named_attributes_.append(std::move(node_with_attribute_name.attribute));
}
}
}
@ -486,6 +493,13 @@ void LocalGeoLogger::log_execution_time(DNode node, std::chrono::microseconds ex
node_exec_times_.append({node, exec_time});
}
void LocalGeoLogger::log_used_named_attribute(DNode node,
std::string attribute_name,
NamedAttributeUsage usage)
{
used_named_attributes_.append({node, {std::move(attribute_name), usage}});
}
void LocalGeoLogger::log_debug_message(DNode node, std::string message)
{
node_debug_messages_.append({node, std::move(message)});

View File

@ -23,6 +23,16 @@ void GeoNodeExecParams::error_message_add(const NodeWarningType type, std::strin
local_logger.log_node_warning(provider_->dnode, type, std::move(message));
}
void GeoNodeExecParams::used_named_attribute(std::string attribute_name,
const NamedAttributeUsage usage)
{
if (provider_->logger == nullptr) {
return;
}
LocalGeoLogger &local_logger = provider_->logger->local();
local_logger.log_used_named_attribute(provider_->dnode, std::move(attribute_name), usage);
}
void GeoNodeExecParams::check_input_geometry_set(StringRef identifier,
const GeometrySet &geometry_set) const
{