Nodes: Add node group assets to search menus

Currently node group assets are supported, but using them by dragging
from the asset browser is cumbersome. This patch adds all node group
assets from user asset libraries and the current file libraries to the
add node search menu and the link drag search menu.

Node groups added through the search will have their "options" hidden,
meaning the data-block selector is displayed. This helps keep the UI
clean, and the selector shouldn't be necessary anyway.

To make that possible, metadata like the node tree type and its inputs
and outputs has to be saved in the file. This requires re-saving the
files that contain the assets with the patch applied.

The node add search operator is moved from Python to C++ to ease
development and allow more flexibility. It supports a tooltip that
gives the description of assets.

Currently the node groups are added with the asset system's existing
"Append & Reuse" behavior. It's likely that linking should be possible
in the future too, but for now the idea is to use the more foolproof
option that doesn't create dependencies between files.

Because loading assets can potentially take a long time, the search
menu refreshes its items as new assets are loaded. However, changing
the search field is necessary to see the update.

Differential Revision: https://developer.blender.org/D15568
This commit is contained in:
Hans Goudey 2022-09-19 11:57:10 -05:00
parent 862de9187f
commit bdb5754147
Notes: blender-bot 2024-01-02 10:10:29 +01:00
Referenced by commit ed8fee16ac, Fix: Hide "Squeeze Value" node from node search
Referenced by commit cf98518055, Nodes: Add node group assets in add menu
Referenced by commit 7e980f2b8c, Fix T101249: Node groups don't show in node search
Referenced by issue #103108, Regression: NODE_OT_add_search ignores NodeItems entries
Referenced by issue #103047, Mix Color is not searchable
Referenced by issue #102555, Regression: Node editor Add menu - nodes gets created offset when adding from the Search operator
Referenced by issue #102118, Mix Color Node removed from search menu. T101700 follow up.
Referenced by issue #101424, Regression: Node search menu exposes an empty node group
Referenced by issue #101359, Search menu slow to load
Referenced by issue #101259, Geometry Nodes in searching pulls nodes from other node add-ons
Referenced by issue #101245, Regression: Node Editor: Crash On Typing or Scrolling In Add Search
Referenced by issue #101249, Regression: Nodegroups don't show up in node search anymore
Referenced by issue #95446, Display asset node groups in the add menu
Referenced by issue #95447, Display asset node groups in link-drag search
Referenced by issue #92825, Refactor node editor add menu and search
Referenced by issue #106138, Regression: Search in shader node editor exposes nodes that are not available in the current renderer
Referenced by issue #103768, Geometry Nodes + Shader Editor + Asset Browser bug
Referenced by issue #116571, Old node groups aren't working in asset browser when dragging and dropping them into GN
14 changed files with 613 additions and 83 deletions

View File

@ -149,78 +149,6 @@ class NODE_OT_add_node(NodeAddOperator, Operator):
bl_options = {'REGISTER', 'UNDO'}
class NODE_OT_add_search(NodeAddOperator, Operator):
'''Add a node to the active tree'''
bl_idname = "node.add_search"
bl_label = "Search and Add Node"
bl_options = {'REGISTER', 'UNDO'}
bl_property = "node_item"
_enum_item_hack = []
# Create an enum list from node items
def node_enum_items(self, context):
import nodeitems_utils
enum_items = NODE_OT_add_search._enum_item_hack
enum_items.clear()
for index, item in enumerate(nodeitems_utils.node_items_iter(context)):
if isinstance(item, nodeitems_utils.NodeItem):
enum_items.append(
(str(index),
item.label,
"",
index,
))
return enum_items
# Look up the item based on index
def find_node_item(self, context):
import nodeitems_utils
node_item = int(self.node_item)
for index, item in enumerate(nodeitems_utils.node_items_iter(context)):
if index == node_item:
return item
return None
node_item: EnumProperty(
name="Node Type",
description="Node type",
items=NODE_OT_add_search.node_enum_items,
)
def execute(self, context):
item = self.find_node_item(context)
# no need to keep
self._enum_item_hack.clear()
if item:
# apply settings from the node item
for setting in item.settings.items():
ops = self.settings.add()
ops.name = setting[0]
ops.value = setting[1]
self.create_node(context, item.nodetype)
if self.use_transform:
bpy.ops.node.translate_attach_remove_on_cancel(
'INVOKE_DEFAULT')
return {'FINISHED'}
else:
return {'CANCELLED'}
def invoke(self, context, event):
self.store_mouse_cursor(context, event)
# Delayed execution in the search popup
context.window_manager.invoke_search_popup(self)
return {'CANCELLED'}
class NODE_OT_collapse_hide_unused_toggle(Operator):
'''Toggle collapsed nodes and hide unused sockets'''
bl_idname = "node.collapse_hide_unused_toggle"
@ -276,7 +204,6 @@ classes = (
NodeSetting,
NODE_OT_add_node,
NODE_OT_add_search,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_tree_path_parent,
)

View File

@ -47,6 +47,7 @@
#include "BKE_anim_data.h"
#include "BKE_animsys.h"
#include "BKE_asset.h"
#include "BKE_bpath.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
@ -54,6 +55,7 @@
#include "BKE_global.h"
#include "BKE_icons.h"
#include "BKE_idprop.h"
#include "BKE_idprop.hh"
#include "BKE_idtype.h"
#include "BKE_image_format.h"
#include "BKE_lib_id.h"
@ -1026,6 +1028,33 @@ static void ntree_blend_read_expand(BlendExpander *expander, ID *id)
ntreeBlendReadExpand(expander, ntree);
}
namespace blender::bke {
static void node_tree_asset_pre_save(void *asset_ptr, struct AssetMetaData *asset_data)
{
bNodeTree &node_tree = *static_cast<bNodeTree *>(asset_ptr);
BKE_asset_metadata_idprop_ensure(asset_data, idprop::create("type", node_tree.type).release());
auto inputs = idprop::create_group("inputs");
auto outputs = idprop::create_group("outputs");
LISTBASE_FOREACH (const bNodeSocket *, socket, &node_tree.inputs) {
auto property = idprop::create(socket->name, socket->typeinfo->idname);
IDP_AddToGroup(inputs.get(), property.release());
}
LISTBASE_FOREACH (const bNodeSocket *, socket, &node_tree.outputs) {
auto property = idprop::create(socket->name, socket->typeinfo->idname);
IDP_AddToGroup(outputs.get(), property.release());
}
BKE_asset_metadata_idprop_ensure(asset_data, inputs.release());
BKE_asset_metadata_idprop_ensure(asset_data, outputs.release());
}
} // namespace blender::bke
static AssetTypeInfo AssetType_NT = {
/* pre_save_fn */ blender::bke::node_tree_asset_pre_save,
};
IDTypeInfo IDType_ID_NT = {
/* id_code */ ID_NT,
/* id_filter */ FILTER_ID_NT,
@ -1035,7 +1064,7 @@ IDTypeInfo IDType_ID_NT = {
/* name_plural */ "node_groups",
/* translation_context */ BLT_I18NCONTEXT_ID_NODETREE,
/* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/* asset_type_info */ nullptr,
/* asset_type_info */ &AssetType_NT,
/* init_data */ ntree_init_data,
/* copy_data */ ntree_copy_data,

View File

@ -35,3 +35,16 @@ void ED_asset_handle_get_full_library_path(const struct bContext *C,
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
namespace blender::ed::asset {
/** If the ID already exists in the database, return it, otherwise add it. */
ID *get_local_id_from_asset_or_append_and_reuse(Main &bmain,
const AssetLibraryReference &library_ref,
AssetHandle asset);
} // namespace blender::ed::asset
#endif

View File

@ -13,6 +13,8 @@
#include "ED_asset_handle.h"
#include "ED_asset_list.hh"
#include "WM_api.h"
const char *ED_asset_handle_get_name(const AssetHandle *asset)
{
return asset->file_data->name;
@ -52,3 +54,31 @@ void ED_asset_handle_get_full_library_path(const bContext *C,
BLO_library_path_explode(asset_path.c_str(), r_full_lib_path, nullptr, nullptr);
}
namespace blender::ed::asset {
ID *get_local_id_from_asset_or_append_and_reuse(Main &bmain,
const AssetLibraryReference &library_ref,
const AssetHandle asset)
{
if (ID *local_id = ED_asset_handle_get_local_id(&asset)) {
return local_id;
}
char blend_path[FILE_MAX_LIBEXTRA];
ED_asset_handle_get_full_library_path(nullptr, &library_ref, &asset, blend_path);
const char *id_name = ED_asset_handle_get_name(&asset);
return WM_file_append_datablock(&bmain,
nullptr,
nullptr,
nullptr,
blend_path,
ED_asset_handle_get_id_type(&asset),
id_name,
BLO_LIBLINK_APPEND_RECURSIVE |
BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR |
BLO_LIBLINK_APPEND_LOCAL_ID_REUSE);
}
} // namespace blender::ed::asset

View File

@ -532,6 +532,7 @@ typedef struct ARegion *(*uiButSearchTooltipFn)(struct bContext *C,
const struct rcti *item_rect,
void *arg,
void *active);
typedef void (*uiButSearchListenFn)(const struct wmRegionListenerParams *params, void *arg);
/* Must return allocated string. */
typedef char *(*uiButToolTipFunc)(struct bContext *C, void *argN, const char *tip);
@ -1659,6 +1660,7 @@ void UI_but_func_search_set(uiBut *but,
void *active);
void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn);
void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn);
void UI_but_func_search_set_listen(uiBut *but, uiButSearchListenFn listen_fn);
/**
* \param search_sep_string: when not NULL, this string is used as a separator,
* showing the icon and highlighted text after the last instance of this string.

View File

@ -6321,6 +6321,13 @@ void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn)
but_search->item_tooltip_fn = tooltip_fn;
}
void UI_but_func_search_set_listen(uiBut *but, uiButSearchListenFn listen_fn)
{
uiButSearch *but_search = (uiButSearch *)but;
BLI_assert(but->type == UI_BTYPE_SEARCH_MENU);
but_search->listen_fn = listen_fn;
}
void UI_but_func_search_set_results_are_suggestions(uiBut *but, const bool value)
{
uiButSearch *but_search = (uiButSearch *)but;

View File

@ -307,6 +307,8 @@ typedef struct uiButSearch {
uiButSearchCreateFn popup_create_fn;
uiButSearchUpdateFn items_update_fn;
uiButSearchListenFn listen_fn;
void *item_active;
void *arg;

View File

@ -10,6 +10,7 @@
#include <cstdarg>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include "DNA_ID.h"
#include "MEM_guardedalloc.h"
@ -86,6 +87,10 @@ struct uiSearchboxData {
* Used so we can show leading text to menu items less prominently (not related to 'use_sep').
*/
const char *sep_string;
/* Owned by uiButSearch */
void *search_arg;
uiButSearchListenFn search_listener;
};
#define SEARCH_ITEMS 10
@ -689,6 +694,14 @@ static void ui_searchbox_region_free_fn(ARegion *region)
region->regiondata = nullptr;
}
static void ui_searchbox_region_listen_fn(const wmRegionListenerParams *params)
{
uiSearchboxData *data = static_cast<uiSearchboxData *>(params->region->regiondata);
if (data->search_listener) {
data->search_listener(params, data->search_arg);
}
}
static ARegion *ui_searchbox_create_generic_ex(bContext *C,
ARegion *butregion,
uiButSearch *search_but,
@ -707,11 +720,14 @@ static ARegion *ui_searchbox_create_generic_ex(bContext *C,
memset(&type, 0, sizeof(ARegionType));
type.draw = ui_searchbox_region_draw_fn;
type.free = ui_searchbox_region_free_fn;
type.listener = ui_searchbox_region_listen_fn;
type.regionid = RGN_TYPE_TEMPORARY;
region->type = &type;
/* Create search-box data. */
uiSearchboxData *data = MEM_cnew<uiSearchboxData>(__func__);
data->search_arg = search_but->arg;
data->search_listener = search_but->listen_fn;
/* Set font, get the bounding-box. */
data->fstyle = style->widget; /* copy struct */

View File

@ -24,6 +24,7 @@ set(INC
set(SRC
add_node_search.cc
drawnode.cc
link_drag_search.cc
node_add.cc

View File

@ -0,0 +1,312 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <optional>
#include "BLI_listbase.h"
#include "BLI_string_search.h"
#include "DNA_space_types.h"
#include "BKE_asset.h"
#include "BKE_asset_catalog.hh"
#include "BKE_asset_library.hh"
#include "BKE_context.h"
#include "BKE_idprop.h"
#include "BKE_lib_id.h"
#include "BKE_node_tree_update.h"
#include "BKE_screen.h"
#include "DEG_depsgraph_build.h"
#include "BLT_translation.h"
#include "RNA_access.h"
#include "WM_api.h"
#include "ED_asset.h"
#include "ED_node.h"
#include "node_intern.hh"
struct bContext;
namespace blender::ed::space_node {
struct AddNodeItem {
std::string ui_name;
std::string identifier;
std::string description;
std::optional<AssetHandle> asset;
std::function<void(const bContext &, bNodeTree &, bNode &)> after_add_fn;
int weight = 0;
};
struct AddNodeSearchStorage {
float2 cursor;
bool use_transform;
Vector<AddNodeItem> search_add_items;
char search[256];
bool update_items_tag = true;
};
static void add_node_search_listen_fn(const wmRegionListenerParams *params, void *arg)
{
AddNodeSearchStorage &storage = *static_cast<AddNodeSearchStorage *>(arg);
const wmNotifier *wmn = params->notifier;
switch (wmn->category) {
case NC_ASSET:
if (wmn->data == ND_ASSET_LIST_READING) {
storage.update_items_tag = true;
}
break;
}
}
static void search_items_for_asset_metadata(const bNodeTree &node_tree,
const AssetLibraryReference &library_ref,
const AssetHandle asset,
Vector<AddNodeItem> &search_items)
{
const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset);
const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&asset_data, "type");
if (tree_type == nullptr || IDP_Int(tree_type) != node_tree.type) {
return;
}
AddNodeItem item{};
item.ui_name = ED_asset_handle_get_name(&asset);
item.identifier = node_tree.typeinfo->group_idname;
item.description = asset_data.description == nullptr ? "" : asset_data.description;
item.asset = asset;
item.after_add_fn = [asset, library_ref](const bContext &C, bNodeTree &node_tree, bNode &node) {
Main &bmain = *CTX_data_main(&C);
node.flag &= ~NODE_OPTIONS;
node.id = asset::get_local_id_from_asset_or_append_and_reuse(bmain, library_ref, asset);
id_us_plus(node.id);
BKE_ntree_update_tag_node_property(&node_tree, &node);
DEG_relations_tag_update(&bmain);
};
search_items.append(std::move(item));
}
static void gather_search_items_for_asset_library(const bContext &C,
const bNodeTree &node_tree,
const AssetLibraryReference &library_ref,
const bool skip_local,
Vector<AddNodeItem> &search_items)
{
AssetFilterSettings filter_settings{};
filter_settings.id_types = FILTER_ID_NT;
ED_assetlist_storage_fetch(&library_ref, &C);
ED_assetlist_ensure_previews_job(&library_ref, &C);
ED_assetlist_iterate(library_ref, [&](AssetHandle asset) {
if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) {
return true;
}
if (skip_local && ED_asset_handle_get_local_id(&asset) != nullptr) {
return true;
}
search_items_for_asset_metadata(node_tree, library_ref, asset, search_items);
return true;
});
}
static void gather_search_items_for_all_assets(const bContext &C,
const bNodeTree &node_tree,
Vector<AddNodeItem> &search_items)
{
int i;
LISTBASE_FOREACH_INDEX (const bUserAssetLibrary *, asset_library, &U.asset_libraries, i) {
AssetLibraryReference library_ref{};
library_ref.custom_library_index = i;
library_ref.type = ASSET_LIBRARY_CUSTOM;
/* Skip local assets to avoid duplicates when the asset is part of the local file library. */
gather_search_items_for_asset_library(C, node_tree, library_ref, true, search_items);
}
AssetLibraryReference library_ref{};
library_ref.custom_library_index = -1;
library_ref.type = ASSET_LIBRARY_LOCAL;
gather_search_items_for_asset_library(C, node_tree, library_ref, false, search_items);
}
static void gather_add_node_operations(const bContext &C,
bNodeTree &node_tree,
Vector<AddNodeItem> &r_search_items)
{
NODE_TYPES_BEGIN (node_type) {
const char *disabled_hint;
if (!(node_type->poll && node_type->poll(node_type, &node_tree, &disabled_hint))) {
continue;
}
AddNodeItem item{};
item.ui_name = IFACE_(node_type->ui_name);
item.identifier = node_type->idname;
item.description = TIP_(node_type->ui_description);
r_search_items.append(std::move(item));
}
NODE_TYPES_END;
gather_search_items_for_all_assets(C, node_tree, r_search_items);
}
static void add_node_search_update_fn(
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
{
AddNodeSearchStorage &storage = *static_cast<AddNodeSearchStorage *>(arg);
if (storage.update_items_tag) {
bNodeTree *node_tree = CTX_wm_space_node(C)->nodetree;
storage.search_add_items.clear();
gather_add_node_operations(*C, *node_tree, storage.search_add_items);
storage.update_items_tag = false;
}
StringSearch *search = BLI_string_search_new();
for (AddNodeItem &item : storage.search_add_items) {
BLI_string_search_add(search, item.ui_name.c_str(), &item, item.weight);
}
/* Don't filter when the menu is first opened, but still run the search
* so the items are in the same order they will appear in while searching. */
const char *string = is_first ? "" : str;
AddNodeItem **filtered_items;
const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items);
for (const int i : IndexRange(filtered_amount)) {
AddNodeItem &item = *filtered_items[i];
if (!UI_search_item_add(items, item.ui_name.c_str(), &item, ICON_NONE, 0, 0)) {
break;
}
}
MEM_freeN(filtered_items);
BLI_string_search_free(search);
}
static void add_node_search_exec_fn(bContext *C, void *arg1, void *arg2)
{
Main &bmain = *CTX_data_main(C);
SpaceNode &snode = *CTX_wm_space_node(C);
bNodeTree &node_tree = *snode.edittree;
AddNodeSearchStorage &storage = *static_cast<AddNodeSearchStorage *>(arg1);
AddNodeItem *item = static_cast<AddNodeItem *>(arg2);
if (item == nullptr) {
return;
}
node_deselect_all(snode);
bNode *new_node = nodeAddNode(C, &node_tree, item->identifier.c_str());
BLI_assert(new_node != nullptr);
if (item->after_add_fn) {
item->after_add_fn(*C, node_tree, *new_node);
}
new_node->locx = storage.cursor.x / UI_DPI_FAC;
new_node->locy = storage.cursor.y / UI_DPI_FAC + 20 * UI_DPI_FAC;
nodeSetSelected(new_node, true);
nodeSetActive(&node_tree, new_node);
/* Ideally it would be possible to tag the node tree in some way so it updates only after the
* translate operation is finished, but normally moving nodes around doesn't cause updates. */
ED_node_tree_propagate_change(C, &bmain, &node_tree);
if (storage.use_transform) {
wmOperatorType *ot = WM_operatortype_find("NODE_OT_translate_attach_remove_on_cancel", true);
BLI_assert(ot);
PointerRNA ptr;
WM_operator_properties_create_ptr(&ptr, ot);
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, nullptr);
WM_operator_properties_free(&ptr);
}
}
static ARegion *add_node_search_tooltip_fn(
bContext *C, ARegion *region, const rcti *item_rect, void * /*arg*/, void *active)
{
const AddNodeItem *item = static_cast<AddNodeItem *>(active);
uiSearchItemTooltipData tooltip_data{};
BLI_strncpy(tooltip_data.description,
item->asset ? item->description.c_str() : TIP_(item->description.c_str()),
sizeof(tooltip_data.description));
return UI_tooltip_create_from_search_item_generic(C, region, item_rect, &tooltip_data);
}
static void add_node_search_free_fn(void *arg)
{
AddNodeSearchStorage *storage = static_cast<AddNodeSearchStorage *>(arg);
delete storage;
}
static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *arg_op)
{
AddNodeSearchStorage &storage = *(AddNodeSearchStorage *)arg_op;
uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS);
UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
uiBut *but = uiDefSearchBut(block,
storage.search,
0,
ICON_VIEWZOOM,
sizeof(storage.search),
10,
10,
UI_searchbox_size_x(),
UI_UNIT_Y,
0,
0,
"");
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
UI_but_func_search_set(but,
nullptr,
add_node_search_update_fn,
&storage,
false,
add_node_search_free_fn,
add_node_search_exec_fn,
nullptr);
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
UI_but_func_search_set_tooltip(but, add_node_search_tooltip_fn);
UI_but_func_search_set_listen(but, add_node_search_listen_fn);
/* Fake button to hold space for the search items. */
uiDefBut(block,
UI_BTYPE_LABEL,
0,
"",
10,
10 - UI_searchbox_size_y(),
UI_searchbox_size_x(),
UI_searchbox_size_y(),
nullptr,
0,
0,
0,
0,
nullptr);
const int offset[2] = {0, -UI_UNIT_Y};
UI_block_bounds_set_popup(block, 0.3f * U.widget_unit, offset);
return block;
}
void invoke_add_node_search_menu(bContext &C, const float2 &cursor, const bool use_transform)
{
AddNodeSearchStorage *storage = new AddNodeSearchStorage{cursor, use_transform};
/* Use the "_ex" variant with `can_refresh` false to avoid a double free when closing Blender. */
UI_popup_block_invoke_ex(&C, create_search_popup_block, storage, nullptr, false);
}
} // namespace blender::ed::space_node

View File

@ -5,7 +5,12 @@
#include "DNA_space_types.h"
#include "BKE_asset.h"
#include "BKE_context.h"
#include "BKE_idprop.h"
#include "BKE_lib_id.h"
#include "BKE_node_tree_update.h"
#include "BKE_screen.h"
#include "NOD_socket_search_link.hh"
@ -15,6 +20,9 @@
#include "WM_api.h"
#include "DEG_depsgraph_build.h"
#include "ED_asset.h"
#include "ED_node.h"
#include "node_intern.hh"
@ -29,6 +37,7 @@ struct LinkDragSearchStorage {
float2 cursor;
Vector<SocketLinkOperation> search_link_ops;
char search[256];
bool update_items_tag = true;
eNodeSocketInOut in_out() const
{
@ -36,6 +45,20 @@ struct LinkDragSearchStorage {
}
};
static void link_drag_search_listen_fn(const wmRegionListenerParams *params, void *arg)
{
LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg);
const wmNotifier *wmn = params->notifier;
switch (wmn->category) {
case NC_ASSET:
if (wmn->data == ND_ASSET_LIST_READING) {
storage.update_items_tag = true;
}
break;
}
}
static void add_reroute_node_fn(nodes::LinkSearchOpParams &params)
{
bNode &reroute = params.add_node("NodeReroute");
@ -111,12 +134,138 @@ static void add_existing_group_input_fn(nodes::LinkSearchOpParams &params,
nodeAddLink(&params.node_tree, &group_input, socket, &params.node, &params.socket);
}
/**
* \note This could use #search_link_ops_for_socket_templates, but we have to store the inputs and
* outputs as IDProperties for assets because of asset indexing, so that's all we have without
* loading the file.
*/
static void search_link_ops_for_asset_metadata(const bNodeTree &node_tree,
const bNodeSocket &socket,
const AssetLibraryReference &library_ref,
const AssetHandle asset,
Vector<SocketLinkOperation> &search_link_ops)
{
const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset);
const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&asset_data, "type");
if (tree_type == nullptr || IDP_Int(tree_type) != node_tree.type) {
return;
}
const bNodeTreeType &node_tree_type = *node_tree.typeinfo;
const eNodeSocketInOut in_out = socket.in_out == SOCK_OUT ? SOCK_IN : SOCK_OUT;
const IDProperty *sockets = BKE_asset_metadata_idprop_find(
&asset_data, in_out == SOCK_IN ? "inputs" : "outputs");
int weight = -1;
Set<StringRef> socket_names;
LISTBASE_FOREACH (IDProperty *, socket_property, &sockets->data.group) {
if (socket_property->type != IDP_STRING) {
continue;
}
const char *socket_idname = IDP_String(socket_property);
const bNodeSocketType *socket_type = nodeSocketTypeFind(socket_idname);
if (socket_type == nullptr) {
continue;
}
eNodeSocketDatatype from = (eNodeSocketDatatype)socket.type;
eNodeSocketDatatype to = (eNodeSocketDatatype)socket_type->type;
if (socket.in_out == SOCK_OUT) {
std::swap(from, to);
}
if (node_tree_type.validate_link && !node_tree_type.validate_link(from, to)) {
continue;
}
if (!socket_names.add(socket_property->name)) {
/* See comment in #search_link_ops_for_declarations. */
continue;
}
const StringRef asset_name = ED_asset_handle_get_name(&asset);
const StringRef socket_name = socket_property->name;
search_link_ops.append(
{asset_name + " " + UI_MENU_ARROW_SEP + socket_name,
[library_ref, asset, socket_property, in_out](nodes::LinkSearchOpParams &params) {
Main &bmain = *CTX_data_main(&params.C);
bNode &node = params.add_node(params.node_tree.typeinfo->group_idname);
node.flag &= ~NODE_OPTIONS;
node.id = asset::get_local_id_from_asset_or_append_and_reuse(bmain, library_ref, asset);
id_us_plus(node.id);
BKE_ntree_update_tag_node_property(&params.node_tree, &node);
DEG_relations_tag_update(&bmain);
/* Create the inputs and outputs on the new node. */
node.typeinfo->group_update_func(&params.node_tree, &node);
bNodeSocket *new_node_socket = bke::node_find_enabled_socket(
node, in_out, socket_property->name);
if (new_node_socket != nullptr) {
/* Rely on the way #nodeAddLink switches in/out if necessary. */
nodeAddLink(&params.node_tree, &params.node, &params.socket, &node, new_node_socket);
}
},
weight});
weight--;
}
}
static void gather_search_link_ops_for_asset_library(const bContext &C,
const bNodeTree &node_tree,
const bNodeSocket &socket,
const AssetLibraryReference &library_ref,
const bool skip_local,
Vector<SocketLinkOperation> &search_link_ops)
{
AssetFilterSettings filter_settings{};
filter_settings.id_types = FILTER_ID_NT;
ED_assetlist_storage_fetch(&library_ref, &C);
ED_assetlist_ensure_previews_job(&library_ref, &C);
ED_assetlist_iterate(library_ref, [&](AssetHandle asset) {
if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) {
return true;
}
if (skip_local && ED_asset_handle_get_local_id(&asset) != nullptr) {
return true;
}
search_link_ops_for_asset_metadata(node_tree, socket, library_ref, asset, search_link_ops);
return true;
});
}
static void gather_search_link_ops_for_all_assets(const bContext &C,
const bNodeTree &node_tree,
const bNodeSocket &socket,
Vector<SocketLinkOperation> &search_link_ops)
{
int i;
LISTBASE_FOREACH_INDEX (const bUserAssetLibrary *, asset_library, &U.asset_libraries, i) {
AssetLibraryReference library_ref{};
library_ref.custom_library_index = i;
library_ref.type = ASSET_LIBRARY_CUSTOM;
/* Skip local assets to avoid duplicates when the asset is part of the local file library. */
gather_search_link_ops_for_asset_library(
C, node_tree, socket, library_ref, true, search_link_ops);
}
AssetLibraryReference library_ref{};
library_ref.custom_library_index = -1;
library_ref.type = ASSET_LIBRARY_LOCAL;
gather_search_link_ops_for_asset_library(
C, node_tree, socket, library_ref, false, search_link_ops);
}
/**
* Call the callback to gather compatible socket connections for all node types, and the operations
* that will actually make the connections. Also add some custom operations like connecting a group
* output node.
*/
static void gather_socket_link_operations(bNodeTree &node_tree,
static void gather_socket_link_operations(const bContext &C,
bNodeTree &node_tree,
const bNodeSocket &socket,
Vector<SocketLinkOperation> &search_link_ops)
{
@ -156,15 +305,20 @@ static void gather_socket_link_operations(bNodeTree &node_tree,
weight--;
}
}
gather_search_link_ops_for_all_assets(C, node_tree, socket, search_link_ops);
}
static void link_drag_search_update_fn(const bContext *UNUSED(C),
void *arg,
const char *str,
uiSearchItems *items,
const bool is_first)
static void link_drag_search_update_fn(
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
{
LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg);
if (storage.update_items_tag) {
bNodeTree *node_tree = CTX_wm_space_node(C)->edittree;
storage.search_link_ops.clear();
gather_socket_link_operations(*C, *node_tree, storage.from_socket, storage.search_link_ops);
storage.update_items_tag = false;
}
StringSearch *search = BLI_string_search_new();
@ -245,9 +399,6 @@ static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *ar
{
LinkDragSearchStorage &storage = *(LinkDragSearchStorage *)arg_op;
bNodeTree &node_tree = *CTX_wm_space_node(C)->edittree;
gather_socket_link_operations(node_tree, storage.from_socket, storage.search_link_ops);
uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS);
UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
@ -265,6 +416,7 @@ static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *ar
0,
"");
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
UI_but_func_search_set_listen(but, link_drag_search_listen_fn);
UI_but_func_search_set(but,
nullptr,
link_drag_search_update_fn,

View File

@ -810,4 +810,37 @@ void NODE_OT_new_node_tree(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Add Node Search
* \{ */
static int node_add_search_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const ARegion &region = *CTX_wm_region(C);
float2 cursor;
UI_view2d_region_to_view(&region.v2d, event->mval[0], event->mval[1], &cursor.x, &cursor.y);
invoke_add_node_search_menu(*C, cursor, RNA_boolean_get(op->ptr, "use_transform"));
return OPERATOR_FINISHED;
}
void NODE_OT_add_search(wmOperatorType *ot)
{
ot->name = "Search and Add Node";
ot->idname = "NODE_OT_add_search";
ot->description = "Search for nodes and add one to the active tree";
ot->invoke = node_add_search_invoke;
ot->poll = ED_operator_node_editable;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(
ot->srna, "use_transform", true, "Use Transform", "Start moving the node after adding it");
}
/** \} */
} // namespace blender::ed::space_node

View File

@ -251,6 +251,7 @@ bNode *add_node(const bContext &C, StringRef idname, const float2 &location);
bNode *add_static_node(const bContext &C, int type, const float2 &location);
void NODE_OT_add_reroute(wmOperatorType *ot);
void NODE_OT_add_search(wmOperatorType *ot);
void NODE_OT_add_group(wmOperatorType *ot);
void NODE_OT_add_object(wmOperatorType *ot);
void NODE_OT_add_collection(wmOperatorType *ot);
@ -375,4 +376,8 @@ void invoke_node_link_drag_add_menu(bContext &C,
bNodeSocket &socket,
const float2 &cursor);
/* add_node_search.cc */
void invoke_add_node_search_menu(bContext &C, const float2 &cursor, bool use_transform);
} // namespace blender::ed::space_node

View File

@ -75,6 +75,7 @@ void node_operatortypes()
WM_operatortype_append(NODE_OT_backimage_fit);
WM_operatortype_append(NODE_OT_backimage_sample);
WM_operatortype_append(NODE_OT_add_search);
WM_operatortype_append(NODE_OT_add_group);
WM_operatortype_append(NODE_OT_add_object);
WM_operatortype_append(NODE_OT_add_collection);