From 6176e66636342dabbd78594ecfb188c8a4bc996c Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 8 May 2024 11:25:00 +0200 Subject: [PATCH] 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 --- scripts/startup/bl_operators/node.py | 8 +++++ scripts/startup/bl_ui/space_node.py | 5 ++++ source/blender/blenkernel/BKE_asset.hh | 6 ++++ source/blender/blenkernel/BKE_node.hh | 2 ++ source/blender/blenkernel/intern/action.cc | 1 + source/blender/blenkernel/intern/lib_id.cc | 6 ++++ source/blender/blenkernel/intern/node.cc | 30 +++++++++++++++++-- source/blender/blenkernel/intern/object.cc | 1 + .../editors/asset/intern/asset_mark_clear.cc | 8 +++++ .../blender/editors/space_node/node_draw.cc | 11 +++++++ source/blender/makesdna/DNA_node_types.h | 2 ++ .../blender/makesrna/intern/rna_nodetree.cc | 3 ++ .../composite/nodes/node_composite_common.cc | 1 + .../nodes/geometry/nodes/node_geo_common.cc | 1 + source/blender/nodes/intern/node_common.cc | 18 +++++++++++ source/blender/nodes/intern/node_common.h | 4 +++ .../nodes/shader/nodes/node_shader_common.cc | 1 + 17 files changed, 105 insertions(+), 3 deletions(-) diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index 6a240f21bb4..531fca6b339 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -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) diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index d5361491dfb..3f1fcdb2a9e 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -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): diff --git a/source/blender/blenkernel/BKE_asset.hh b/source/blender/blenkernel/BKE_asset.hh index 0b6eda3345c..56529bbf738 100644 --- a/source/blender/blenkernel/BKE_asset.hh +++ b/source/blender/blenkernel/BKE_asset.hh @@ -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(); diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index ebb72b975d3..609ef7a7093 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -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); diff --git a/source/blender/blenkernel/intern/action.cc b/source/blender/blenkernel/intern/action.cc index e4e1b6565bd..463b15edf8c 100644 --- a/source/blender/blenkernel/intern/action.cc +++ b/source/blender/blenkernel/intern/action.cc @@ -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 = { diff --git a/source/blender/blenkernel/intern/lib_id.cc b/source/blender/blenkernel/intern/lib_id.cc index fc71bb22f5d..f41801a16fc 100644 --- a/source/blender/blenkernel/intern/lib_id.cc +++ b/source/blender/blenkernel/intern/lib_id.cc @@ -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 { diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index c26e8e96e6e..0f65d183e27 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -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(__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(asset_ptr)); + bNodeTree &ntree = *static_cast(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(asset_ptr)); + bNodeTree &ntree = *static_cast(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(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 = { diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index 9dc7d91281e..49bb0c0bea0 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -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 = { diff --git a/source/blender/editors/asset/intern/asset_mark_clear.cc b/source/blender/editors/asset/intern/asset_mark_clear.cc index 9fcfc8e7a5e..18478bd32f2 100644 --- a/source/blender/editors/asset/intern/asset_mark_clear.cc +++ b/source/blender/editors/asset/intern/asset_mark_clear.cc @@ -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); diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 66286e0894f..e3668b2f364 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -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(arg); + if (node.typeinfo->ui_description_fn) { + return node.typeinfo->ui_description_fn(node); + } + return StringRef(tip); + }, + const_cast(&node), + nullptr); if (node.flag & NODE_MUTED) { UI_but_flag_enable(but, UI_BUT_INACTIVE); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 0f4afd05b4f..9008852b8cd 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -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; diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 5a5279a5aa0..8b22ca75fc7 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -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); diff --git a/source/blender/nodes/composite/nodes/node_composite_common.cc b/source/blender/nodes/composite/nodes/node_composite_common.cc index d326f115f01..af124159c0b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_common.cc +++ b/source/blender/nodes/composite/nodes/node_composite_common.cc @@ -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); diff --git a/source/blender/nodes/geometry/nodes/node_geo_common.cc b/source/blender/nodes/geometry/nodes/node_geo_common.cc index 84d6c693e4f..c6c846fafae 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_common.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_common.cc @@ -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); diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index 3674c1d68c1..0c0548d4833 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -9,6 +9,7 @@ #include #include +#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(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) diff --git a/source/blender/nodes/intern/node_common.h b/source/blender/nodes/intern/node_common.h index 0779f0132f7..92a039dd157 100644 --- a/source/blender/nodes/intern/node_common.h +++ b/source/blender/nodes/intern/node_common.h @@ -33,4 +33,8 @@ void ntree_update_reroute_nodes(struct bNodeTree *ntree); #ifdef __cplusplus } + +# include + +std::string node_group_ui_description(const bNode &node); #endif diff --git a/source/blender/nodes/shader/nodes/node_shader_common.cc b/source/blender/nodes/shader/nodes/node_shader_common.cc index c21b6daf0be..cff6980934c 100644 --- a/source/blender/nodes/shader/nodes/node_shader_common.cc +++ b/source/blender/nodes/shader/nodes/node_shader_common.cc @@ -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);