diff options
-rw-r--r-- | release/scripts/startup/bl_operators/node.py | 73 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node.cc | 31 | ||||
-rw-r--r-- | source/blender/editors/asset/ED_asset_handle.h | 13 | ||||
-rw-r--r-- | source/blender/editors/asset/intern/asset_handle.cc | 30 | ||||
-rw-r--r-- | source/blender/editors/include/UI_interface.h | 2 | ||||
-rw-r--r-- | source/blender/editors/interface/interface.cc | 7 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_intern.h | 2 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_region_search.cc | 16 | ||||
-rw-r--r-- | source/blender/editors/space_node/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/editors/space_node/add_node_search.cc | 312 | ||||
-rw-r--r-- | source/blender/editors/space_node/link_drag_search.cc | 170 | ||||
-rw-r--r-- | source/blender/editors/space_node/node_add.cc | 33 | ||||
-rw-r--r-- | source/blender/editors/space_node/node_intern.hh | 5 | ||||
-rw-r--r-- | source/blender/editors/space_node/node_ops.cc | 1 |
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 ¶ms) { bNode &reroute = params.add_node("NodeReroute"); @@ -112,11 +135,137 @@ static void add_existing_group_input_fn(nodes::LinkSearchOpParams ¶ms, } /** + * \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 ¶ms) { + Main &bmain = *CTX_data_main(¶ms.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(¶ms.node_tree, &node); + DEG_relations_tag_update(&bmain); + + /* Create the inputs and outputs on the new node. */ + node.typeinfo->group_update_func(¶ms.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(¶ms.node_tree, ¶ms.node, ¶ms.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 ®ion = *CTX_wm_region(C); + + float2 cursor; + UI_view2d_region_to_view(®ion.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); |