Nodes: add node group description

This allows node groups to have a description that is shown in the add menu
or when hovering over the node header.

This new description is stored in `bNodeTree.description`. Unfortunately, it
conflicts a bit with `ID.asset_data.description`. The difference is that the latter
only exists for assets. However, it makes sense for node groups to have
descriptions even if they are not assets (just like `static` functions in C++ should
also be able to have comments). In some cases, node groups are also generated
by addons for a specific purpose. Those should still have a description without
being reusable to make it easier to understand for users.

The solution here is to use the asset description if the node group is an asset,
and to use `bNodeTree.description` otherwise. The description is synced
automatically when marking or clearing assets.

A side benefit of this solution is that appended node group assets can keep their
description, which is currently always lost.

Pull Request: https://projects.blender.org/blender/blender/pulls/121334
This commit is contained in:
Jacques Lucke 2024-05-08 11:25:00 +02:00
parent d66598c16f
commit 6176e66636
17 changed files with 105 additions and 3 deletions

@ -27,6 +27,8 @@ from bpy.app.translations import (
pgettext_data as data_,
)
from nodeitems_builtins import node_tree_group_type
class NodeSetting(PropertyGroup):
value: StringProperty(
@ -151,6 +153,12 @@ class NODE_OT_add_node(NodeAddOperator, Operator):
@classmethod
def description(cls, _context, properties):
nodetype = properties["type"]
if nodetype in node_tree_group_type.values():
for setting in properties.settings:
if setting.name == "node_tree":
node_group = eval(setting.value)
if node_group.description:
return node_group.description
bl_rna = bpy.types.Node.bl_rna_get_subclass(nodetype)
if bl_rna is not None:
return tip_(bl_rna.description)

@ -990,6 +990,11 @@ class NODE_PT_node_tree_properties(Panel):
col.prop(group, "is_modifier")
col.prop(group, "is_tool")
if group.asset_data:
layout.prop(group.asset_data, "description", text="Group Description")
else:
layout.prop(group, "description", text="Group Description")
# Grease Pencil properties
class NODE_PT_annotation(AnnotationDataPanel, Panel):

@ -24,6 +24,7 @@ struct PreviewImage;
using PreSaveFn = void (*)(void *asset_ptr, AssetMetaData *asset_data);
using OnMarkAssetFn = void (*)(void *asset_ptr, AssetMetaData *asset_data);
using OnClearAssetDataFn = void (*)(void *asset_ptr, AssetMetaData *asset_data);
struct AssetTypeInfo {
/**
@ -32,6 +33,11 @@ struct AssetTypeInfo {
*/
PreSaveFn pre_save_fn;
OnMarkAssetFn on_mark_asset_fn;
/**
* Should be called whenever a local asset gets cleared of its asset data but stays available
* otherwise, i.e. when an asset data-block is turned back into a normal data-block.
*/
OnClearAssetDataFn on_clear_asset_fn;
};
AssetMetaData *BKE_asset_metadata_create();

@ -236,6 +236,8 @@ struct bNodeType {
/** Optional override for node class, used for drawing node header. */
int (*ui_class)(const bNode *node);
/** Optional dynamic description of what the node group does. */
std::string (*ui_description_fn)(const bNode &node);
/** Called when the node is updated in the editor. */
void (*updatefunc)(bNodeTree *ntree, bNode *node);

@ -265,6 +265,7 @@ static void action_asset_metadata_ensure(void *asset_ptr, AssetMetaData *asset_d
static AssetTypeInfo AssetType_AC = {
/*pre_save_fn*/ action_asset_metadata_ensure,
/*on_mark_asset_fn*/ action_asset_metadata_ensure,
/*on_clear_asset_fn*/ nullptr,
};
IDTypeInfo IDType_ID_AC = {

@ -219,6 +219,12 @@ void BKE_lib_id_clear_library_data(Main *bmain, ID *id, const int flags)
if (ID_IS_ASSET(id)) {
if ((flags & LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR) != 0) {
const IDTypeInfo *idtype_info = BKE_idtype_get_info_from_id(id);
if (idtype_info && idtype_info->asset_type_info &&
idtype_info->asset_type_info->on_clear_asset_fn)
{
idtype_info->asset_type_info->on_clear_asset_fn(id, id->asset_data);
}
BKE_asset_metadata_free(&id->asset_data);
}
else {

@ -755,6 +755,7 @@ static void write_node_socket(BlendWriter *writer, const bNodeSocket *sock)
void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
{
BKE_id_blend_write(writer, &ntree->id);
BLO_write_string(writer, ntree->description);
for (bNode *node : ntree->all_nodes()) {
if (ntree->type == NTREE_SHADER && node->type == SH_NODE_BSDF_HAIR_PRINCIPLED) {
@ -1044,6 +1045,8 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree)
ntree->runtime = MEM_new<bNodeTreeRuntime>(__func__);
BKE_ntree_update_tag_missing_runtime_data(ntree);
BLO_read_string(reader, &ntree->description);
BLO_read_struct_list(reader, bNode, &ntree->nodes);
int i;
LISTBASE_FOREACH_INDEX (bNode *, node, &ntree->nodes, i) {
@ -1288,12 +1291,32 @@ void node_update_asset_metadata(bNodeTree &node_tree)
static void node_tree_asset_pre_save(void *asset_ptr, AssetMetaData * /*asset_data*/)
{
node_update_asset_metadata(*static_cast<bNodeTree *>(asset_ptr));
bNodeTree &ntree = *static_cast<bNodeTree *>(asset_ptr);
node_update_asset_metadata(ntree);
}
static void node_tree_asset_on_mark_asset(void *asset_ptr, AssetMetaData * /*asset_data*/)
static void node_tree_asset_on_mark_asset(void *asset_ptr, AssetMetaData *asset_data)
{
node_update_asset_metadata(*static_cast<bNodeTree *>(asset_ptr));
bNodeTree &ntree = *static_cast<bNodeTree *>(asset_ptr);
node_update_asset_metadata(ntree);
/* Copy node tree description to asset description so that the user does not have to write it
* again. */
if (!asset_data->description) {
asset_data->description = BLI_strdup_null(ntree.description);
}
}
static void node_tree_asset_on_clear_asset(void *asset_ptr, AssetMetaData *asset_data)
{
bNodeTree &ntree = *static_cast<bNodeTree *>(asset_ptr);
/* Copy asset description to node tree description so that it is not lost when the asset data is
* removed. */
if (asset_data->description) {
MEM_SAFE_FREE(ntree.description);
ntree.description = BLI_strdup_null(asset_data->description);
}
}
} // namespace blender::bke
@ -1301,6 +1324,7 @@ static void node_tree_asset_on_mark_asset(void *asset_ptr, AssetMetaData * /*ass
static AssetTypeInfo AssetType_NT = {
/*pre_save_fn*/ blender::bke::node_tree_asset_pre_save,
/*on_mark_asset_fn*/ blender::bke::node_tree_asset_on_mark_asset,
/*on_clear_asset_fn*/ blender::bke::node_tree_asset_on_clear_asset,
};
IDTypeInfo IDType_ID_NT = {

@ -1064,6 +1064,7 @@ static void object_asset_metadata_ensure(void *asset_ptr, AssetMetaData *asset_d
static AssetTypeInfo AssetType_OB = {
/*pre_save_fn*/ object_asset_metadata_ensure,
/*on_mark_asset_fn*/ object_asset_metadata_ensure,
/*on_clear_asset_fn*/ nullptr,
};
IDTypeInfo IDType_ID_OB = {

@ -69,6 +69,14 @@ bool clear_id(ID *id)
if (!id->asset_data) {
return false;
}
const IDTypeInfo *id_type_info = BKE_idtype_get_info_from_id(id);
if (AssetTypeInfo *type_info = id_type_info->asset_type_info) {
if (type_info->on_clear_asset_fn) {
type_info->on_clear_asset_fn(id, id->asset_data);
}
}
BKE_asset_metadata_free(&id->asset_data);
id_fake_user_clear(id);

@ -3448,6 +3448,17 @@ static void node_draw_basis(const bContext &C,
0,
0,
TIP_(node.typeinfo->ui_description));
UI_but_func_tooltip_set(
but,
[](bContext * /*C*/, void *arg, const char *tip) -> std::string {
const bNode &node = *static_cast<const bNode *>(arg);
if (node.typeinfo->ui_description_fn) {
return node.typeinfo->ui_description_fn(node);
}
return StringRef(tip);
},
const_cast<bNode *>(&node),
nullptr);
if (node.flag & NODE_MUTED) {
UI_but_flag_enable(but, UI_BUT_INACTIVE);

@ -663,6 +663,8 @@ typedef struct bNodeTree {
struct bNodeTreeType *typeinfo;
/** Runtime type identifier. */
char idname[64];
/** User-defined description of the node tree. */
char *description;
/** Grease pencil data. */
struct bGPdata *gpd;

@ -10372,6 +10372,9 @@ static void rna_def_nodetree(BlenderRNA *brna)
prop, "", "The current location (offset) of the view for this Node Tree");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
prop = RNA_def_property(srna, "description", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(prop, "Description", "Description of the node tree");
/* AnimData */
rna_def_animdata_common(srna);

@ -27,6 +27,7 @@ void register_node_type_cmp_group()
ntype.poll = cmp_node_poll_default;
ntype.poll_instance = node_group_poll_instance;
ntype.insert_link = node_insert_link_default;
ntype.ui_description_fn = node_group_ui_description;
ntype.rna_ext.srna = RNA_struct_find("CompositorNodeGroup");
BLI_assert(ntype.rna_ext.srna != nullptr);
RNA_struct_blender_type_set(ntype.rna_ext.srna, &ntype);

@ -24,6 +24,7 @@ static void register_node_type_geo_group()
ntype.poll = geo_node_poll_default;
ntype.poll_instance = node_group_poll_instance;
ntype.insert_link = node_insert_link_default;
ntype.ui_description_fn = node_group_ui_description;
ntype.rna_ext.srna = RNA_struct_find("GeometryNodeGroup");
BLI_assert(ntype.rna_ext.srna != nullptr);
RNA_struct_blender_type_set(ntype.rna_ext.srna, &ntype);

@ -9,6 +9,7 @@
#include <cstddef>
#include <cstring>
#include "DNA_asset_types.h"
#include "DNA_node_types.h"
#include "BLI_listbase.h"
@ -93,6 +94,23 @@ bool node_group_poll_instance(const bNode *node,
return nodeGroupPoll(nodetree, grouptree, disabled_hint);
}
std::string node_group_ui_description(const bNode &node)
{
if (!node.id) {
return "";
}
const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node.id);
if (group->id.asset_data) {
if (group->id.asset_data->description) {
return group->id.asset_data->description;
}
}
if (!group->description) {
return "";
}
return group->description;
}
bool nodeGroupPoll(const bNodeTree *nodetree,
const bNodeTree *grouptree,
const char **r_disabled_hint)

@ -33,4 +33,8 @@ void ntree_update_reroute_nodes(struct bNodeTree *ntree);
#ifdef __cplusplus
}
# include <string>
std::string node_group_ui_description(const bNode &node);
#endif

@ -91,6 +91,7 @@ void register_node_type_sh_group()
ntype.poll = sh_node_poll_default;
ntype.poll_instance = node_group_poll_instance;
ntype.insert_link = node_insert_link_default;
ntype.ui_description_fn = node_group_ui_description;
ntype.rna_ext.srna = RNA_struct_find("ShaderNodeGroup");
BLI_assert(ntype.rna_ext.srna != nullptr);
RNA_struct_blender_type_set(ntype.rna_ext.srna, &ntype);