Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/startup/bl_operators/node.py73
-rw-r--r--source/blender/blenkernel/intern/node.cc31
-rw-r--r--source/blender/editors/asset/ED_asset_handle.h13
-rw-r--r--source/blender/editors/asset/intern/asset_handle.cc30
-rw-r--r--source/blender/editors/include/UI_interface.h2
-rw-r--r--source/blender/editors/interface/interface.cc7
-rw-r--r--source/blender/editors/interface/interface_intern.h2
-rw-r--r--source/blender/editors/interface/interface_region_search.cc16
-rw-r--r--source/blender/editors/space_node/CMakeLists.txt1
-rw-r--r--source/blender/editors/space_node/add_node_search.cc312
-rw-r--r--source/blender/editors/space_node/link_drag_search.cc170
-rw-r--r--source/blender/editors/space_node/node_add.cc33
-rw-r--r--source/blender/editors/space_node/node_intern.hh5
-rw-r--r--source/blender/editors/space_node/node_ops.cc1
14 files changed, 613 insertions, 83 deletions
diff --git a/release/scripts/startup/bl_operators/node.py b/release/scripts/startup/bl_operators/node.py
index ff9b5a06fb7..5cc9c850ebe 100644
--- a/release/scripts/startup/bl_operators/node.py
+++ b/release/scripts/startup/bl_operators/node.py
@@ -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,
)
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 04f5b131743..3a241efdac2 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -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,
diff --git a/source/blender/editors/asset/ED_asset_handle.h b/source/blender/editors/asset/ED_asset_handle.h
index b408e919f32..4a81840c40d 100644
--- a/source/blender/editors/asset/ED_asset_handle.h
+++ b/source/blender/editors/asset/ED_asset_handle.h
@@ -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
diff --git a/source/blender/editors/asset/intern/asset_handle.cc b/source/blender/editors/asset/intern/asset_handle.cc
index ade8b803ed3..00fffd595c0 100644
--- a/source/blender/editors/asset/intern/asset_handle.cc
+++ b/source/blender/editors/asset/intern/asset_handle.cc
@@ -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
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h
index 27e2f89f684..c3376493413 100644
--- a/source/blender/editors/include/UI_interface.h
+++ b/source/blender/editors/include/UI_interface.h
@@ -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.
diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc
index ca4918b2e8d..64f7e035d3f 100644
--- a/source/blender/editors/interface/interface.cc
+++ b/source/blender/editors/interface/interface.cc
@@ -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;
diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h
index 0c842084de6..6ef7d346418 100644
--- a/source/blender/editors/interface/interface_intern.h
+++ b/source/blender/editors/interface/interface_intern.h
@@ -307,6 +307,8 @@ typedef struct uiButSearch {
uiButSearchCreateFn popup_create_fn;
uiButSearchUpdateFn items_update_fn;
+ uiButSearchListenFn listen_fn;
+
void *item_active;
void *arg;
diff --git a/source/blender/editors/interface/interface_region_search.cc b/source/blender/editors/interface/interface_region_search.cc
index 6bb47666afd..fb7810792e9 100644
--- a/source/blender/editors/interface/interface_region_search.cc
+++ b/source/blender/editors/interface/interface_region_search.cc
@@ -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 */
diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt
index 8a1d47eaa8d..adb03439568 100644
--- a/source/blender/editors/space_node/CMakeLists.txt
+++ b/source/blender/editors/space_node/CMakeLists.txt
@@ -24,6 +24,7 @@ set(INC
set(SRC
+ add_node_search.cc
drawnode.cc
link_drag_search.cc
node_add.cc
diff --git a/source/blender/editors/space_node/add_node_search.cc b/source/blender/editors/space_node/add_node_search.cc
new file mode 100644
index 00000000000..7ae66e9feb7
--- /dev/null
+++ b/source/blender/editors/space_node/add_node_search.cc
@@ -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
diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc
index 03bd0b5b36a..17410937d4c 100644
--- a/source/blender/editors/space_node/link_drag_search.cc
+++ b/source/blender/editors/space_node/link_drag_search.cc
@@ -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");
@@ -112,11 +135,137 @@ static void add_existing_group_input_fn(nodes::LinkSearchOpParams &params,
}
/**
+ * \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,
diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc
index 9949037479e..efe53fd6f14 100644
--- a/source/blender/editors/space_node/node_add.cc
+++ b/source/blender/editors/space_node/node_add.cc
@@ -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
diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh
index 456cbf5064d..70ac0e48d01 100644
--- a/source/blender/editors/space_node/node_intern.hh
+++ b/source/blender/editors/space_node/node_intern.hh
@@ -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
diff --git a/source/blender/editors/space_node/node_ops.cc b/source/blender/editors/space_node/node_ops.cc
index a208370a6f9..f02c019359d 100644
--- a/source/blender/editors/space_node/node_ops.cc
+++ b/source/blender/editors/space_node/node_ops.cc
@@ -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);