diff options
Diffstat (limited to 'source/blender/editors/space_node')
18 files changed, 1866 insertions, 603 deletions
diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt index 8a1d47eaa8d..ff9e5352d0a 100644 --- a/source/blender/editors/space_node/CMakeLists.txt +++ b/source/blender/editors/space_node/CMakeLists.txt @@ -5,6 +5,7 @@ set(INC ../../blenfont ../../blenkernel ../../blenlib + ../../blenloader ../../blentranslation ../../compositor ../../depsgraph @@ -18,12 +19,17 @@ set(INC ../../render ../../windowmanager ../../../../intern/guardedalloc + + # dna_type_offsets.h + ${CMAKE_CURRENT_BINARY_DIR}/../../makesdna/intern # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna ) set(SRC + add_node_search.cc + add_menu_assets.cc drawnode.cc link_drag_search.cc node_add.cc @@ -79,5 +85,5 @@ endif() blender_add_lib(bf_editor_space_node "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") -# RNA_prototypes.h +# RNA_prototypes.h dna_type_offsets.h add_dependencies(bf_editor_space_node bf_rna) diff --git a/source/blender/editors/space_node/add_menu_assets.cc b/source/blender/editors/space_node/add_menu_assets.cc new file mode 100644 index 00000000000..4bb98124a3f --- /dev/null +++ b/source/blender/editors/space_node/add_menu_assets.cc @@ -0,0 +1,316 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_multi_value_map.hh" + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BKE_asset.h" +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.hh" +#include "BKE_idprop.h" +#include "BKE_screen.h" + +#include "BLT_translation.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "ED_asset.h" +#include "ED_screen.h" + +#include "node_intern.hh" + +namespace blender::ed::space_node { + +static bool node_add_menu_poll(const bContext *C, MenuType * /*mt*/) +{ + return CTX_wm_space_node(C); +} + +static void node_add_menu_assets_listen_fn(const wmRegionListenerParams *params) +{ + const wmNotifier *wmn = params->notifier; + ARegion *region = params->region; + + switch (wmn->category) { + case NC_ASSET: + if (wmn->data == ND_ASSET_LIST_READING) { + ED_region_tag_refresh_ui(region); + } + break; + } +} + +struct LibraryAsset { + AssetLibraryReference library_ref; + AssetHandle handle; +}; + +struct LibraryCatalog { + bke::AssetLibrary *library; + const bke::AssetCatalog *catalog; +}; + +struct AssetItemTree { + bke::AssetCatalogTree catalogs; + MultiValueMap<bke::AssetCatalogPath, LibraryAsset> assets_per_path; + Map<const bke::AssetCatalogTreeItem *, bke::AssetCatalogPath> full_catalog_per_tree_item; +}; + +static bool all_loading_finished() +{ + for (const AssetLibraryReference &library : bke::all_valid_asset_library_refs()) { + if (!ED_assetlist_is_loaded(&library)) { + return false; + } + } + return true; +} + +static AssetItemTree build_catalog_tree(const bContext &C, const bNodeTree *node_tree) +{ + if (!node_tree) { + return {}; + } + const Main &bmain = *CTX_data_main(&C); + const Vector<AssetLibraryReference> all_libraries = bke::all_valid_asset_library_refs(); + + /* Merge catalogs from all libraries to deduplicate menu items. Also store the catalog and + * library for each asset ID in order to use them later when retrieving assets and removing + * empty catalogs. */ + Map<bke::CatalogID, LibraryCatalog> id_to_catalog_map; + bke::AssetCatalogTree catalogs_from_all_libraries; + for (const AssetLibraryReference &library_ref : all_libraries) { + if (bke::AssetLibrary *library = BKE_asset_library_load(&bmain, library_ref)) { + if (bke::AssetCatalogTree *tree = library->catalog_service->get_catalog_tree()) { + tree->foreach_item([&](bke::AssetCatalogTreeItem &item) { + const bke::CatalogID &id = item.get_catalog_id(); + bke::AssetCatalog *catalog = library->catalog_service->find_catalog(id); + catalogs_from_all_libraries.insert_item(*catalog); + id_to_catalog_map.add(item.get_catalog_id(), LibraryCatalog{library, catalog}); + }); + } + } + } + + /* Find all the matching node group assets for every catalog path. */ + MultiValueMap<bke::AssetCatalogPath, LibraryAsset> assets_per_path; + for (const AssetLibraryReference &library_ref : all_libraries) { + AssetFilterSettings type_filter{}; + type_filter.id_types = FILTER_ID_NT; + + ED_assetlist_storage_fetch(&library_ref, &C); + ED_assetlist_iterate(library_ref, [&](AssetHandle asset) { + if (!ED_asset_filter_matches_asset(&type_filter, &asset)) { + return true; + } + const AssetMetaData &meta_data = *ED_asset_handle_get_metadata(&asset); + const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&meta_data, "type"); + if (tree_type == nullptr || IDP_Int(tree_type) != node_tree->type) { + return true; + } + if (BLI_uuid_is_nil(meta_data.catalog_id)) { + return true; + } + const LibraryCatalog *library_catalog = id_to_catalog_map.lookup_ptr(meta_data.catalog_id); + if (library_catalog == nullptr) { + return true; + } + assets_per_path.add(library_catalog->catalog->path, LibraryAsset{library_ref, asset}); + return true; + }); + } + + /* Build the final tree without any of the catalogs that don't have proper node group assets. */ + bke::AssetCatalogTree catalogs_with_node_assets; + catalogs_from_all_libraries.foreach_item([&](bke::AssetCatalogTreeItem &item) { + if (!assets_per_path.lookup(item.catalog_path()).is_empty()) { + const bke::CatalogID &id = item.get_catalog_id(); + const LibraryCatalog &library_catalog = id_to_catalog_map.lookup(id); + bke::AssetCatalog *catalog = library_catalog.library->catalog_service->find_catalog(id); + catalogs_with_node_assets.insert_item(*catalog); + } + }); + + /* Build another map storing full asset paths for each tree item, in order to have stable + * pointers to asset catalog paths to use for context pointers. This is necessary because + * #bke::AssetCatalogTreeItem doesn't store its full path directly. */ + Map<const bke::AssetCatalogTreeItem *, bke::AssetCatalogPath> full_catalog_per_tree_item; + catalogs_with_node_assets.foreach_item([&](bke::AssetCatalogTreeItem &item) { + full_catalog_per_tree_item.add_new(&item, item.catalog_path()); + }); + + return {std::move(catalogs_with_node_assets), + std::move(assets_per_path), + std::move(full_catalog_per_tree_item)}; +} + +static void node_add_catalog_assets_draw(const bContext *C, Menu *menu) +{ + bScreen &screen = *CTX_wm_screen(C); + const SpaceNode &snode = *CTX_wm_space_node(C); + if (!snode.runtime->assets_for_menu) { + BLI_assert_unreachable(); + return; + } + AssetItemTree &tree = *snode.runtime->assets_for_menu; + const bNodeTree *edit_tree = snode.edittree; + if (!edit_tree) { + return; + } + + const PointerRNA menu_path_ptr = CTX_data_pointer_get(C, "asset_catalog_path"); + if (RNA_pointer_is_null(&menu_path_ptr)) { + return; + } + const bke::AssetCatalogPath &menu_path = *static_cast<const bke::AssetCatalogPath *>( + menu_path_ptr.data); + + const Span<LibraryAsset> asset_items = tree.assets_per_path.lookup(menu_path); + bke::AssetCatalogTreeItem *catalog_item = tree.catalogs.find_item(menu_path); + BLI_assert(catalog_item != nullptr); + + if (asset_items.is_empty() && !catalog_item->has_children()) { + return; + } + + uiLayout *layout = menu->layout; + uiItemS(layout); + + for (const LibraryAsset &item : asset_items) { + uiLayout *col = uiLayoutColumn(layout, false); + PointerRNA file{ + &screen.id, &RNA_FileSelectEntry, const_cast<FileDirEntry *>(item.handle.file_data)}; + uiLayoutSetContextPointer(col, "active_file", &file); + + PointerRNA library_ptr{&screen.id, + &RNA_AssetLibraryReference, + const_cast<AssetLibraryReference *>(&item.library_ref)}; + uiLayoutSetContextPointer(col, "asset_library_ref", &library_ptr); + + uiItemO(col, ED_asset_handle_get_name(&item.handle), ICON_NONE, "NODE_OT_add_group_asset"); + } + + catalog_item->foreach_child([&](bke::AssetCatalogTreeItem &child_item) { + const bke::AssetCatalogPath &path = tree.full_catalog_per_tree_item.lookup(&child_item); + PointerRNA path_ptr{ + &screen.id, &RNA_AssetCatalogPath, const_cast<bke::AssetCatalogPath *>(&path)}; + uiLayout *col = uiLayoutColumn(layout, false); + uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); + uiItemM(col, "NODE_MT_node_add_catalog_assets", path.name().c_str(), ICON_NONE); + }); +} + +static void add_root_catalogs_draw(const bContext *C, Menu *menu) +{ + bScreen &screen = *CTX_wm_screen(C); + SpaceNode &snode = *CTX_wm_space_node(C); + const bNodeTree *edit_tree = snode.edittree; + uiLayout *layout = menu->layout; + + snode.runtime->assets_for_menu = std::make_shared<AssetItemTree>( + build_catalog_tree(*C, edit_tree)); + + const bool loading_finished = all_loading_finished(); + + AssetItemTree &tree = *snode.runtime->assets_for_menu; + if (tree.catalogs.is_empty() && loading_finished) { + return; + } + + uiItemS(layout); + + if (!loading_finished) { + uiItemL(layout, IFACE_("Loading Asset Libraries"), ICON_INFO); + } + + /* Avoid adding a separate root catalog when the assets have already been added to one of the + * builtin menus. + * TODO: The need to define the builtin menu labels here is completely non-ideal. We don't have + * any UI introspection that can do this though. This can be solved in the near future by + * removing the need to define the add menu completely, instead using a per-node-type path which + * can be merged with catalog tree. + */ + static Set<std::string> all_builtin_menus = []() { + Set<std::string> menus; + menus.add_new("Attribute"); + menus.add_new("Color"); + menus.add_new("Curve"); + menus.add_new("Curve Primitives"); + menus.add_new("Curve Topology"); + menus.add_new("Geometry"); + menus.add_new("Input"); + menus.add_new("Instances"); + menus.add_new("Material"); + menus.add_new("Mesh"); + menus.add_new("Mesh Primitives"); + menus.add_new("Mesh Topology"); + menus.add_new("Output"); + menus.add_new("Point"); + menus.add_new("Text"); + menus.add_new("Texture"); + menus.add_new("Utilities"); + menus.add_new("UV"); + menus.add_new("Vector"); + menus.add_new("Volume"); + menus.add_new("Group"); + menus.add_new("Layout"); + return menus; + }(); + + tree.catalogs.foreach_root_item([&](bke::AssetCatalogTreeItem &item) { + if (all_builtin_menus.contains(item.get_name())) { + return; + } + const bke::AssetCatalogPath &path = tree.full_catalog_per_tree_item.lookup(&item); + PointerRNA path_ptr{ + &screen.id, &RNA_AssetCatalogPath, const_cast<bke::AssetCatalogPath *>(&path)}; + uiLayout *col = uiLayoutColumn(layout, false); + uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); + uiItemM(col, "NODE_MT_node_add_catalog_assets", path.name().c_str(), ICON_NONE); + }); +} + +MenuType add_catalog_assets_menu_type() +{ + MenuType type{}; + BLI_strncpy(type.idname, "NODE_MT_node_add_catalog_assets", sizeof(type.idname)); + type.poll = node_add_menu_poll; + type.draw = node_add_catalog_assets_draw; + type.listener = node_add_menu_assets_listen_fn; + return type; +} + +MenuType add_root_catalogs_menu_type() +{ + MenuType type{}; + BLI_strncpy(type.idname, "NODE_MT_node_add_root_catalogs", sizeof(type.idname)); + type.poll = node_add_menu_poll; + type.draw = add_root_catalogs_draw; + type.listener = node_add_menu_assets_listen_fn; + return type; +} + +} // namespace blender::ed::space_node + +/* Note: This is only necessary because Python can't set an asset catalog path context item. */ +void uiTemplateNodeAssetMenuItems(uiLayout *layout, bContext *C, const char *catalog_path) +{ + using namespace blender; + using namespace blender::ed::space_node; + bScreen &screen = *CTX_wm_screen(C); + SpaceNode &snode = *CTX_wm_space_node(C); + AssetItemTree &tree = *snode.runtime->assets_for_menu; + const bke::AssetCatalogTreeItem *item = tree.catalogs.find_root_item(catalog_path); + if (!item) { + return; + } + const bke::AssetCatalogPath &path = tree.full_catalog_per_tree_item.lookup(item); + PointerRNA path_ptr{ + &screen.id, &RNA_AssetCatalogPath, const_cast<bke::AssetCatalogPath *>(&path)}; + uiItemS(layout); + uiLayout *col = uiLayoutColumn(layout, false); + uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); + uiItemMContents(col, "NODE_MT_node_add_catalog_assets"); +} 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..23ec2f2f66e --- /dev/null +++ b/source/blender/editors/space_node/add_node_search.cc @@ -0,0 +1,358 @@ +/* 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_main.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, + Set<std::string> &r_added_assets, + Vector<AddNodeItem> &search_items) +{ + AssetFilterSettings filter_settings{}; + filter_settings.id_types = FILTER_ID_NT; + + ED_assetlist_storage_fetch(&library_ref, &C); + ED_assetlist_iterate(library_ref, [&](AssetHandle asset) { + if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) { + return true; + } + if (!r_added_assets.add(ED_asset_handle_get_name(&asset))) { + /* If an asset with the same name has already been added, skip this. */ + 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, + Set<std::string> &r_added_assets, + 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, r_added_assets, 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, r_added_assets, search_items); +} + +static void gather_search_items_for_node_groups(const bContext &C, + const bNodeTree &node_tree, + const Set<std::string> &local_assets, + Vector<AddNodeItem> &search_items) +{ + const StringRef group_node_id = node_tree.typeinfo->group_idname; + + Main &bmain = *CTX_data_main(&C); + LISTBASE_FOREACH (bNodeTree *, node_group, &bmain.nodetrees) { + if (node_group->typeinfo->group_idname != group_node_id) { + continue; + } + if (local_assets.contains(node_group->id.name)) { + continue; + } + if (!nodeGroupPoll(&node_tree, node_group, nullptr)) { + continue; + } + AddNodeItem item{}; + item.ui_name = node_group->id.name + 2; + item.identifier = node_tree.typeinfo->group_idname; + item.after_add_fn = [node_group](const bContext &C, bNodeTree &node_tree, bNode &node) { + Main &bmain = *CTX_data_main(&C); + node.id = &node_group->id; + 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_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; + } + if ((StringRefNull(node_tree.typeinfo->group_idname) == node_type->idname)) { + /* Skip the empty group type. */ + continue; + } + if (StringRefNull(node_type->ui_name).endswith("(Legacy)")) { + 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; + + /* Use a set to avoid adding items for node groups that are also assets. Using data-block + * names is a crutch, since different assets may have the same name. However, an alternative + * using #ED_asset_handle_get_local_id didn't work in this case. */ + Set<std::string> added_assets; + gather_search_items_for_all_assets(C, node_tree, added_assets, r_search_items); + gather_search_items_for_node_groups(C, node_tree, added_assets, 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)->edittree; + 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<const 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/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index fbbdd40e92e..c66b8ad4ff0 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -73,10 +73,10 @@ namespace blender::ed::space_node { /* ****************** SOCKET BUTTON DRAW FUNCTIONS ***************** */ -static void node_socket_button_label(bContext *UNUSED(C), +static void node_socket_button_label(bContext * /*C*/, uiLayout *layout, - PointerRNA *UNUSED(ptr), - PointerRNA *UNUSED(node_ptr), + PointerRNA * /*ptr*/, + PointerRNA * /*node_ptr*/, const char *text) { uiItemL(layout, text, 0); @@ -84,7 +84,7 @@ static void node_socket_button_label(bContext *UNUSED(C), /* ****************** BUTTON CALLBACKS FOR ALL TREES ***************** */ -static void node_buts_value(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_value(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { bNode *node = (bNode *)ptr->data; /* first output stores value */ @@ -95,7 +95,7 @@ static void node_buts_value(uiLayout *layout, bContext *UNUSED(C), PointerRNA *p uiItemR(layout, &sockptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE); } -static void node_buts_rgb(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_rgb(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { bNode *node = (bNode *)ptr->data; /* first output stores value */ @@ -109,7 +109,7 @@ static void node_buts_rgb(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr uiItemR(col, &sockptr, "default_value", DEFAULT_FLAGS | UI_ITEM_R_SLIDER, "", ICON_NONE); } -static void node_buts_mix_rgb(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_mix_rgb(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { bNodeTree *ntree = (bNodeTree *)ptr->owner_id; @@ -123,7 +123,7 @@ static void node_buts_mix_rgb(uiLayout *layout, bContext *UNUSED(C), PointerRNA uiItemR(col, ptr, "use_clamp", DEFAULT_FLAGS, nullptr, ICON_NONE); } -static void node_buts_time(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_time(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiTemplateCurveMapping(layout, ptr, "curve", 's', false, false, false, false); @@ -132,17 +132,17 @@ static void node_buts_time(uiLayout *layout, bContext *UNUSED(C), PointerRNA *pt uiItemR(col, ptr, "frame_end", DEFAULT_FLAGS, IFACE_("End"), ICON_NONE); } -static void node_buts_colorramp(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_colorramp(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiTemplateColorRamp(layout, ptr, "color_ramp", false); } -static void node_buts_curvevec(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_curvevec(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiTemplateCurveMapping(layout, ptr, "mapping", 'v', false, false, false, false); } -static void node_buts_curvefloat(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_curvefloat(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiTemplateCurveMapping(layout, ptr, "mapping", 0, false, false, false, false); } @@ -164,7 +164,7 @@ void ED_node_sample_set(const float col[4]) namespace blender::ed::space_node { -static void node_buts_curvecol(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_curvecol(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { bNode *node = (bNode *)ptr->data; CurveMapping *cumap = (CurveMapping *)node->storage; @@ -183,7 +183,7 @@ static void node_buts_curvecol(uiLayout *layout, bContext *UNUSED(C), PointerRNA layout, ptr, "mapping", 'c', false, false, false, (ntree->type == NTREE_COMPOSIT)); } -static void node_buts_normal(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_normal(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { bNode *node = (bNode *)ptr->data; /* first output stores normal */ @@ -194,7 +194,7 @@ static void node_buts_normal(uiLayout *layout, bContext *UNUSED(C), PointerRNA * uiItemR(layout, &sockptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE); } -static void node_buts_texture(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_texture(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { bNode *node = (bNode *)ptr->data; @@ -209,13 +209,13 @@ static void node_buts_texture(uiLayout *layout, bContext *UNUSED(C), PointerRNA } } -static void node_buts_math(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_math(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiItemR(layout, ptr, "operation", DEFAULT_FLAGS, "", ICON_NONE); uiItemR(layout, ptr, "use_clamp", DEFAULT_FLAGS, nullptr, ICON_NONE); } -static void node_buts_combsep_color(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_combsep_color(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiItemR(layout, ptr, "mode", DEFAULT_FLAGS, "", ICON_NONE); } @@ -283,7 +283,7 @@ static void node_draw_buttons_group(uiLayout *layout, bContext *C, PointerRNA *p layout, C, ptr, "node_tree", nullptr, nullptr, nullptr, UI_TEMPLATE_ID_FILTER_ALL, nullptr); } -static void node_buts_frame_ex(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_frame_ex(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiItemR(layout, ptr, "label_size", DEFAULT_FLAGS, IFACE_("Label Size"), ICON_NONE); uiItemR(layout, ptr, "shrink", DEFAULT_FLAGS, IFACE_("Shrink"), ICON_NONE); @@ -441,17 +441,17 @@ static void node_shader_buts_tex_environment_ex(uiLayout *layout, bContext *C, P uiItemR(layout, ptr, "projection", DEFAULT_FLAGS, IFACE_("Projection"), ICON_NONE); } -static void node_shader_buts_displacement(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_shader_buts_displacement(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiItemR(layout, ptr, "space", DEFAULT_FLAGS, "", 0); } -static void node_shader_buts_glossy(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_shader_buts_glossy(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiItemR(layout, ptr, "distribution", DEFAULT_FLAGS, "", ICON_NONE); } -static void node_buts_output_shader(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_buts_output_shader(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiItemR(layout, ptr, "target", DEFAULT_FLAGS, "", ICON_NONE); } @@ -519,7 +519,7 @@ static void node_shader_set_butfunc(bNodeType *ntype) /* ****************** BUTTON CALLBACKS FOR COMPOSITE NODES ***************** */ static void node_buts_image_views(uiLayout *layout, - bContext *UNUSED(C), + bContext * /*C*/, PointerRNA *ptr, PointerRNA *imaptr) { @@ -579,7 +579,7 @@ static void node_composit_buts_image_ex(uiLayout *layout, bContext *C, PointerRN uiTemplateImage(layout, C, ptr, "image", &iuserptr, false, true); } -static void node_composit_buts_huecorrect(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_composit_buts_huecorrect(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { bNode *node = (bNode *)ptr->data; CurveMapping *cumap = (CurveMapping *)node->storage; @@ -595,14 +595,12 @@ static void node_composit_buts_huecorrect(uiLayout *layout, bContext *UNUSED(C), uiTemplateCurveMapping(layout, ptr, "mapping", 'h', false, false, false, false); } -static void node_composit_buts_ycc(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_composit_buts_ycc(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiItemR(layout, ptr, "mode", DEFAULT_FLAGS, "", ICON_NONE); } -static void node_composit_buts_combsep_color(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void node_composit_buts_combsep_color(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { bNode *node = (bNode *)ptr->data; NodeCMPCombSepColor *storage = (NodeCMPCombSepColor *)node->storage; @@ -733,7 +731,7 @@ static void node_composit_backdrop_ellipsemask( } static void node_composit_buts_cryptomatte_legacy(uiLayout *layout, - bContext *UNUSED(C), + bContext * /*C*/, PointerRNA *ptr) { uiLayout *col = uiLayoutColumn(layout, true); @@ -748,8 +746,8 @@ static void node_composit_buts_cryptomatte_legacy(uiLayout *layout, } static void node_composit_buts_cryptomatte_legacy_ex(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *UNUSED(ptr)) + bContext * /*C*/, + PointerRNA * /*ptr*/) { uiItemO(layout, IFACE_("Add Crypto Layer"), ICON_ADD, "NODE_OT_cryptomatte_layer_add"); uiItemO(layout, IFACE_("Remove Crypto Layer"), ICON_REMOVE, "NODE_OT_cryptomatte_layer_remove"); @@ -874,7 +872,7 @@ static void node_composit_set_butfunc(bNodeType *ntype) /* ****************** BUTTON CALLBACKS FOR TEXTURE NODES ***************** */ -static void node_texture_buts_bricks(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_texture_buts_bricks(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiLayout *col; @@ -887,7 +885,7 @@ static void node_texture_buts_bricks(uiLayout *layout, bContext *UNUSED(C), Poin uiItemR(col, ptr, "squash_frequency", DEFAULT_FLAGS, IFACE_("Frequency"), ICON_NONE); } -static void node_texture_buts_proc(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_texture_buts_proc(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { PointerRNA tex_ptr; bNode *node = (bNode *)ptr->data; @@ -938,7 +936,7 @@ static void node_texture_buts_proc(uiLayout *layout, bContext *UNUSED(C), Pointe uiItemR( row, &tex_ptr, "noise_basis_2", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, nullptr, ICON_NONE); row = uiLayoutRow(col, false); - uiLayoutSetActive(row, !(ELEM(tex->stype, TEX_BAND, TEX_RING))); + uiLayoutSetActive(row, !ELEM(tex->stype, TEX_BAND, TEX_RING)); uiItemR(row, &tex_ptr, "noise_type", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, nullptr, ICON_NONE); break; @@ -998,12 +996,12 @@ static void node_texture_buts_image_ex(uiLayout *layout, bContext *C, PointerRNA uiTemplateImage(layout, C, ptr, "image", &iuserptr, false, false); } -static void node_texture_buts_output(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_texture_buts_output(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiItemR(layout, ptr, "filepath", DEFAULT_FLAGS, "", ICON_NONE); } -static void node_texture_buts_combsep_color(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +static void node_texture_buts_combsep_color(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiItemR(layout, ptr, "mode", DEFAULT_FLAGS, "", ICON_NONE); } @@ -1068,7 +1066,7 @@ static void node_texture_set_butfunc(bNodeType *ntype) * Only called on node initialization, once. * \{ */ -static void node_property_update_default(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) +static void node_property_update_default(Main *bmain, Scene * /*scene*/, PointerRNA *ptr) { bNodeTree *ntree = (bNodeTree *)ptr->owner_id; bNode *node = (bNode *)ptr->data; @@ -1102,18 +1100,18 @@ static void node_template_properties_update(bNodeType *ntype) } } -static void node_socket_undefined_draw(bContext *UNUSED(C), +static void node_socket_undefined_draw(bContext * /*C*/, uiLayout *layout, - PointerRNA *UNUSED(ptr), - PointerRNA *UNUSED(node_ptr), - const char *UNUSED(text)) + PointerRNA * /*ptr*/, + PointerRNA * /*node_ptr*/, + const char * /*text*/) { uiItemL(layout, IFACE_("Undefined Socket Type"), ICON_ERROR); } -static void node_socket_undefined_draw_color(bContext *UNUSED(C), - PointerRNA *UNUSED(ptr), - PointerRNA *UNUSED(node_ptr), +static void node_socket_undefined_draw_color(bContext * /*C*/, + PointerRNA * /*ptr*/, + PointerRNA * /*node_ptr*/, float *r_color) { r_color[0] = 1.0f; @@ -1122,15 +1120,15 @@ static void node_socket_undefined_draw_color(bContext *UNUSED(C), r_color[3] = 1.0f; } -static void node_socket_undefined_interface_draw(bContext *UNUSED(C), +static void node_socket_undefined_interface_draw(bContext * /*C*/, uiLayout *layout, - PointerRNA *UNUSED(ptr)) + PointerRNA * /*ptr*/) { uiItemL(layout, IFACE_("Undefined Socket Type"), ICON_ERROR); } -static void node_socket_undefined_interface_draw_color(bContext *UNUSED(C), - PointerRNA *UNUSED(ptr), +static void node_socket_undefined_interface_draw_color(bContext * /*C*/, + PointerRNA * /*ptr*/, float *r_color) { r_color[0] = 1.0f; @@ -1173,7 +1171,7 @@ void ED_node_init_butfuncs() NODE_TYPES_END; } -void ED_init_custom_node_type(bNodeType *UNUSED(ntype)) +void ED_init_custom_node_type(bNodeType * /*ntype*/) { } @@ -1205,18 +1203,16 @@ static const float std_node_socket_colors[][4] = { }; /* common color callbacks for standard types */ -static void std_node_socket_draw_color(bContext *UNUSED(C), +static void std_node_socket_draw_color(bContext * /*C*/, PointerRNA *ptr, - PointerRNA *UNUSED(node_ptr), + PointerRNA * /*node_ptr*/, float *r_color) { bNodeSocket *sock = (bNodeSocket *)ptr->data; int type = sock->typeinfo->type; copy_v4_v4(r_color, std_node_socket_colors[type]); } -static void std_node_socket_interface_draw_color(bContext *UNUSED(C), - PointerRNA *ptr, - float *r_color) +static void std_node_socket_interface_draw_color(bContext * /*C*/, PointerRNA *ptr, float *r_color) { bNodeSocket *sock = (bNodeSocket *)ptr->data; int type = sock->typeinfo->type; @@ -1425,7 +1421,7 @@ static void std_node_socket_draw( } } -static void std_node_socket_interface_draw(bContext *UNUSED(C), uiLayout *layout, PointerRNA *ptr) +static void std_node_socket_interface_draw(bContext * /*C*/, uiLayout *layout, PointerRNA *ptr) { bNodeSocket *sock = (bNodeSocket *)ptr->data; int type = sock->typeinfo->type; @@ -1469,9 +1465,9 @@ static void std_node_socket_interface_draw(bContext *UNUSED(C), uiLayout *layout uiItemR(layout, ptr, "hide_value", DEFAULT_FLAGS, nullptr, 0); } -static void node_socket_virtual_draw_color(bContext *UNUSED(C), - PointerRNA *UNUSED(ptr), - PointerRNA *UNUSED(node_ptr), +static void node_socket_virtual_draw_color(bContext * /*C*/, + PointerRNA * /*ptr*/, + PointerRNA * /*node_ptr*/, float *r_color) { copy_v4_v4(r_color, virtual_node_socket_color); @@ -1585,10 +1581,12 @@ void draw_nodespace_back_pix(const bContext &C, GPU_matrix_pop(); } -static float2 socket_link_connection_location(const bNodeSocket &socket, const bNodeLink &link) +static float2 socket_link_connection_location(const bNode &node, + const bNodeSocket &socket, + const bNodeLink &link) { const float2 socket_location(socket.locx, socket.locy); - if (socket.flag & SOCK_MULTI_INPUT && socket.in_out == SOCK_IN) { + if (socket.is_multi_input() && socket.is_input() && !(node.flag & NODE_HIDDEN)) { return node_link_calculate_multi_input_position( socket_location, link.multi_input_socket_index, socket.total_inputs); } @@ -1604,12 +1602,19 @@ static void calculate_inner_link_bezier_points(std::array<float2, 4> &points) points[2] = math::interpolate(points[0], points[3], 2.0f / 3.0f); } else { - const float dist = curving * 0.1f * math::distance(points[0].x, points[3].x); + const float dist_x = math::distance(points[0].x, points[3].x); + const float dist_y = math::distance(points[0].y, points[3].y); - points[1].x = points[0].x + dist; + /* Reduce the handle offset when the link endpoints are close to horizontal. */ + const float slope = safe_divide(dist_y, dist_x); + const float clamp_factor = math::min(1.0f, slope * (4.5f - 0.25f * float(curving))); + + const float handle_offset = curving * 0.1f * dist_x * clamp_factor; + + points[1].x = points[0].x + handle_offset; points[1].y = points[0].y; - points[2].x = points[3].x - dist; + points[2].x = points[3].x - handle_offset; points[2].y = points[3].y; } } @@ -1617,8 +1622,8 @@ static void calculate_inner_link_bezier_points(std::array<float2, 4> &points) static std::array<float2, 4> node_link_bezier_points(const bNodeLink &link) { std::array<float2, 4> points; - points[0] = socket_link_connection_location(*link.fromsock, link); - points[3] = socket_link_connection_location(*link.tosock, link); + points[0] = socket_link_connection_location(*link.fromnode, *link.fromsock, link); + points[3] = socket_link_connection_location(*link.tonode, *link.tosock, link); calculate_inner_link_bezier_points(points); return points; } @@ -1757,7 +1762,7 @@ static void nodelink_batch_init() /* curve strip */ for (int i = 0; i < LINK_RESOL; i++) { - uv[0] = 255 * (i / (float)(LINK_RESOL - 1)); + uv[0] = 255 * (i / float(LINK_RESOL - 1)); uv[1] = 0; set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp); uv[1] = 255; @@ -1901,7 +1906,7 @@ static void nodelink_batch_draw(const SpaceNode &snode) GPU_blend(GPU_BLEND_NONE); } -void nodelink_batch_start(SpaceNode &UNUSED(snode)) +void nodelink_batch_start(SpaceNode & /*snode*/) { g_batch_link.enabled = true; } @@ -1976,7 +1981,7 @@ static void node_draw_link_end_marker(const float2 center, UI_draw_roundbox_corner_set(UI_CNR_ALL); UI_draw_roundbox_4fv(&rect, true, radius, color); - /* Roundbox disables alpha. Reenable it for node links that are drawn after this one. */ + /* Round-box disables alpha. Re-enable it for node links that are drawn after this one. */ GPU_blend(GPU_BLEND_ALPHA); } @@ -2209,8 +2214,11 @@ static std::array<float2, 4> node_link_bezier_points_dragged(const SpaceNode &sn { const float2 cursor = snode.runtime->cursor * UI_DPI_FAC; std::array<float2, 4> points; - points[0] = link.fromsock ? socket_link_connection_location(*link.fromsock, link) : cursor; - points[3] = link.tosock ? socket_link_connection_location(*link.tosock, link) : cursor; + points[0] = link.fromsock ? + socket_link_connection_location(*link.fromnode, *link.fromsock, link) : + cursor; + points[3] = link.tosock ? socket_link_connection_location(*link.tonode, *link.tosock, link) : + cursor; calculate_inner_link_bezier_points(points); return points; } diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc index f1387da97b5..b2dfbd47aeb 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,136 @@ 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_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) { @@ -125,7 +273,9 @@ static void gather_socket_link_operations(bNodeTree &node_tree, if (!(node_type->poll && node_type->poll(node_type, &node_tree, &disabled_hint))) { continue; } - + if (StringRefNull(node_type->ui_name).endswith("(Legacy)")) { + continue; + } if (node_type->gather_link_search_ops) { nodes::GatherLinkSearchOpParams params{*node_type, node_tree, socket, search_link_ops}; node_type->gather_link_search_ops(params); @@ -156,15 +306,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(); @@ -214,7 +369,7 @@ static void link_drag_search_exec_fn(bContext *C, void *arg1, void *arg2) bNode *new_node = new_nodes.first(); new_node->locx = storage.cursor.x / UI_DPI_FAC; - new_node->locy = storage.cursor.y / UI_DPI_FAC + 20 * UI_DPI_FAC; + new_node->locy = storage.cursor.y / UI_DPI_FAC + 20; if (storage.in_out() == SOCK_IN) { new_node->locx -= new_node->width; } @@ -245,9 +400,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 +417,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..41f70a6d8cf 100644 --- a/source/blender/editors/space_node/node_add.cc +++ b/source/blender/editors/space_node/node_add.cc @@ -30,6 +30,7 @@ #include "DEG_depsgraph_build.h" +#include "ED_asset.h" #include "ED_node.h" /* own include */ #include "ED_render.h" #include "ED_screen.h" @@ -194,15 +195,16 @@ static int add_reroute_exec(bContext *C, wmOperator *op) } /* Place the new reroute at the average location of all connected cuts. */ - const float2 loc = std::accumulate(cuts.values().begin(), cuts.values().end(), float2(0)) / - cuts.size() / UI_DPI_FAC; - reroute->locx = loc.x; - reroute->locy = loc.y; + const float2 insert_point = std::accumulate( + cuts.values().begin(), cuts.values().end(), float2(0)) / + cuts.size(); + reroute->locx = insert_point.x / UI_DPI_FAC; + reroute->locy = insert_point.y / UI_DPI_FAC; /* Attach the reroute node to frame nodes behind it. */ for (const int i : frame_nodes.index_range()) { bNode *frame_node = frame_nodes.last(i); - if (BLI_rctf_isect_pt_v(&frame_node->totr, loc)) { + if (BLI_rctf_isect_pt_v(&frame_node->totr, insert_point)) { nodeAttachNode(reroute, frame_node); break; } @@ -243,38 +245,36 @@ void NODE_OT_add_reroute(wmOperatorType *ot) /** \name Add Node Group Operator * \{ */ -static bNodeTree *node_add_group_get_and_poll_group_node_tree(Main *bmain, - wmOperator *op, - bNodeTree *ntree) +static bool node_group_add_poll(const bNodeTree &node_tree, + const bNodeTree &node_group, + ReportList &reports) { - bNodeTree *node_group = reinterpret_cast<bNodeTree *>( - WM_operator_properties_id_lookup_from_name_or_session_uuid(bmain, op->ptr, ID_NT)); - if (!node_group) { - return nullptr; + if (node_group.type != node_tree.type) { + return false; } const char *disabled_hint = nullptr; - if ((node_group->type != ntree->type) || !nodeGroupPoll(ntree, node_group, &disabled_hint)) { + if (!nodeGroupPoll(&node_tree, &node_group, &disabled_hint)) { if (disabled_hint) { - BKE_reportf(op->reports, + BKE_reportf(&reports, RPT_ERROR, "Can not add node group '%s' to '%s':\n %s", - node_group->id.name + 2, - ntree->id.name + 2, + node_group.id.name + 2, + node_tree.id.name + 2, disabled_hint); } else { - BKE_reportf(op->reports, + BKE_reportf(&reports, RPT_ERROR, "Can not add node group '%s' to '%s'", - node_group->id.name + 2, - ntree->id.name + 2); + node_group.id.name + 2, + node_tree.id.name + 2); } - return nullptr; + return false; } - return node_group; + return true; } static int node_add_group_exec(bContext *C, wmOperator *op) @@ -283,10 +283,14 @@ static int node_add_group_exec(bContext *C, wmOperator *op) SpaceNode *snode = CTX_wm_space_node(C); bNodeTree *ntree = snode->edittree; - bNodeTree *node_group = node_add_group_get_and_poll_group_node_tree(bmain, op, ntree); + bNodeTree *node_group = reinterpret_cast<bNodeTree *>( + WM_operator_properties_id_lookup_from_name_or_session_uuid(bmain, op->ptr, ID_NT)); if (!node_group) { return OPERATOR_CANCELLED; } + if (!node_group_add_poll(*ntree, *node_group, *op->reports)) { + return OPERATOR_CANCELLED; + } ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); @@ -319,9 +323,8 @@ static bool node_add_group_poll(bContext *C) } const SpaceNode *snode = CTX_wm_space_node(C); if (snode->edittree->type == NTREE_CUSTOM) { - CTX_wm_operator_poll_msg_set(C, - "This node editor displays a custom (Python defined) node tree. " - "Dropping node groups isn't supported for this"); + CTX_wm_operator_poll_msg_set( + C, "Adding node groups isn't supported for custom (Python defined) node trees"); return false; } return true; @@ -366,6 +369,105 @@ void NODE_OT_add_group(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Add Node Group Asset Operator + * \{ */ + +static bool add_node_group_asset(const bContext &C, + const AssetLibraryReference &library_ref, + const AssetHandle asset, + ReportList &reports) +{ + Main &bmain = *CTX_data_main(&C); + SpaceNode &snode = *CTX_wm_space_node(&C); + bNodeTree &edit_tree = *snode.edittree; + + bNodeTree *node_group = reinterpret_cast<bNodeTree *>( + asset::get_local_id_from_asset_or_append_and_reuse(bmain, library_ref, asset)); + if (!node_group) { + return false; + } + if (!node_group_add_poll(edit_tree, *node_group, reports)) { + /* Remove the node group if it was newly appended but can't be added to the tree. */ + id_us_plus(&node_group->id); + BKE_id_free_us(&bmain, node_group); + return false; + } + + ED_preview_kill_jobs(CTX_wm_manager(&C), CTX_data_main(&C)); + + bNode *group_node = add_node( + C, ntreeTypeFind(node_group->idname)->group_idname, snode.runtime->cursor); + if (!group_node) { + BKE_report(&reports, RPT_WARNING, "Could not add node group"); + return false; + } + /* By default, don't show the data-block selector since it's not usually necessary for assets. */ + group_node->flag &= ~NODE_OPTIONS; + + group_node->id = &node_group->id; + id_us_plus(group_node->id); + BKE_ntree_update_tag_node_property(&edit_tree, group_node); + + nodeSetActive(&edit_tree, group_node); + ED_node_tree_propagate_change(&C, &bmain, nullptr); + DEG_relations_tag_update(&bmain); + + return true; +} + +static int node_add_group_asset_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + ARegion ®ion = *CTX_wm_region(C); + SpaceNode &snode = *CTX_wm_space_node(C); + + const AssetLibraryReference *library_ref = CTX_wm_asset_library_ref(C); + if (!library_ref) { + return OPERATOR_CANCELLED; + } + bool is_valid; + const AssetHandle handle = CTX_wm_asset_handle(C, &is_valid); + if (!is_valid) { + return OPERATOR_CANCELLED; + } + + /* Convert mouse coordinates to v2d space. */ + UI_view2d_region_to_view(®ion.v2d, + event->mval[0], + event->mval[1], + &snode.runtime->cursor[0], + &snode.runtime->cursor[1]); + + snode.runtime->cursor /= UI_DPI_FAC; + + if (!add_node_group_asset(*C, *library_ref, handle, *op->reports)) { + return OPERATOR_CANCELLED; + } + + 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); + + return OPERATOR_FINISHED; +} + +void NODE_OT_add_group_asset(wmOperatorType *ot) +{ + ot->name = "Add Node Group Asset"; + ot->description = "Add a node group asset to the active node tree"; + ot->idname = "NODE_OT_add_group_asset"; + + ot->invoke = node_add_group_asset_invoke; + ot->poll = node_add_group_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Add Node Object Operator * \{ */ @@ -392,7 +494,7 @@ static int node_add_object_exec(bContext *C, wmOperator *op) bNodeSocket *sock = nodeFindSocket(object_node, SOCK_IN, "Object"); if (!sock) { - BKE_report(op->reports, RPT_WARNING, "Could not find node object socket"); + BLI_assert_unreachable(); return OPERATOR_CANCELLED; } @@ -777,12 +879,14 @@ static int new_node_tree_exec(bContext *C, wmOperator *op) ED_node_tree_update(C); } + WM_event_add_notifier(C, NC_NODE | NA_ADDED, nullptr); + return OPERATOR_FINISHED; } -static const EnumPropertyItem *new_node_tree_type_itemf(bContext *UNUSED(C), - PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), +static const EnumPropertyItem *new_node_tree_type_itemf(bContext * /*C*/, + PointerRNA * /*ptr*/, + PropertyRNA * /*prop*/, bool *r_free) { return rna_node_tree_type_itemf(nullptr, nullptr, r_free); @@ -810,4 +914,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_draw.cc b/source/blender/editors/space_node/node_draw.cc index 3a8e5d0aed6..5ae6573df7c 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -60,6 +60,7 @@ #include "ED_node.h" #include "ED_screen.h" #include "ED_space_api.h" +#include "ED_viewer_path.hh" #include "UI_interface.hh" #include "UI_resources.h" @@ -95,6 +96,14 @@ extern void ui_draw_dropshadow( */ struct TreeDrawContext { /** + * Whether a viewer node is active in geometry nodes can not be determined by a flag on the node + * alone. That's because if the node group with the viewer is used multiple times, it's only + * active in one of these cases. + * The active node is cached here to avoid doing the more expensive check for every viewer node + * in the tree. + */ + const bNode *active_geometry_nodes_viewer = nullptr; + /** * Geometry nodes logs various data during execution. The logged data that corresponds to the * currently drawn node tree can be retrieved from the log below. */ @@ -432,7 +441,7 @@ static void node_update_basis(const bContext &C, float aspect = 1.0f; if (node.preview_xsize && node.preview_ysize) { - aspect = (float)node.preview_ysize / (float)node.preview_xsize; + aspect = float(node.preview_ysize) / float(node.preview_xsize); } dy -= NODE_DYS / 2; @@ -597,7 +606,7 @@ static void node_update_hidden(bNode &node, uiBlock &block) float hiddenrad = HIDDEN_RAD; float tot = MAX2(totin, totout); if (tot > 4) { - hiddenrad += 5.0f * (float)(tot - 4); + hiddenrad += 5.0f * float(tot - 4); } node.totr.xmin = loc.x; @@ -606,7 +615,7 @@ static void node_update_hidden(bNode &node, uiBlock &block) node.totr.ymin = node.totr.ymax - 2 * hiddenrad; /* Output sockets. */ - float rad = (float)M_PI / (1.0f + (float)totout); + float rad = float(M_PI) / (1.0f + float(totout)); float drad = rad; LISTBASE_FOREACH (bNodeSocket *, socket, &node.outputs) { @@ -619,7 +628,7 @@ static void node_update_hidden(bNode &node, uiBlock &block) } /* Input sockets. */ - rad = drad = -(float)M_PI / (1.0f + (float)totin); + rad = drad = -float(M_PI) / (1.0f + float(totin)); LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) { if (!nodeSocketIsHidden(socket)) { @@ -639,15 +648,19 @@ static void node_update_hidden(bNode &node, uiBlock &block) node.totr.ymax); } -static int node_get_colorid(const bNode &node) +static int node_get_colorid(TreeDrawContext &tree_draw_ctx, const bNode &node) { const int nclass = (node.typeinfo->ui_class == nullptr) ? node.typeinfo->nclass : node.typeinfo->ui_class(&node); switch (nclass) { case NODE_CLASS_INPUT: return TH_NODE_INPUT; - case NODE_CLASS_OUTPUT: + case NODE_CLASS_OUTPUT: { + if (node.type == GEO_NODE_VIEWER) { + return &node == tree_draw_ctx.active_geometry_nodes_viewer ? TH_NODE_OUTPUT : TH_NODE; + } return (node.flag & NODE_DO_OUTPUT) ? TH_NODE_OUTPUT : TH_NODE; + } case NODE_CLASS_CONVERTER: return TH_NODE_CONVERTER; case NODE_CLASS_OP_COLOR: @@ -1066,7 +1079,7 @@ static bool node_socket_has_tooltip(const bNodeTree &ntree, const bNodeSocket &s static char *node_socket_get_tooltip(const bContext *C, const bNodeTree *ntree, - const bNode *UNUSED(node), + const bNode * /*node*/, const bNodeSocket *socket) { SpaceNode *snode = CTX_wm_space_node(C); @@ -1108,7 +1121,7 @@ static char *node_socket_get_tooltip(const bContext *C, return BLI_strdup(output.str().c_str()); } -static void node_socket_add_tooltip_in_node_editor(TreeDrawContext *UNUSED(tree_draw_ctx), +static void node_socket_add_tooltip_in_node_editor(TreeDrawContext * /*tree_draw_ctx*/, const bNodeTree *ntree, const bNode *node, const bNodeSocket *sock, @@ -1125,7 +1138,7 @@ static void node_socket_add_tooltip_in_node_editor(TreeDrawContext *UNUSED(tree_ uiLayoutSetTooltipFunc( layout, - [](bContext *C, void *argN, const char *UNUSED(tip)) { + [](bContext *C, void *argN, const char * /*tip*/) { SocketTooltipData *data = static_cast<SocketTooltipData *>(argN); return node_socket_get_tooltip(C, data->ntree, data->node, data->socket); }, @@ -1204,7 +1217,7 @@ static void node_socket_draw_nested(const bContext &C, UI_but_func_tooltip_set( but, - [](bContext *C, void *argN, const char *UNUSED(tip)) { + [](bContext *C, void *argN, const char * /*tip*/) { SocketTooltipData *data = (SocketTooltipData *)argN; return node_socket_get_tooltip(C, data->ntree, data->node, data->socket); }, @@ -1293,20 +1306,20 @@ static void node_draw_preview(bNodePreview *preview, rctf *prv) { float xrect = BLI_rctf_size_x(prv); float yrect = BLI_rctf_size_y(prv); - float xscale = xrect / ((float)preview->xsize); - float yscale = yrect / ((float)preview->ysize); + float xscale = xrect / float(preview->xsize); + float yscale = yrect / float(preview->ysize); float scale; /* Uniform scale and offset. */ rctf draw_rect = *prv; if (xscale < yscale) { - float offset = 0.5f * (yrect - ((float)preview->ysize) * xscale); + float offset = 0.5f * (yrect - float(preview->ysize) * xscale); draw_rect.ymin += offset; draw_rect.ymax -= offset; scale = xscale; } else { - float offset = 0.5f * (xrect - ((float)preview->xsize) * yscale); + float offset = 0.5f * (xrect - float(preview->xsize) * yscale); draw_rect.xmin += offset; draw_rect.xmax -= offset; scale = yscale; @@ -1613,7 +1626,7 @@ struct NodeErrorsTooltipData { Span<geo_log::NodeWarning> warnings; }; -static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char *UNUSED(tip)) +static char *node_errors_tooltip_fn(bContext * /*C*/, void *argN, const char * /*tip*/) { NodeErrorsTooltipData &data = *(NodeErrorsTooltipData *)argN; @@ -1770,10 +1783,10 @@ struct NodeExtraInfoRow { }; struct NamedAttributeTooltipArg { - Map<std::string, geo_log::NamedAttributeUsage> usage_by_attribute; + Map<StringRefNull, geo_log::NamedAttributeUsage> usage_by_attribute; }; -static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char *UNUSED(tip)) +static char *named_attribute_tooltip(bContext * /*C*/, void *argN, const char * /*tip*/) { NamedAttributeTooltipArg &arg = *static_cast<NamedAttributeTooltipArg *>(argN); @@ -1824,7 +1837,7 @@ static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char } static NodeExtraInfoRow row_from_used_named_attribute( - const Map<std::string, geo_log::NamedAttributeUsage> &usage_by_attribute_name) + const Map<StringRefNull, geo_log::NamedAttributeUsage> &usage_by_attribute_name) { const int attributes_num = usage_by_attribute_name.size(); @@ -1930,8 +1943,8 @@ static void node_draw_extra_info_row(const bNode &node, UI_BTYPE_BUT, 0, extra_info_row.icon, - (int)but_icon_left, - (int)(rect.ymin + row * (20.0f * U.dpi_fac)), + int(but_icon_left), + int(rect.ymin + row * (20.0f * U.dpi_fac)), but_icon_width, UI_UNIT_Y, nullptr, @@ -1956,10 +1969,10 @@ static void node_draw_extra_info_row(const bNode &node, UI_BTYPE_LABEL, 0, extra_info_row.text.c_str(), - (int)but_text_left, - (int)(rect.ymin + row * (20.0f * U.dpi_fac)), - (short)but_text_width, - (short)NODE_DY, + int(but_text_left), + int(rect.ymin + row * (20.0f * U.dpi_fac)), + short(but_text_width), + short(NODE_DY), nullptr, 0, 0, @@ -2055,7 +2068,7 @@ static void node_draw_basis(const bContext &C, const rctf &rct = node.totr; float color[4]; - int color_id = node_get_colorid(node); + int color_id = node_get_colorid(tree_draw_ctx, node); GPU_line_width(1.0f); @@ -2132,6 +2145,9 @@ static void node_draw_basis(const bContext &C, 0, ""); UI_but_func_set(but, node_toggle_button_cb, &node, (void *)"NODE_OT_group_edit"); + if (node.id) { + UI_but_icon_indicator_number_set(but, ID_REAL_USERS(node.id)); + } UI_block_emboss_set(&block, UI_EMBOSS); } if (node.type == NODE_CUSTOM && node.typeinfo->ui_icon != ICON_NONE) { @@ -2153,6 +2169,29 @@ static void node_draw_basis(const bContext &C, ""); UI_block_emboss_set(&block, UI_EMBOSS); } + if (node.type == GEO_NODE_VIEWER) { + const bool is_active = &node == tree_draw_ctx.active_geometry_nodes_viewer; + iconofs -= iconbutw; + UI_block_emboss_set(&block, UI_EMBOSS_NONE); + uiBut *but = uiDefIconBut(&block, + UI_BTYPE_BUT, + 0, + is_active ? ICON_HIDE_OFF : ICON_HIDE_ON, + iconofs, + rct.ymax - NODE_DY, + iconbutw, + UI_UNIT_Y, + nullptr, + 0, + 0, + 0, + 0, + ""); + /* Selection implicitly activates the node. */ + const char *operator_idname = is_active ? "NODE_OT_deactivate_viewer" : "NODE_OT_select"; + UI_but_func_set(but, node_toggle_button_cb, &node, (void *)operator_idname); + UI_block_emboss_set(&block, UI_EMBOSS); + } node_add_error_message_button(tree_draw_ctx, node, block, rct, iconofs); @@ -2195,10 +2234,10 @@ static void node_draw_basis(const bContext &C, UI_BTYPE_LABEL, 0, showname, - (int)(rct.xmin + NODE_MARGIN_X + 0.4f), - (int)(rct.ymax - NODE_DY), - (short)(iconofs - rct.xmin - (18.0f * U.dpi_fac)), - (short)NODE_DY, + int(rct.xmin + NODE_MARGIN_X + 0.4f), + int(rct.ymax - NODE_DY), + short(iconofs - rct.xmin - (18.0f * U.dpi_fac)), + short(NODE_DY), nullptr, 0, 0, @@ -2341,7 +2380,7 @@ static void node_draw_hidden(const bContext &C, float scale; UI_view2d_scale_get(&v2d, &scale, nullptr); - const int color_id = node_get_colorid(node); + const int color_id = node_get_colorid(tree_draw_ctx, node); node_draw_extra_info_panel(tree_draw_ctx, snode, node, block); @@ -2425,8 +2464,8 @@ static void node_draw_hidden(const bContext &C, showname, round_fl_to_int(rct.xmin + NODE_MARGIN_X), round_fl_to_int(centy - NODE_DY * 0.5f), - (short)(BLI_rctf_size_x(&rct) - ((18.0f + 12.0f) * U.dpi_fac)), - (short)NODE_DY, + short(BLI_rctf_size_x(&rct) - ((18.0f + 12.0f) * U.dpi_fac)), + short(NODE_DY), nullptr, 0, 0, @@ -2698,7 +2737,8 @@ static void node_update_nodetree(const bContext &C, } } -static void frame_node_draw_label(const bNodeTree &ntree, +static void frame_node_draw_label(TreeDrawContext &tree_draw_ctx, + const bNodeTree &ntree, const bNode &node, const SpaceNode &snode) { @@ -2714,15 +2754,15 @@ static void frame_node_draw_label(const bNodeTree &ntree, BLF_enable(fontid, BLF_ASPECT); BLF_aspect(fontid, aspect, aspect, 1.0f); /* clamp otherwise it can suck up a LOT of memory */ - BLF_size(fontid, MIN2(24.0f, font_size) * U.pixelsize, U.dpi); + BLF_size(fontid, MIN2(24.0f, font_size) * U.dpi_fac); /* title color */ - int color_id = node_get_colorid(node); + int color_id = node_get_colorid(tree_draw_ctx, node); uchar color[3]; UI_GetThemeColorBlendShade3ubv(TH_TEXT, color_id, 0.4f, 10, color); BLF_color3ubv(fontid, color); - const float margin = (float)(NODE_DY / 4); + const float margin = float(NODE_DY / 4); const float width = BLF_width(fontid, label, sizeof(label)); const float ascender = BLF_ascender(fontid); const int label_height = ((margin / aspect) + (ascender * aspect)); @@ -2831,7 +2871,7 @@ static void frame_node_draw(const bContext &C, } /* label and text */ - frame_node_draw_label(ntree, node, snode); + frame_node_draw_label(tree_draw_ctx, ntree, node, snode); node_draw_extra_info_panel(tree_draw_ctx, snode, node, block); @@ -2866,7 +2906,7 @@ static void reroute_node_draw( x, y, width, - (short)NODE_DY, + short(NODE_DY), nullptr, 0, 0, @@ -2974,7 +3014,7 @@ static void node_draw_nodetree(const bContext &C, } } -/* Draw the breadcrumb on the bottom of the editor. */ +/* Draw the breadcrumb on the top of the editor. */ static void draw_tree_path(const bContext &C, ARegion ®ion) { using namespace blender; @@ -3013,7 +3053,7 @@ static void snode_setup_v2d(SpaceNode &snode, ARegion ®ion, const float2 &cen UI_view2d_view_ortho(&v2d); /* Aspect + font, set each time. */ - snode.runtime->aspect = BLI_rctf_size_x(&v2d.cur) / (float)region.winx; + snode.runtime->aspect = BLI_rctf_size_x(&v2d.cur) / float(region.winx); // XXX snode->curfont = uiSetCurFont_ext(snode->aspect); } @@ -3036,6 +3076,9 @@ static void draw_nodetree(const bContext &C, tree_draw_ctx.geo_tree_log->ensure_node_warnings(); tree_draw_ctx.geo_tree_log->ensure_node_run_time(); } + WorkSpace *workspace = CTX_wm_workspace(&C); + tree_draw_ctx.active_geometry_nodes_viewer = viewer_path::find_geometry_nodes_viewer( + workspace->viewer_path, *snode); } node_update_nodetree(C, tree_draw_ctx, ntree, nodes, blocks); diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index f07a1205c6b..7982b47f363 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -43,7 +43,7 @@ #include "ED_render.h" #include "ED_screen.h" #include "ED_select_utils.h" -#include "ED_spreadsheet.h" +#include "ED_viewer_path.hh" #include "RNA_access.h" #include "RNA_define.h" @@ -89,8 +89,8 @@ struct CompoJob { Depsgraph *compositor_depsgraph; bNodeTree *localtree; /* Jon system integration. */ - const short *stop; - short *do_update; + const bool *stop; + bool *do_update; float *progress; }; @@ -166,7 +166,7 @@ static int compo_get_recalc_flags(const bContext *C) } /* called by compo, only to check job 'stop' value */ -static int compo_breakjob(void *cjv) +static bool compo_breakjob(void *cjv) { CompoJob *cj = (CompoJob *)cjv; @@ -179,7 +179,7 @@ static int compo_breakjob(void *cjv) } /* called by compo, wmJob sends notifier */ -static void compo_statsdrawjob(void *cjv, const char *UNUSED(str)) +static void compo_statsdrawjob(void *cjv, const char * /*str*/) { CompoJob *cj = (CompoJob *)cjv; @@ -234,7 +234,7 @@ static void compo_initjob(void *cjv) } /* called before redraw notifiers, it moves finished previews over */ -static void compo_updatejob(void *UNUSED(cjv)) +static void compo_updatejob(void * /*cjv*/) { WM_main_add_notifier(NC_SCENE | ND_COMPO_RESULT, nullptr); } @@ -250,8 +250,8 @@ static void compo_progressjob(void *cjv, float progress) static void compo_startjob(void *cjv, /* Cannot be const, this function implements wm_jobs_start_callback. * NOLINTNEXTLINE: readability-non-const-parameter. */ - short *stop, - short *do_update, + bool *stop, + bool *do_update, float *progress) { CompoJob *cj = (CompoJob *)cjv; @@ -443,11 +443,11 @@ void ED_node_tree_propagate_change(const bContext *C, Main *bmain, bNodeTree *ro } NodeTreeUpdateExtraParams params = {nullptr}; - params.tree_changed_fn = [](ID *id, bNodeTree *ntree, void *UNUSED(user_data)) { + params.tree_changed_fn = [](ID *id, bNodeTree *ntree, void * /*user_data*/) { blender::ed::space_node::send_notifiers_after_tree_change(id, ntree); DEG_id_tag_update(&ntree->id, ID_RECALC_COPY_ON_WRITE); }; - params.tree_output_changed_fn = [](ID *UNUSED(id), bNodeTree *ntree, void *UNUSED(user_data)) { + params.tree_output_changed_fn = [](ID * /*id*/, bNodeTree *ntree, void * /*user_data*/) { DEG_id_tag_update(&ntree->id, ID_RECALC_NTREE_OUTPUT); }; @@ -502,6 +502,7 @@ void ED_node_shader_default(const bContext *C, ID *id) } ma->nodetree = ntreeCopyTree(bmain, ma_default->nodetree); + ma->nodetree->owner_id = &ma->id; BKE_ntree_update_main_tree(bmain, ma->nodetree, nullptr); } else if (ELEM(GS(id->name), ID_WO, ID_LA)) { @@ -713,10 +714,12 @@ void ED_node_set_active( /* Sync to active texpaint slot, otherwise we can end up painting on a different slot * than we are looking at. */ if (ma->texpaintslot) { - Image *image = (Image *)node->id; - for (int i = 0; i < ma->tot_slots; i++) { - if (ma->texpaintslot[i].ima == image) { - ma->paint_active_slot = i; + if (node->id != nullptr && GS(node->id->name) == ID_IM) { + Image *image = (Image *)node->id; + for (int i = 0; i < ma->tot_slots; i++) { + if (ma->texpaintslot[i].ima == image) { + ma->paint_active_slot = i; + } } } } @@ -732,25 +735,9 @@ void ED_node_set_active( /* Sync to Image Editor under the following conditions: * - current image is not pinned * - current image is not a Render Result or ViewerNode (want to keep looking at these) */ - Image *image = (Image *)node->id; - wmWindowManager *wm = (wmWindowManager *)bmain->wm.first; - LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { - const bScreen *screen = WM_window_get_active_screen(win); - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { - if (sl->spacetype != SPACE_IMAGE) { - continue; - } - SpaceImage *sima = (SpaceImage *)sl; - if (sima->pin) { - continue; - } - if (sima->image && ELEM(sima->image->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { - continue; - } - ED_space_image_set(bmain, sima, image, true); - } - } + if (node->id != nullptr && GS(node->id->name) == ID_IM) { + Image *image = (Image *)node->id; + ED_space_image_sync(bmain, image, true); } if (r_active_texture_changed) { @@ -816,14 +803,14 @@ void ED_node_set_active( } } node->flag |= NODE_DO_OUTPUT; - ED_spreadsheet_context_paths_set_geometry_node(bmain, snode, node); } + blender::ed::viewer_path::activate_geometry_node(*bmain, *snode, *node); } } } } -void ED_node_post_apply_transform(bContext *UNUSED(C), bNodeTree *UNUSED(ntree)) +void ED_node_post_apply_transform(bContext * /*C*/, bNodeTree * /*ntree*/) { /* XXX This does not work due to layout functions relying on node->block, * which only exists during actual drawing. Can we rely on valid totr rects? @@ -1356,12 +1343,15 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) SpaceNode *snode = CTX_wm_space_node(C); bNodeTree *ntree = snode->edittree; const bool keep_inputs = RNA_boolean_get(op->ptr, "keep_inputs"); + bool linked = RNA_boolean_get(op->ptr, "linked") || ((U.dupflag & USER_DUP_NTREE) == 0); + const bool dupli_node_tree = !linked; bool changed = false; ED_preview_kill_jobs(CTX_wm_manager(C), bmain); Map<const bNode *, bNode *> node_map; Map<const bNodeSocket *, bNodeSocket *> socket_map; + Map<const ID *, ID *> duplicated_node_groups; bNode *lastnode = (bNode *)ntree->nodes.last; LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { @@ -1369,6 +1359,18 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) bNode *new_node = bke::node_copy_with_mapping( ntree, *node, LIB_ID_COPY_DEFAULT, true, socket_map); node_map.add_new(node, new_node); + + if (node->id && dupli_node_tree) { + ID *new_group = duplicated_node_groups.lookup_or_add_cb(node->id, [&]() { + ID *new_group = BKE_id_copy(bmain, node->id); + /* Remove user added by copying. */ + id_us_min(new_group); + return new_group; + }); + id_us_plus(new_group); + id_us_min(new_node->id); + new_node->id = new_group; + } changed = true; } @@ -1457,6 +1459,8 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) void NODE_OT_duplicate(wmOperatorType *ot) { + PropertyRNA *prop; + /* identifiers */ ot->name = "Duplicate Nodes"; ot->description = "Duplicate selected nodes"; @@ -1471,12 +1475,19 @@ void NODE_OT_duplicate(wmOperatorType *ot) RNA_def_boolean( ot->srna, "keep_inputs", false, "Keep Inputs", "Keep the input links to duplicated nodes"); + + prop = RNA_def_boolean(ot->srna, + "linked", + true, + "Linked", + "Duplicate node but not node trees, linking to the original data"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /* XXX: some code needing updating to operators. */ /* goes over all scenes, reads render layers */ -static int node_read_viewlayers_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_read_viewlayers_exec(bContext *C, wmOperator * /*op*/) { Main *bmain = CTX_data_main(C); SpaceNode *snode = CTX_wm_space_node(C); @@ -1524,7 +1535,7 @@ void NODE_OT_read_viewlayers(wmOperatorType *ot) ot->flag = 0; } -int node_render_changed_exec(bContext *C, wmOperator *UNUSED(op)) +int node_render_changed_exec(bContext *C, wmOperator * /*op*/) { Scene *sce = CTX_data_scene(C); @@ -1631,7 +1642,7 @@ static void node_flag_toggle_exec(SpaceNode *snode, int toggle_flag) } } -static int node_hide_toggle_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_hide_toggle_exec(bContext *C, wmOperator * /*op*/) { SpaceNode *snode = CTX_wm_space_node(C); @@ -1662,7 +1673,7 @@ void NODE_OT_hide_toggle(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -static int node_preview_toggle_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_preview_toggle_exec(bContext *C, wmOperator * /*op*/) { SpaceNode *snode = CTX_wm_space_node(C); @@ -1695,7 +1706,47 @@ void NODE_OT_preview_toggle(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -static int node_options_toggle_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_deactivate_viewer_exec(bContext *C, wmOperator * /*op*/) +{ + SpaceNode &snode = *CTX_wm_space_node(C); + WorkSpace &workspace = *CTX_wm_workspace(C); + + bNode *active_viewer = viewer_path::find_geometry_nodes_viewer(workspace.viewer_path, snode); + + LISTBASE_FOREACH (bNode *, node, &snode.edittree->nodes) { + if (node->type != GEO_NODE_VIEWER) { + continue; + } + if (!(node->flag & SELECT)) { + continue; + } + if (node == active_viewer) { + node->flag &= ~NODE_DO_OUTPUT; + BKE_ntree_update_tag_node_property(snode.edittree, node); + } + } + + ED_node_tree_propagate_change(C, CTX_data_main(C), snode.edittree); + + return OPERATOR_FINISHED; +} + +void NODE_OT_deactivate_viewer(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Deactivate Viewer Node"; + ot->description = "Deactivate selected viewer node in geometry nodes"; + ot->idname = __func__; + + /* callbacks */ + ot->exec = node_deactivate_viewer_exec; + ot->poll = ED_operator_node_active; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int node_options_toggle_exec(bContext *C, wmOperator * /*op*/) { SpaceNode *snode = CTX_wm_space_node(C); @@ -1726,7 +1777,7 @@ void NODE_OT_options_toggle(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -static int node_socket_toggle_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_socket_toggle_exec(bContext *C, wmOperator * /*op*/) { SpaceNode *snode = CTX_wm_space_node(C); @@ -1782,7 +1833,7 @@ void NODE_OT_hide_socket_toggle(wmOperatorType *ot) /** \name Node Mute Operator * \{ */ -static int node_mute_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_mute_exec(bContext *C, wmOperator * /*op*/) { Main *bmain = CTX_data_main(C); SpaceNode *snode = CTX_wm_space_node(C); @@ -1822,7 +1873,7 @@ void NODE_OT_mute_toggle(wmOperatorType *ot) /** \name Node Delete Operator * \{ */ -static int node_delete_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_delete_exec(bContext *C, wmOperator * /*op*/) { Main *bmain = CTX_data_main(C); SpaceNode *snode = CTX_wm_space_node(C); @@ -1872,7 +1923,7 @@ static bool node_switch_view_poll(bContext *C) return false; } -static int node_switch_view_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_switch_view_exec(bContext *C, wmOperator * /*op*/) { SpaceNode *snode = CTX_wm_space_node(C); @@ -1909,7 +1960,7 @@ void NODE_OT_switch_view_update(wmOperatorType *ot) /** \name Node Delete with Reconnect Operator * \{ */ -static int node_delete_reconnect_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_delete_reconnect_exec(bContext *C, wmOperator * /*op*/) { Main *bmain = CTX_data_main(C); SpaceNode *snode = CTX_wm_space_node(C); @@ -2003,7 +2054,7 @@ void NODE_OT_output_file_add_socket(wmOperatorType *ot) /** \name Node Multi File Output Remove Socket Operator * \{ */ -static int node_output_file_remove_active_socket_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_output_file_remove_active_socket_exec(bContext *C, wmOperator * /*op*/) { SpaceNode *snode = CTX_wm_space_node(C); PointerRNA ptr = CTX_data_pointer_get(C, "node"); @@ -2129,7 +2180,7 @@ void NODE_OT_output_file_move_active_socket(wmOperatorType *ot) /** \name Node Copy Node Color Operator * \{ */ -static int node_copy_color_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_copy_color_exec(bContext *C, wmOperator * /*op*/) { SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &ntree = *snode.edittree; @@ -2177,7 +2228,7 @@ void NODE_OT_node_copy_color(wmOperatorType *ot) /** \name Node Copy to Clipboard Operator * \{ */ -static int node_clipboard_copy_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_clipboard_copy_exec(bContext *C, wmOperator * /*op*/) { SpaceNode *snode = CTX_wm_space_node(C); bNodeTree *ntree = snode->edittree; @@ -2230,6 +2281,7 @@ static int node_clipboard_copy_exec(bContext *C, wmOperator *UNUSED(op)) newlink->tosock = socket_map.lookup(link->tosock); newlink->fromnode = node_map.lookup(link->fromnode); newlink->fromsock = socket_map.lookup(link->fromsock); + newlink->multi_input_socket_index = link->multi_input_socket_index; BKE_node_clipboard_add_link(newlink); } @@ -2351,11 +2403,19 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op) } LISTBASE_FOREACH (bNodeLink *, link, clipboard_links_lb) { - nodeAddLink(ntree, - node_map.lookup(link->fromnode), - socket_map.lookup(link->fromsock), - node_map.lookup(link->tonode), - socket_map.lookup(link->tosock)); + bNodeLink *new_link = nodeAddLink(ntree, + node_map.lookup(link->fromnode), + socket_map.lookup(link->fromsock), + node_map.lookup(link->tonode), + socket_map.lookup(link->tosock)); + new_link->multi_input_socket_index = link->multi_input_socket_index; + } + + ntree->ensure_topology_cache(); + + for (bNode *new_node : node_map.values()) { + /* Update multi input socket indices in case all connected nodes weren't copied. */ + update_multi_input_indices_for_removed_links(*new_node); } Main *bmain = CTX_data_main(C); @@ -2532,7 +2592,7 @@ static int ntree_socket_change_type_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } - /* Don't handle subtypes for now. */ + /* Don't handle sub-types for now. */ nodeModifySocketType(ntree, nullptr, iosock, socket_type->idname); /* Need the extra update here because the loop above does not check for valid links in the node @@ -2570,8 +2630,8 @@ static bool socket_change_poll_type(void *userdata, bNodeSocketType *socket_type } static const EnumPropertyItem *socket_change_type_itemf(bContext *C, - PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), + PointerRNA * /*ptr*/, + PropertyRNA * /*prop*/, bool *r_free) { if (!C) { @@ -2854,8 +2914,8 @@ static void viewer_border_corner_to_backdrop(SpaceNode *snode, float bufx = backdrop_width * snode->zoom; float bufy = backdrop_height * snode->zoom; - *fx = (bufx > 0.0f ? ((float)x - 0.5f * region->winx - snode->xof) / bufx + 0.5f : 0.0f); - *fy = (bufy > 0.0f ? ((float)y - 0.5f * region->winy - snode->yof) / bufy + 0.5f : 0.0f); + *fx = (bufx > 0.0f ? (float(x) - 0.5f * region->winx - snode->xof) / bufx + 0.5f : 0.0f); + *fy = (bufy > 0.0f ? (float(y) - 0.5f * region->winy - snode->yof) / bufy + 0.5f : 0.0f); } static int viewer_border_exec(bContext *C, wmOperator *op) @@ -2935,7 +2995,7 @@ void NODE_OT_viewer_border(wmOperatorType *ot) WM_operator_properties_gesture_box(ot); } -static int clear_viewer_border_exec(bContext *C, wmOperator *UNUSED(op)) +static int clear_viewer_border_exec(bContext *C, wmOperator * /*op*/) { SpaceNode *snode = CTX_wm_space_node(C); bNodeTree *btree = snode->nodetree; @@ -2968,7 +3028,7 @@ void NODE_OT_clear_viewer_border(wmOperatorType *ot) /** \name Cryptomatte Add Socket * \{ */ -static int node_cryptomatte_add_socket_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_cryptomatte_add_socket_exec(bContext *C, wmOperator * /*op*/) { SpaceNode *snode = CTX_wm_space_node(C); PointerRNA ptr = CTX_data_pointer_get(C, "node"); @@ -3016,7 +3076,7 @@ void NODE_OT_cryptomatte_layer_add(wmOperatorType *ot) /** \name Cryptomatte Remove Socket * \{ */ -static int node_cryptomatte_remove_socket_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_cryptomatte_remove_socket_exec(bContext *C, wmOperator * /*op*/) { SpaceNode *snode = CTX_wm_space_node(C); PointerRNA ptr = CTX_data_pointer_get(C, "node"); diff --git a/source/blender/editors/space_node/node_geometry_attribute_search.cc b/source/blender/editors/space_node/node_geometry_attribute_search.cc index 809c4b2fe59..a1a8fd0dfdc 100644 --- a/source/blender/editors/space_node/node_geometry_attribute_search.cc +++ b/source/blender/editors/space_node/node_geometry_attribute_search.cc @@ -213,7 +213,7 @@ static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v) ED_undo_push(C, "Assign Attribute Name"); } -void node_geometry_add_attribute_search_button(const bContext &UNUSED(C), +void node_geometry_add_attribute_search_button(const bContext & /*C*/, const bNode &node, PointerRNA &socket_ptr, uiLayout &layout) diff --git a/source/blender/editors/space_node/node_gizmo.cc b/source/blender/editors/space_node/node_gizmo.cc index f9126556b71..7a77d523f43 100644 --- a/source/blender/editors/space_node/node_gizmo.cc +++ b/source/blender/editors/space_node/node_gizmo.cc @@ -65,7 +65,7 @@ static void node_gizmo_calc_matrix_space_with_image_dims(const SpaceNode *snode, /** \name Backdrop Gizmo * \{ */ -static void gizmo_node_backdrop_prop_matrix_get(const wmGizmo *UNUSED(gz), +static void gizmo_node_backdrop_prop_matrix_get(const wmGizmo * /*gz*/, wmGizmoProperty *gz_prop, void *value_p) { @@ -78,7 +78,7 @@ static void gizmo_node_backdrop_prop_matrix_get(const wmGizmo *UNUSED(gz), matrix[3][1] = snode->yof; } -static void gizmo_node_backdrop_prop_matrix_set(const wmGizmo *UNUSED(gz), +static void gizmo_node_backdrop_prop_matrix_set(const wmGizmo * /*gz*/, wmGizmoProperty *gz_prop, const void *value_p) { @@ -90,7 +90,7 @@ static void gizmo_node_backdrop_prop_matrix_set(const wmGizmo *UNUSED(gz), snode->yof = matrix[3][1]; } -static bool WIDGETGROUP_node_transform_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt)) +static bool WIDGETGROUP_node_transform_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/) { SpaceNode *snode = CTX_wm_space_node(C); @@ -109,7 +109,7 @@ static bool WIDGETGROUP_node_transform_poll(const bContext *C, wmGizmoGroupType return false; } -static void WIDGETGROUP_node_transform_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) +static void WIDGETGROUP_node_transform_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup) { wmGizmoWrapper *wwrapper = (wmGizmoWrapper *)MEM_mallocN(sizeof(wmGizmoWrapper), __func__); @@ -252,7 +252,7 @@ static void gizmo_node_crop_prop_matrix_get(const wmGizmo *gz, const float *dims = crop_group->state.dims; const bNode *node = (const bNode *)gz_prop->custom_func.user_data; const NodeTwoXYs *nxy = (const NodeTwoXYs *)node->storage; - bool is_relative = (bool)node->custom2; + bool is_relative = bool(node->custom2); rctf rct; two_xy_to_rect(nxy, &rct, dims, is_relative); matrix[0][0] = fabsf(BLI_rctf_size_x(&rct)); @@ -271,7 +271,7 @@ static void gizmo_node_crop_prop_matrix_set(const wmGizmo *gz, const float *dims = crop_group->state.dims; bNode *node = (bNode *)gz_prop->custom_func.user_data; NodeTwoXYs *nxy = (NodeTwoXYs *)node->storage; - bool is_relative = (bool)node->custom2; + bool is_relative = bool(node->custom2); rctf rct; two_xy_to_rect(nxy, &rct, dims, is_relative); const bool nx = rct.xmin > rct.xmax; @@ -294,7 +294,7 @@ static void gizmo_node_crop_prop_matrix_set(const wmGizmo *gz, gizmo_node_crop_update(crop_group); } -static bool WIDGETGROUP_node_crop_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt)) +static bool WIDGETGROUP_node_crop_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/) { SpaceNode *snode = CTX_wm_space_node(C); @@ -316,7 +316,7 @@ static bool WIDGETGROUP_node_crop_poll(const bContext *C, wmGizmoGroupType *UNUS return false; } -static void WIDGETGROUP_node_crop_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) +static void WIDGETGROUP_node_crop_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup) { NodeCropWidgetGroup *crop_group = MEM_new<NodeCropWidgetGroup>(__func__); crop_group->border = WM_gizmo_new("GIZMO_GT_cage_2d", gzgroup, nullptr); @@ -406,7 +406,7 @@ struct NodeSunBeamsWidgetGroup { } state; }; -static bool WIDGETGROUP_node_sbeam_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt)) +static bool WIDGETGROUP_node_sbeam_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/) { SpaceNode *snode = CTX_wm_space_node(C); @@ -425,7 +425,7 @@ static bool WIDGETGROUP_node_sbeam_poll(const bContext *C, wmGizmoGroupType *UNU return false; } -static void WIDGETGROUP_node_sbeam_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) +static void WIDGETGROUP_node_sbeam_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup) { NodeSunBeamsWidgetGroup *sbeam_group = (NodeSunBeamsWidgetGroup *)MEM_mallocN( sizeof(NodeSunBeamsWidgetGroup), __func__); @@ -511,7 +511,7 @@ struct NodeCornerPinWidgetGroup { } state; }; -static bool WIDGETGROUP_node_corner_pin_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt)) +static bool WIDGETGROUP_node_corner_pin_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/) { SpaceNode *snode = CTX_wm_space_node(C); @@ -530,7 +530,7 @@ static bool WIDGETGROUP_node_corner_pin_poll(const bContext *C, wmGizmoGroupType return false; } -static void WIDGETGROUP_node_corner_pin_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) +static void WIDGETGROUP_node_corner_pin_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup) { NodeCornerPinWidgetGroup *cpin_group = (NodeCornerPinWidgetGroup *)MEM_mallocN( sizeof(NodeCornerPinWidgetGroup), __func__); diff --git a/source/blender/editors/space_node/node_group.cc b/source/blender/editors/space_node/node_group.cc index 21def1bd9d7..329f78860a4 100644 --- a/source/blender/editors/space_node/node_group.cc +++ b/source/blender/editors/space_node/node_group.cc @@ -14,6 +14,7 @@ #include "BLI_linklist.h" #include "BLI_listbase.h" +#include "BLI_map.hh" #include "BLI_math_vec_types.hh" #include "BLI_string.h" #include "BLI_vector.hh" @@ -603,9 +604,7 @@ static int node_group_separate_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static int node_group_separate_invoke(bContext *C, - wmOperator *UNUSED(op), - const wmEvent *UNUSED(event)) +static int node_group_separate_invoke(bContext *C, wmOperator * /*op*/, const wmEvent * /*event*/) { uiPopupMenu *pup = UI_popup_menu_begin( C, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Separate"), ICON_NONE); @@ -737,6 +736,74 @@ static int node_get_selected_minmax( return totselect; } +/** + * Redirect a link that are connecting a non-selected node to selected one. + * Create new socket or reuse an existing one that was connected from the same input. + * The output sockets of group nodes usually have consciously given names so they have + * precedence over socket names the link points to. + * + * \param ntree: The node tree that the node group is being created from. + * \param ngroup: The node tree of the new node group. + * \param gnode: The new group node in the original tree. + * \param input_node: The input node of the new node group. + * \param link: The incoming link that needs to be altered. + * \param reusable_sockets: Map for input socket interface lookup. + */ +static void node_group_make_redirect_incoming_link( + bNodeTree &ntree, + bNodeTree *ngroup, + bNode *gnode, + bNode *input_node, + bNodeLink *link, + Map<bNodeSocket *, bNodeSocket *> &reusable_sockets) +{ + bNodeSocket *input_socket = reusable_sockets.lookup_default(link->fromsock, nullptr); + if (input_socket) { + /* The incoming link is from a socket that has already been linked to + * a socket interface of the input node. + * Change the source of the link to the previously created socket interface. + * Move the link into the node tree of the new group. */ + link->fromnode = input_node; + link->fromsock = input_socket; + BLI_remlink(&ntree.links, link); + BLI_addtail(&ngroup->links, link); + } + else { + bNode *node_for_typeinfo = nullptr; + bNodeSocket *socket_for_typeinfo = nullptr; + /* Find a socket where typeinfo and name may come from. */ + node_socket_skip_reroutes( + &ntree.links, link->tonode, link->tosock, &node_for_typeinfo, &socket_for_typeinfo); + bNodeSocket *socket_for_naming = socket_for_typeinfo; + + /* Use the name of group node output sockets. */ + if (ELEM(link->fromnode->type, NODE_GROUP_INPUT, NODE_GROUP, NODE_CUSTOM_GROUP)) { + socket_for_naming = link->fromsock; + } + + bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocketWithName(ngroup, + node_for_typeinfo, + socket_for_typeinfo, + socket_for_naming->idname, + socket_for_naming->name); + + /* Update the group node and interface sockets so the new interface socket can be linked. */ + node_group_update(&ntree, gnode); + node_group_input_update(ngroup, input_node); + + /* Create new internal link. */ + bNodeSocket *input_sock = node_group_input_find_socket(input_node, iosock->identifier); + nodeAddLink(ngroup, input_node, input_sock, link->tonode, link->tosock); + + /* Redirect external link. */ + link->tonode = gnode; + link->tosock = node_group_find_input_socket(gnode, iosock->identifier); + + /* Remember which interface socket the link has been redirected to. */ + reusable_sockets.add_new(link->fromsock, input_sock); + } +} + static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree, bNode *gnode) { Main *bmain = CTX_data_main(&C); @@ -836,6 +903,10 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree, output_node->locy = -offsety; /* relink external sockets */ + + /* A map from link sources to input sockets already connected. */ + Map<bNodeSocket *, bNodeSocket *> reusable_sockets; + LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) { const bool fromselect = node_group_make_use_node(*link->fromnode, gnode); const bool toselect = node_group_make_use_node(*link->tonode, gnode); @@ -853,24 +924,8 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree, continue; } - bNodeSocket *link_sock; - bNode *link_node; - node_socket_skip_reroutes(&ntree.links, link->tonode, link->tosock, &link_node, &link_sock); - bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, link_node, link_sock); - - /* update the group node and interface node sockets, - * so the new interface socket can be linked. - */ - node_group_update(&ntree, gnode); - node_group_input_update(ngroup, input_node); - - /* create new internal link */ - bNodeSocket *input_sock = node_group_input_find_socket(input_node, iosock->identifier); - nodeAddLink(ngroup, input_node, input_sock, link->tonode, link->tosock); - - /* redirect external link */ - link->tonode = gnode; - link->tosock = node_group_find_input_socket(gnode, iosock->identifier); + node_group_make_redirect_incoming_link( + ntree, ngroup, gnode, input_node, link, reusable_sockets); } else if (fromselect && !toselect) { /* Remove hidden links to not create unconnected sockets in the interface. */ @@ -1047,6 +1102,8 @@ static int node_group_make_exec(bContext *C, wmOperator *op) ED_node_tree_propagate_change(C, bmain, nullptr); + WM_event_add_notifier(C, NC_NODE | NA_ADDED, nullptr); + /* We broke relations in node tree, need to rebuild them in the graphs. */ DEG_relations_tag_update(bmain); diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index 456cbf5064d..88be9a52c61 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -38,6 +38,8 @@ extern const char *node_context_dir[]; namespace blender::ed::space_node { +struct AssetItemTree; + /** Temporary data used in node link drag modal operator. */ struct bNodeLinkDrag { /** Links dragged by the operator. */ @@ -96,6 +98,15 @@ struct SpaceNode_Runtime { /* XXX hack for translate_attach op-macros to pass data from transform op to insert_offset op */ /** Temporary data for node insert offset (in UI called Auto-offset). */ struct NodeInsertOfsData *iofsd; + + /** + * Temporary data for node add menu in order to provide longer-term storage for context pointers. + * Recreated every time the root menu is opened. In the future this will be replaced with an "all + * libraries" cache in the asset system itself. + * + * Stored with a shared pointer so that it can be forward declared. + */ + std::shared_ptr<AssetItemTree> assets_for_menu; }; enum NodeResizeDirection { @@ -251,7 +262,9 @@ 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_group_asset(wmOperatorType *ot); void NODE_OT_add_object(wmOperatorType *ot); void NODE_OT_add_collection(wmOperatorType *ot); void NODE_OT_add_file(wmOperatorType *ot); @@ -269,6 +282,8 @@ void NODE_OT_group_edit(wmOperatorType *ot); /* node_relationships.cc */ +void update_multi_input_indices_for_removed_links(bNode &node); + void NODE_OT_link(wmOperatorType *ot); void NODE_OT_link_make(wmOperatorType *ot); void NODE_OT_links_cut(wmOperatorType *ot); @@ -321,6 +336,7 @@ void NODE_OT_hide_socket_toggle(wmOperatorType *ot); void NODE_OT_preview_toggle(wmOperatorType *ot); void NODE_OT_options_toggle(wmOperatorType *ot); void NODE_OT_node_copy_color(wmOperatorType *ot); +void NODE_OT_deactivate_viewer(wmOperatorType *ot); void NODE_OT_read_viewlayers(wmOperatorType *ot); void NODE_OT_render_changed(wmOperatorType *ot); @@ -375,4 +391,13 @@ 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); + +/* add_menu_assets.cc */ + +MenuType add_catalog_assets_menu_type(); +MenuType add_root_catalogs_menu_type(); + } // 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..104d1acf3b4 100644 --- a/source/blender/editors/space_node/node_ops.cc +++ b/source/blender/editors/space_node/node_ops.cc @@ -44,6 +44,7 @@ void node_operatortypes() WM_operatortype_append(NODE_OT_options_toggle); WM_operatortype_append(NODE_OT_hide_socket_toggle); WM_operatortype_append(NODE_OT_node_copy_color); + WM_operatortype_append(NODE_OT_deactivate_viewer); WM_operatortype_append(NODE_OT_duplicate); WM_operatortype_append(NODE_OT_delete); @@ -75,7 +76,9 @@ 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_group_asset); WM_operatortype_append(NODE_OT_add_object); WM_operatortype_append(NODE_OT_add_collection); WM_operatortype_append(NODE_OT_add_file); @@ -134,6 +137,7 @@ void ED_operatormacros_node() mot = WM_operatortype_macro_define(ot, "NODE_OT_select"); RNA_boolean_set(mot->ptr, "extend", false); RNA_boolean_set(mot->ptr, "socket_select", true); + RNA_boolean_set(mot->ptr, "clear_viewer", true); WM_operatortype_macro_define(ot, "NODE_OT_link_viewer"); ot = WM_operatortype_append_macro("NODE_OT_translate_attach", @@ -171,7 +175,17 @@ void ED_operatormacros_node() "Duplicate", "Duplicate selected nodes and move them", OPTYPE_UNDO | OPTYPE_REGISTER); - WM_operatortype_macro_define(ot, "NODE_OT_duplicate"); + mot = WM_operatortype_macro_define(ot, "NODE_OT_duplicate"); + RNA_boolean_set(mot->ptr, "linked", false); + WM_operatortype_macro_define(ot, "NODE_OT_translate_attach"); + + ot = WM_operatortype_append_macro( + "NODE_OT_duplicate_move_linked", + "Duplicate Linked", + "Duplicate selected nodes, but not their node trees, and move them", + OPTYPE_UNDO | OPTYPE_REGISTER); + mot = WM_operatortype_macro_define(ot, "NODE_OT_duplicate"); + RNA_boolean_set(mot->ptr, "linked", true); WM_operatortype_macro_define(ot, "NODE_OT_translate_attach"); /* modified operator call for duplicating with input links */ diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index 929fb64bd70..8eeba8727dc 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -27,8 +27,8 @@ #include "ED_render.h" #include "ED_screen.h" #include "ED_space_api.h" -#include "ED_spreadsheet.h" #include "ED_util.h" +#include "ED_viewer_path.hh" #include "RNA_access.h" #include "RNA_define.h" @@ -64,6 +64,10 @@ struct NodeInsertOfsData { float offset_x; /* offset to apply to node chain */ }; +namespace blender::ed::space_node { + +bNodeSocket *get_main_socket(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out); + static void clear_picking_highlight(ListBase *links) { LISTBASE_FOREACH (bNodeLink *, link, links) { @@ -71,10 +75,6 @@ static void clear_picking_highlight(ListBase *links) } } -namespace blender::ed::space_node { - -void update_multi_input_indices_for_removed_links(bNode &node); - /* -------------------------------------------------------------------- */ /** \name Add Node * \{ */ @@ -170,7 +170,7 @@ static void pick_input_link_by_link_intersect(const bContext &C, } } -static bool socket_is_available(bNodeTree *UNUSED(ntree), bNodeSocket *sock, const bool allow_used) +static bool socket_is_available(bNodeTree * /*ntree*/, bNodeSocket *sock, const bool allow_used) { if (nodeSocketIsHidden(sock)) { return false; @@ -495,17 +495,6 @@ static bool is_viewer_node(const bNode &node) return ELEM(node.type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER); } -static Vector<const bNode *> find_viewer_nodes(const bNodeTree &tree) -{ - Vector<const bNode *> viewer_nodes; - for (const bNode *node : tree.all_nodes()) { - if (is_viewer_node(*node)) { - viewer_nodes.append(node); - } - } - return viewer_nodes; -} - static bool is_viewer_socket_in_viewer(const bNodeSocket &socket) { const bNode &node = socket.owner_node(); @@ -516,18 +505,10 @@ static bool is_viewer_socket_in_viewer(const bNodeSocket &socket) return socket.index() == 0; } -static bool is_linked_to_viewer(const bNodeSocket &socket, const bNode &viewer_node) +static bool is_viewer_socket(const bNodeSocket &socket) { - for (const bNodeSocket *target_socket : socket.directly_linked_sockets()) { - if (&target_socket->owner_node() != &viewer_node) { - continue; - } - if (!target_socket->is_available()) { - continue; - } - if (is_viewer_socket_in_viewer(*target_socket)) { - return true; - } + if (is_viewer_node(socket.owner_node())) { + return is_viewer_socket_in_viewer(socket); } return false; } @@ -549,137 +530,165 @@ static void remove_links_to_unavailable_viewer_sockets(bNodeTree &btree, bNode & } } -static const bNode *get_existing_viewer(const bNodeTree &tree) +static bNodeSocket *determine_socket_to_view(bNode &node_to_view) { - Vector<const bNode *> viewer_nodes = find_viewer_nodes(tree); - - /* Check if there is already an active viewer node that should be used. */ - for (const bNode *viewer_node : viewer_nodes) { - if (viewer_node->flag & NODE_DO_OUTPUT) { - return viewer_node; + int last_linked_socket_index = -1; + for (bNodeSocket *socket : node_to_view.output_sockets()) { + if (!socket_can_be_viewed(*socket)) { + continue; } - } - - /* If no active but non-active viewers exist, make one active. */ - if (!viewer_nodes.is_empty()) { - const_cast<bNode *>(viewer_nodes[0])->flag |= NODE_DO_OUTPUT; - return viewer_nodes[0]; - } - return nullptr; -} - -static const bNodeSocket *find_output_socket_to_be_viewed(const bNode *active_viewer_node, - const bNode &node_to_view) -{ - /* Check if any of the output sockets is selected, which is the case when the user just clicked - * on the socket. */ - for (const bNodeSocket *output_socket : node_to_view.output_sockets()) { - if (output_socket->flag & SELECT) { - return output_socket; + for (bNodeLink *link : socket->directly_linked_links()) { + bNodeSocket &target_socket = *link->tosock; + bNode &target_node = *link->tonode; + if (is_viewer_socket(target_socket)) { + if (link->is_muted() || !(target_node.flag & NODE_DO_OUTPUT)) { + /* This socket is linked to a deactivated viewer, the viewer should be activated. */ + return socket; + } + last_linked_socket_index = socket->index(); + } } } - const bNodeSocket *last_socket_linked_to_viewer = nullptr; - if (active_viewer_node != nullptr) { - for (const bNodeSocket *output_socket : node_to_view.output_sockets()) { - if (!socket_can_be_viewed(*output_socket)) { - continue; - } - if (is_linked_to_viewer(*output_socket, *active_viewer_node)) { - last_socket_linked_to_viewer = output_socket; + if (last_linked_socket_index == -1) { + /* Return the first socket that can be viewed. */ + for (bNodeSocket *socket : node_to_view.output_sockets()) { + if (socket_can_be_viewed(*socket)) { + return socket; } } + return nullptr; } - if (last_socket_linked_to_viewer == nullptr) { - /* If no output is connected to a viewer, use the first output that can be viewed. */ - for (const bNodeSocket *output_socket : node_to_view.output_sockets()) { - if (socket_can_be_viewed(*output_socket)) { - return output_socket; - } + + /* Pick the next socket to be linked to the viewer. */ + const int tot_outputs = node_to_view.output_sockets().size(); + for (const int offset : IndexRange(1, tot_outputs)) { + const int index = (last_linked_socket_index + offset) % tot_outputs; + bNodeSocket &output_socket = node_to_view.output_socket(index); + if (!socket_can_be_viewed(output_socket)) { + continue; } - } - else { - /* Pick the next socket to be linked to the viewer. */ - const int tot_outputs = node_to_view.output_sockets().size(); - for (const int offset : IndexRange(1, tot_outputs - 1)) { - const int index = (last_socket_linked_to_viewer->index() + offset) % tot_outputs; - const bNodeSocket &output_socket = node_to_view.output_socket(index); - if (!socket_can_be_viewed(output_socket)) { + bool is_currently_viewed = false; + for (const bNodeLink *link : output_socket.directly_linked_links()) { + bNodeSocket &target_socket = *link->tosock; + bNode &target_node = *link->tonode; + if (!is_viewer_socket(target_socket)) { continue; } - if (is_linked_to_viewer(output_socket, *active_viewer_node)) { + if (link->is_muted()) { continue; } - return &output_socket; + if (!(target_node.flag & NODE_DO_OUTPUT)) { + continue; + } + is_currently_viewed = true; + break; + } + if (is_currently_viewed) { + continue; } + return &output_socket; } return nullptr; } -static int link_socket_to_viewer(const bContext &C, - bNode *viewer_bnode, - bNode &bnode_to_view, - bNodeSocket &bsocket_to_view) +static void finalize_viewer_link(const bContext &C, + SpaceNode &snode, + bNode &viewer_node, + bNodeLink &viewer_link) { - SpaceNode &snode = *CTX_wm_space_node(&C); - bNodeTree &btree = *snode.edittree; + Main *bmain = CTX_data_main(&C); + remove_links_to_unavailable_viewer_sockets(*snode.edittree, viewer_node); + viewer_link.flag &= ~NODE_LINK_MUTED; + viewer_node.flag &= ~NODE_MUTED; + viewer_node.flag |= NODE_DO_OUTPUT; + if (snode.edittree->type == NTREE_GEOMETRY) { + viewer_path::activate_geometry_node(*bmain, snode, viewer_node); + } + ED_node_tree_propagate_change(&C, bmain, snode.edittree); +} + +static int view_socket(const bContext &C, + SpaceNode &snode, + bNodeTree &btree, + bNode &bnode_to_view, + bNodeSocket &bsocket_to_view) +{ + bNode *viewer_node = nullptr; + /* Try to find a viewer that is already active. */ + LISTBASE_FOREACH (bNode *, node, &btree.nodes) { + if (is_viewer_node(*node)) { + if (node->flag & NODE_DO_OUTPUT) { + viewer_node = node; + break; + } + } + } + + /* Try to reactivate existing viewer connection. */ + for (bNodeLink *link : bsocket_to_view.directly_linked_links()) { + bNodeSocket &target_socket = *link->tosock; + bNode &target_node = *link->tonode; + if (is_viewer_socket(target_socket) && ELEM(viewer_node, nullptr, &target_node)) { + finalize_viewer_link(C, snode, target_node, *link); + return OPERATOR_FINISHED; + } + } - if (viewer_bnode == nullptr) { - /* Create a new viewer node if none exists. */ + if (viewer_node == nullptr) { + LISTBASE_FOREACH (bNode *, node, &btree.nodes) { + if (is_viewer_node(*node)) { + viewer_node = node; + break; + } + } + } + if (viewer_node == nullptr) { const int viewer_type = get_default_viewer_type(&C); const float2 location{bsocket_to_view.locx / UI_DPI_FAC + 100, bsocket_to_view.locy / UI_DPI_FAC}; - viewer_bnode = add_static_node(C, viewer_type, location); - if (viewer_bnode == nullptr) { - return OPERATOR_CANCELLED; - } + viewer_node = add_static_node(C, viewer_type, location); } - bNodeSocket *viewer_bsocket = node_link_viewer_get_socket(btree, *viewer_bnode, bsocket_to_view); + bNodeSocket *viewer_bsocket = node_link_viewer_get_socket(btree, *viewer_node, bsocket_to_view); if (viewer_bsocket == nullptr) { return OPERATOR_CANCELLED; } - - bNodeLink *link_to_change = nullptr; - LISTBASE_FOREACH (bNodeLink *, link, &btree.links) { + bNodeLink *viewer_link = nullptr; + LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &btree.links) { if (link->tosock == viewer_bsocket) { - link_to_change = link; + viewer_link = link; break; } } - - if (link_to_change == nullptr) { - nodeAddLink(&btree, &bnode_to_view, &bsocket_to_view, viewer_bnode, viewer_bsocket); + if (viewer_link == nullptr) { + viewer_link = nodeAddLink( + &btree, &bnode_to_view, &bsocket_to_view, viewer_node, viewer_bsocket); } else { - link_to_change->fromnode = &bnode_to_view; - link_to_change->fromsock = &bsocket_to_view; + viewer_link->fromnode = &bnode_to_view; + viewer_link->fromsock = &bsocket_to_view; BKE_ntree_update_tag_link_changed(&btree); } - - remove_links_to_unavailable_viewer_sockets(btree, *viewer_bnode); - - if (btree.type == NTREE_GEOMETRY) { - ED_spreadsheet_context_paths_set_geometry_node(CTX_data_main(&C), &snode, viewer_bnode); - } - - ED_node_tree_propagate_change(&C, CTX_data_main(&C), &btree); - return OPERATOR_FINISHED; + finalize_viewer_link(C, snode, *viewer_node, *viewer_link); + return OPERATOR_CANCELLED; } -static int node_link_viewer(const bContext &C, bNode &bnode_to_view) +static int node_link_viewer(const bContext &C, bNode &bnode_to_view, bNodeSocket *bsocket_to_view) { SpaceNode &snode = *CTX_wm_space_node(&C); bNodeTree *btree = snode.edittree; btree->ensure_topology_cache(); - bNode *active_viewer_bnode = const_cast<bNode *>(get_existing_viewer(*btree)); - bNodeSocket *bsocket_to_view = const_cast<bNodeSocket *>( - find_output_socket_to_be_viewed(active_viewer_bnode, bnode_to_view)); if (bsocket_to_view == nullptr) { - return OPERATOR_FINISHED; + bsocket_to_view = determine_socket_to_view(bnode_to_view); + } + + if (bsocket_to_view == nullptr) { + return OPERATOR_CANCELLED; } - return link_socket_to_viewer(C, active_viewer_bnode, bnode_to_view, *bsocket_to_view); + + return view_socket(C, snode, *btree, bnode_to_view, *bsocket_to_view); } /** \} */ @@ -690,7 +699,7 @@ static int node_link_viewer(const bContext &C, bNode &bnode_to_view) /** \name Link to Viewer Node Operator * \{ */ -static int node_active_link_viewer_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_active_link_viewer_exec(bContext *C, wmOperator * /*op*/) { SpaceNode &snode = *CTX_wm_space_node(C); bNode *node = nodeGetActive(snode.edittree); @@ -701,7 +710,15 @@ static int node_active_link_viewer_exec(bContext *C, wmOperator *UNUSED(op)) ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); - if (viewer_linking::node_link_viewer(*C, *node) == OPERATOR_CANCELLED) { + bNodeSocket *socket_to_view = nullptr; + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { + if (socket->flag & SELECT) { + socket_to_view = socket; + break; + } + } + + if (viewer_linking::node_link_viewer(*C, *node, socket_to_view) == OPERATOR_CANCELLED) { return OPERATOR_CANCELLED; } @@ -788,7 +805,7 @@ static bool should_create_drag_link_search_menu(const bNodeTree &node_tree, return true; } -static void draw_draglink_tooltip(const bContext *UNUSED(C), ARegion *UNUSED(region), void *arg) +static void draw_draglink_tooltip(const bContext * /*C*/, ARegion * /*region*/, void *arg) { bNodeLinkDrag *nldrag = static_cast<bNodeLinkDrag *>(arg); @@ -798,7 +815,8 @@ static void draw_draglink_tooltip(const bContext *UNUSED(C), ARegion *UNUSED(reg nldrag->cursor[0]; const float y = nldrag->cursor[1] - 2.0f * UI_DPI_FAC; - UI_icon_draw_ex(x, y, ICON_ADD, U.inv_dpi_fac, 1.0f, 0.0f, text_col, false); + UI_icon_draw_ex( + x, y, ICON_ADD, U.inv_dpi_fac, 1.0f, 0.0f, text_col, false, UI_NO_ICON_OVERLAY_TEXT); } static void draw_draglink_tooltip_activate(const ARegion ®ion, bNodeLinkDrag &nldrag) @@ -817,7 +835,7 @@ static void draw_draglink_tooltip_deactivate(const ARegion ®ion, bNodeLinkDra } } -static void node_link_update_header(bContext *C, bNodeLinkDrag *UNUSED(nldrag)) +static void node_link_update_header(bContext *C, bNodeLinkDrag * /*nldrag*/) { char header[UI_MAX_DRAW_STR]; @@ -915,7 +933,7 @@ static void node_link_exit(bContext &C, wmOperator &op, const bool apply_links) ED_node_tree_propagate_change(&C, bmain, &ntree); - /* Ensure draglink tooltip is disabled. */ + /* Ensure drag-link tool-tip is disabled. */ draw_draglink_tooltip_deactivate(*CTX_wm_region(&C), *nldrag); ED_workspace_status_text(&C, nullptr); @@ -1532,7 +1550,7 @@ void NODE_OT_links_mute(wmOperatorType *ot) /** \name Detach Links Operator * \{ */ -static int detach_links_exec(bContext *C, wmOperator *UNUSED(op)) +static int detach_links_exec(bContext *C, wmOperator * /*op*/) { SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &ntree = *snode.edittree; @@ -1569,7 +1587,7 @@ void NODE_OT_links_detach(wmOperatorType *ot) /** \name Set Parent Operator * \{ */ -static int node_parent_set_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_parent_set_exec(bContext *C, wmOperator * /*op*/) { SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &ntree = *snode.edittree; @@ -1651,7 +1669,7 @@ static void node_join_attach_recursive(bNode *node, } } -static int node_join_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_join_exec(bContext *C, wmOperator * /*op*/) { Main &bmain = *CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); @@ -1660,6 +1678,7 @@ static int node_join_exec(bContext *C, wmOperator *UNUSED(op)) const Set<bNode *> selected_nodes = get_selected_nodes(ntree); bNode *frame_node = nodeAddStaticNode(C, &ntree, NODE_FRAME); + nodeSetActive(&ntree, frame_node); /* reset tags */ LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { @@ -1721,7 +1740,7 @@ static bNode *node_find_frame_to_attach(ARegion ®ion, return nullptr; } -static int node_attach_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +static int node_attach_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event) { ARegion ®ion = *CTX_wm_region(C); SpaceNode &snode = *CTX_wm_space_node(C); @@ -1733,29 +1752,31 @@ static int node_attach_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent } LISTBASE_FOREACH_BACKWARD (bNode *, node, &ntree.nodes) { - if (node->flag & NODE_SELECT) { - if (node->parent == nullptr) { - /* disallow moving a parent into its child */ - if (nodeAttachNodeCheck(frame, node) == false) { - /* attach all unparented nodes */ - nodeAttachNode(node, frame); - } + if (!(node->flag & NODE_SELECT)) { + continue; + } + + if (node->parent == nullptr) { + /* disallow moving a parent into its child */ + if (nodeAttachNodeCheck(frame, node) == false) { + /* attach all unparented nodes */ + nodeAttachNode(node, frame); } - else { - /* attach nodes which share parent with the frame */ - bNode *parent; - for (parent = frame->parent; parent; parent = parent->parent) { - if (parent == node->parent) { - break; - } + } + else { + /* attach nodes which share parent with the frame */ + bNode *parent; + for (parent = frame->parent; parent; parent = parent->parent) { + if (parent == node->parent) { + break; } + } - if (parent) { - /* disallow moving a parent into its child */ - if (nodeAttachNodeCheck(frame, node) == false) { - nodeDetachNode(node); - nodeAttachNode(node, frame); - } + if (parent) { + /* disallow moving a parent into its child */ + if (nodeAttachNodeCheck(frame, node) == false) { + nodeDetachNode(node); + nodeAttachNode(node, frame); } } } @@ -1819,7 +1840,7 @@ static void node_detach_recursive(bNode *node) } /* detach the root nodes in the current selection */ -static int node_detach_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_detach_exec(bContext *C, wmOperator * /*op*/) { SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &ntree = *snode.edittree; @@ -1864,100 +1885,55 @@ void NODE_OT_detach(wmOperatorType *ot) /** \name Automatic Node Insert on Dragging * \{ */ -/* prevent duplicate testing code below */ -static bool ed_node_link_conditions(ScrArea *area, - bool test, - SpaceNode **r_snode, - bNode **r_select) +static bNode *get_selected_node_for_insertion(bNodeTree &node_tree) { - SpaceNode *snode = area ? (SpaceNode *)area->spacedata.first : nullptr; - - *r_snode = snode; - *r_select = nullptr; - - /* no unlucky accidents */ - if (area == nullptr || area->spacetype != SPACE_NODE) { - return false; - } - - if (!test) { - /* no need to look for a node */ - return true; - } - - bNode *node; - bNode *select = nullptr; - for (node = (bNode *)snode->edittree->nodes.first; node; node = node->next) { + bNode *selected_node = nullptr; + int selected_node_count = 0; + for (bNode *node : node_tree.all_nodes()) { if (node->flag & SELECT) { - if (select) { - break; - } - select = node; + selected_node = node; + selected_node_count++; + } + if (selected_node_count > 1) { + return nullptr; } } - /* only one selected */ - if (node || select == nullptr) { - return false; + if (!selected_node) { + return nullptr; } - - /* correct node */ - if (BLI_listbase_is_empty(&select->inputs) || BLI_listbase_is_empty(&select->outputs)) { - return false; + if (selected_node->input_sockets().is_empty() || selected_node->output_sockets().is_empty()) { + return nullptr; } - - ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); - - /* test node for links */ - LISTBASE_FOREACH (bNodeLink *, link, &snode->edittree->links) { - if (node_link_is_hidden_or_dimmed(region->v2d, *link)) { - continue; - } - - if (link->tonode == select || link->fromnode == select) { - return false; - } + if (std::any_of(selected_node->input_sockets().begin(), + selected_node->input_sockets().end(), + [&](const bNodeSocket *socket) { return socket->is_directly_linked(); })) { + return nullptr; } - - *r_select = select; - return true; + if (std::any_of(selected_node->output_sockets().begin(), + selected_node->output_sockets().end(), + [&](const bNodeSocket *socket) { return socket->is_directly_linked(); })) { + return nullptr; + }; + return selected_node; } -/** \} */ - -} // namespace blender::ed::space_node - -/* -------------------------------------------------------------------- */ -/** \name Node Line Intersection Test - * \{ */ - -void ED_node_link_intersect_test(ScrArea *area, int test) +void node_insert_on_link_flags_set(SpaceNode &snode, const ARegion ®ion) { - using namespace blender; - using namespace blender::ed::space_node; - - bNode *select; - SpaceNode *snode; - if (!ed_node_link_conditions(area, test, &snode, &select)) { - return; - } + bNodeTree &node_tree = *snode.edittree; + node_tree.ensure_topology_cache(); - /* clear flags */ - LISTBASE_FOREACH (bNodeLink *, link, &snode->edittree->links) { - link->flag &= ~NODE_LINKFLAG_HILITE; - } + node_insert_on_link_flags_clear(node_tree); - if (test == 0) { + bNode *node_to_insert = get_selected_node_for_insertion(node_tree); + if (!node_to_insert) { return; } - ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); - /* find link to select/highlight */ bNodeLink *selink = nullptr; float dist_best = FLT_MAX; - LISTBASE_FOREACH (bNodeLink *, link, &snode->edittree->links) { - - if (node_link_is_hidden_or_dimmed(region->v2d, *link)) { + LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) { + if (node_link_is_hidden_or_dimmed(region.v2d, *link)) { continue; } @@ -1969,10 +1945,10 @@ void ED_node_link_intersect_test(ScrArea *area, int test) * upper left node edge of a intersected line segment */ for (int i = 0; i < NODE_LINK_RESOL; i++) { /* Check if the node rectangle intersects the line from this point to next one. */ - if (BLI_rctf_isect_segment(&select->totr, coords[i], coords[i + 1])) { + if (BLI_rctf_isect_segment(&node_to_insert->totr, coords[i], coords[i + 1])) { /* store the shortest distance to the upper left edge * of all intersections found so far */ - const float node_xy[] = {select->totr.xmin, select->totr.ymax}; + const float node_xy[] = {node_to_insert->totr.xmin, node_to_insert->totr.ymax}; /* to be precise coords should be clipped by select->totr, * but not done since there's no real noticeable difference */ @@ -1992,9 +1968,89 @@ void ED_node_link_intersect_test(ScrArea *area, int test) } } -/** \} */ +void node_insert_on_link_flags_clear(bNodeTree &node_tree) +{ + LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) { + link->flag &= ~NODE_LINKFLAG_HILITE; + } +} -namespace blender::ed::space_node { +void node_insert_on_link_flags(Main &bmain, SpaceNode &snode) +{ + bNodeTree &node_tree = *snode.edittree; + node_tree.ensure_topology_cache(); + bNode *node_to_insert = get_selected_node_for_insertion(node_tree); + if (!node_to_insert) { + return; + } + + /* Find link to insert on. */ + bNodeTree &ntree = *snode.edittree; + bNodeLink *old_link = nullptr; + LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { + if (link->flag & NODE_LINKFLAG_HILITE) { + old_link = link; + break; + } + } + if (old_link == nullptr) { + return; + } + + old_link->flag &= ~NODE_LINKFLAG_HILITE; + + bNodeSocket *best_input = get_main_socket(ntree, *node_to_insert, SOCK_IN); + bNodeSocket *best_output = get_main_socket(ntree, *node_to_insert, SOCK_OUT); + + if (node_to_insert->type != NODE_REROUTE) { + /* Ignore main sockets when the types don't match. */ + if (best_input != nullptr && ntree.typeinfo->validate_link != nullptr && + !ntree.typeinfo->validate_link(static_cast<eNodeSocketDatatype>(old_link->fromsock->type), + static_cast<eNodeSocketDatatype>(best_input->type))) { + best_input = nullptr; + } + if (best_output != nullptr && ntree.typeinfo->validate_link != nullptr && + !ntree.typeinfo->validate_link(static_cast<eNodeSocketDatatype>(best_output->type), + static_cast<eNodeSocketDatatype>(old_link->tosock->type))) { + best_output = nullptr; + } + } + + bNode *from_node = old_link->fromnode; + bNodeSocket *from_socket = old_link->fromsock; + bNode *to_node = old_link->tonode; + + if (best_output != nullptr) { + /* Relink the "start" of the existing link to the newly inserted node. */ + old_link->fromnode = node_to_insert; + old_link->fromsock = best_output; + BKE_ntree_update_tag_link_changed(&ntree); + } + else { + nodeRemLink(&ntree, old_link); + } + + if (best_input != nullptr) { + /* Add a new link that connects the node on the left to the newly inserted node. */ + nodeAddLink(&ntree, from_node, from_socket, node_to_insert, best_input); + } + + /* Set up insert offset data, it needs stuff from here. */ + if ((snode.flag & SNODE_SKIP_INSOFFSET) == 0) { + BLI_assert(snode.runtime->iofsd == nullptr); + NodeInsertOfsData *iofsd = MEM_cnew<NodeInsertOfsData>(__func__); + + iofsd->insert = node_to_insert; + iofsd->prev = from_node; + iofsd->next = to_node; + + snode.runtime->iofsd = iofsd; + } + + ED_node_tree_propagate_change(nullptr, &bmain, &ntree); +} + +/** \} */ /* -------------------------------------------------------------------- */ /** \name Node Insert Offset Operator @@ -2031,7 +2087,7 @@ static int get_main_socket_priority(const bNodeSocket *socket) } /** Get the "main" socket based on the node declaration or an heuristic. */ -static bNodeSocket *get_main_socket(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) +bNodeSocket *get_main_socket(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) { ListBase *sockets = (in_out == SOCK_IN) ? &node.inputs : &node.outputs; @@ -2087,7 +2143,7 @@ static bNodeSocket *get_main_socket(bNodeTree &ntree, bNode &node, eNodeSocketIn return nullptr; } -static bool node_parents_offset_flag_enable_cb(bNode *parent, void *UNUSED(userdata)) +static bool node_parents_offset_flag_enable_cb(bNode *parent, void * /*userdata*/) { /* NODE_TEST is used to flag nodes that shouldn't be offset (again) */ parent->flag |= NODE_TEST; @@ -2312,10 +2368,10 @@ static void node_link_insert_offset_ntree(NodeInsertOfsData *iofsd, /** * Modal handler for insert offset animation */ -static int node_insert_offset_modal(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +static int node_insert_offset_modal(bContext *C, wmOperator *op, const wmEvent *event) { SpaceNode *snode = CTX_wm_space_node(C); - NodeInsertOfsData *iofsd = snode->runtime->iofsd; + NodeInsertOfsData *iofsd = static_cast<NodeInsertOfsData *>(op->customdata); bool redraw = false; if (!snode || event->type != TIMER || iofsd == nullptr || @@ -2323,7 +2379,7 @@ static int node_insert_offset_modal(bContext *C, wmOperator *UNUSED(op), const w return OPERATOR_PASS_THROUGH; } - const float duration = (float)iofsd->anim_timer->duration; + const float duration = float(iofsd->anim_timer->duration); /* handle animation - do this before possibly aborting due to duration, since * main thread might be so busy that node hasn't reached final position yet */ @@ -2355,7 +2411,6 @@ static int node_insert_offset_modal(bContext *C, wmOperator *UNUSED(op), const w node->anim_init_locx = node->anim_ofsx = 0.0f; } - snode->runtime->iofsd = nullptr; MEM_freeN(iofsd); return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); @@ -2370,6 +2425,8 @@ static int node_insert_offset_invoke(bContext *C, wmOperator *op, const wmEvent { const SpaceNode *snode = CTX_wm_space_node(C); NodeInsertOfsData *iofsd = snode->runtime->iofsd; + snode->runtime->iofsd = nullptr; + op->customdata = iofsd; if (!iofsd || !iofsd->insert) { return OPERATOR_CANCELLED; @@ -2408,84 +2465,3 @@ void NODE_OT_insert_offset(wmOperatorType *ot) /** \} */ } // namespace blender::ed::space_node - -/* -------------------------------------------------------------------- */ -/** \name Note Link Insert - * \{ */ - -void ED_node_link_insert(Main *bmain, ScrArea *area) -{ - using namespace blender::ed::space_node; - - bNode *node_to_insert; - SpaceNode *snode; - if (!ed_node_link_conditions(area, true, &snode, &node_to_insert)) { - return; - } - - /* Find link to insert on. */ - bNodeTree &ntree = *snode->edittree; - bNodeLink *old_link = nullptr; - LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { - if (link->flag & NODE_LINKFLAG_HILITE) { - old_link = link; - break; - } - } - if (old_link == nullptr) { - return; - } - - old_link->flag &= ~NODE_LINKFLAG_HILITE; - - bNodeSocket *best_input = get_main_socket(ntree, *node_to_insert, SOCK_IN); - bNodeSocket *best_output = get_main_socket(ntree, *node_to_insert, SOCK_OUT); - - if (node_to_insert->type != NODE_REROUTE) { - /* Ignore main sockets when the types don't match. */ - if (best_input != nullptr && ntree.typeinfo->validate_link != nullptr && - !ntree.typeinfo->validate_link(static_cast<eNodeSocketDatatype>(old_link->fromsock->type), - static_cast<eNodeSocketDatatype>(best_input->type))) { - best_input = nullptr; - } - if (best_output != nullptr && ntree.typeinfo->validate_link != nullptr && - !ntree.typeinfo->validate_link(static_cast<eNodeSocketDatatype>(best_output->type), - static_cast<eNodeSocketDatatype>(old_link->tosock->type))) { - best_output = nullptr; - } - } - - bNode *from_node = old_link->fromnode; - bNodeSocket *from_socket = old_link->fromsock; - bNode *to_node = old_link->tonode; - - if (best_output != nullptr) { - /* Relink the "start" of the existing link to the newly inserted node. */ - old_link->fromnode = node_to_insert; - old_link->fromsock = best_output; - BKE_ntree_update_tag_link_changed(&ntree); - } - else { - nodeRemLink(&ntree, old_link); - } - - if (best_input != nullptr) { - /* Add a new link that connects the node on the left to the newly inserted node. */ - nodeAddLink(&ntree, from_node, from_socket, node_to_insert, best_input); - } - - /* Set up insert offset data, it needs stuff from here. */ - if ((snode->flag & SNODE_SKIP_INSOFFSET) == 0) { - NodeInsertOfsData *iofsd = MEM_cnew<NodeInsertOfsData>(__func__); - - iofsd->insert = node_to_insert; - iofsd->prev = from_node; - iofsd->next = to_node; - - snode->runtime->iofsd = iofsd; - } - - ED_node_tree_propagate_change(nullptr, bmain, snode->edittree); -} - -/** \} */ diff --git a/source/blender/editors/space_node/node_select.cc b/source/blender/editors/space_node/node_select.cc index d93b205b1b7..7c39169415b 100644 --- a/source/blender/editors/space_node/node_select.cc +++ b/source/blender/editors/space_node/node_select.cc @@ -23,13 +23,14 @@ #include "BKE_main.h" #include "BKE_node.h" #include "BKE_node_runtime.hh" +#include "BKE_node_tree_update.h" #include "BKE_workspace.h" #include "ED_node.h" /* own include */ #include "ED_screen.h" #include "ED_select_utils.h" -#include "ED_spreadsheet.h" #include "ED_view3d.h" +#include "ED_viewer_path.hh" #include "RNA_access.h" #include "RNA_define.h" @@ -644,6 +645,15 @@ static bool node_mouse_select(bContext *C, } } + if (RNA_boolean_get(op->ptr, "clear_viewer")) { + if (node == nullptr) { + /* Disable existing active viewer. */ + WorkSpace *workspace = CTX_wm_workspace(C); + BKE_viewer_path_clear(&workspace->viewer_path); + WM_event_add_notifier(C, NC_VIEWER_PATH, nullptr); + } + } + if (!(changed || found)) { return false; } @@ -655,7 +665,7 @@ static bool node_mouse_select(bContext *C, ED_node_set_active(&bmain, &snode, snode.edittree, node, &active_texture_changed); } else if (node != nullptr && node->type == GEO_NODE_VIEWER) { - ED_spreadsheet_context_paths_set_geometry_node(&bmain, &snode, node); + viewer_path::activate_geometry_node(bmain, snode, *node); } ED_node_set_active_viewer_key(&snode); node_sort(*snode.edittree); @@ -731,6 +741,12 @@ void NODE_OT_select(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_HIDDEN); RNA_def_boolean(ot->srna, "socket_select", false, "Socket Select", ""); + + RNA_def_boolean(ot->srna, + "clear_viewer", + false, + "Clear Viewer", + "Deactivate geometry nodes viewer when clicking in empty space"); } /** \} */ @@ -843,8 +859,7 @@ static int node_circleselect_exec(bContext *C, wmOperator *op) int x, y, radius; float2 offset; - float zoom = (float)(BLI_rcti_size_x(®ion->winrct)) / - (float)(BLI_rctf_size_x(®ion->v2d.cur)); + float zoom = float(BLI_rcti_size_x(®ion->winrct)) / float(BLI_rctf_size_x(®ion->v2d.cur)); const eSelectOp sel_op = ED_select_op_modal( (eSelectOp)RNA_enum_get(op->ptr, "mode"), @@ -867,7 +882,7 @@ static int node_circleselect_exec(bContext *C, wmOperator *op) /* Frame nodes are selectable by their borders (including their whole rect - as for other * nodes - would prevent selection of _only_ other nodes inside that frame. */ rctf frame_inside = node_frame_rect_inside(*node); - const float radius_adjusted = (float)radius / zoom; + const float radius_adjusted = float(radius) / zoom; BLI_rctf_pad(&frame_inside, -2.0f * radius_adjusted, -2.0f * radius_adjusted); if (BLI_rctf_isect_circle(&node->totr, offset, radius_adjusted) && !BLI_rctf_isect_circle(&frame_inside, offset, radius_adjusted)) { @@ -1119,7 +1134,7 @@ void NODE_OT_select_all(wmOperatorType *ot) /** \name Select Linked To Operator * \{ */ -static int node_select_linked_to_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_select_linked_to_exec(bContext *C, wmOperator * /*op*/) { SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &node_tree = *snode.edittree; @@ -1169,7 +1184,7 @@ void NODE_OT_select_linked_to(wmOperatorType *ot) /** \name Select Linked From Operator * \{ */ -static int node_select_linked_from_exec(bContext *C, wmOperator *UNUSED(op)) +static int node_select_linked_from_exec(bContext *C, wmOperator * /*op*/) { SpaceNode &snode = *CTX_wm_space_node(C); bNodeTree &node_tree = *snode.edittree; @@ -1337,10 +1352,10 @@ static void node_find_create_label(const bNode *node, char *str, int maxlen) /* Generic search invoke. */ static void node_find_update_fn(const bContext *C, - void *UNUSED(arg), + void * /*arg*/, const char *str, uiSearchItems *items, - const bool UNUSED(is_first)) + const bool /*is_first*/) { SpaceNode *snode = CTX_wm_space_node(C); @@ -1368,7 +1383,7 @@ static void node_find_update_fn(const bContext *C, BLI_string_search_free(search); } -static void node_find_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2) +static void node_find_exec_fn(bContext *C, void * /*arg1*/, void *arg2) { SpaceNode *snode = CTX_wm_space_node(C); bNode *active = (bNode *)arg2; @@ -1435,7 +1450,7 @@ static uiBlock *node_find_menu(bContext *C, ARegion *region, void *arg_op) return block; } -static int node_find_node_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +static int node_find_node_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) { UI_popup_block_invoke(C, node_find_menu, op, nullptr); return OPERATOR_CANCELLED; diff --git a/source/blender/editors/space_node/node_templates.cc b/source/blender/editors/space_node/node_templates.cc index 5fc194e02a4..1b7eadd336a 100644 --- a/source/blender/editors/space_node/node_templates.cc +++ b/source/blender/editors/space_node/node_templates.cc @@ -494,7 +494,7 @@ static int ui_node_item_name_compare(const void *a, const void *b) return BLI_strcasecmp_natural(type_a->ui_name, type_b->ui_name); } -static bool ui_node_item_special_poll(const bNodeTree *UNUSED(ntree), const bNodeType *ntype) +static bool ui_node_item_special_poll(const bNodeTree * /*ntree*/, const bNodeType *ntype) { if (STREQ(ntype->idname, "ShaderNodeUVAlongStroke")) { /* TODO(sergey): Currently we don't have Freestyle nodes edited from diff --git a/source/blender/editors/space_node/node_view.cc b/source/blender/editors/space_node/node_view.cc index 00756083580..f9b019e12ea 100644 --- a/source/blender/editors/space_node/node_view.cc +++ b/source/blender/editors/space_node/node_view.cc @@ -240,7 +240,7 @@ static int snode_bg_viewmove_invoke(bContext *C, wmOperator *op, const wmEvent * NodeViewMove *nvm; Image *ima; ImBuf *ibuf; - const float pad = 32.0f; /* better be bigger than scrollbars */ + const float pad = 32.0f; /* Better be bigger than scroll-bars. */ void *lock; @@ -273,7 +273,7 @@ static int snode_bg_viewmove_invoke(bContext *C, wmOperator *op, const wmEvent * return OPERATOR_RUNNING_MODAL; } -static void snode_bg_viewmove_cancel(bContext *UNUSED(C), wmOperator *op) +static void snode_bg_viewmove_cancel(bContext * /*C*/, wmOperator *op) { MEM_freeN(op->customdata); op->customdata = nullptr; @@ -341,7 +341,7 @@ void NODE_OT_backimage_zoom(wmOperatorType *ot) /** \name Background Image Fit * \{ */ -static int backimage_fit_exec(bContext *C, wmOperator *UNUSED(op)) +static int backimage_fit_exec(bContext *C, wmOperator * /*op*/) { Main *bmain = CTX_data_main(C); SpaceNode *snode = CTX_wm_space_node(C); @@ -464,9 +464,9 @@ bool ED_space_node_get_position( /* map the mouse coords to the backdrop image space */ float bufx = ibuf->x * snode->zoom; float bufy = ibuf->y * snode->zoom; - fpos[0] = (bufx > 0.0f ? ((float)mval[0] - 0.5f * region->winx - snode->xof) / bufx + 0.5f : + fpos[0] = (bufx > 0.0f ? (float(mval[0]) - 0.5f * region->winx - snode->xof) / bufx + 0.5f : 0.0f); - fpos[1] = (bufy > 0.0f ? ((float)mval[1] - 0.5f * region->winy - snode->yof) / bufy + 0.5f : + fpos[1] = (bufy > 0.0f ? (float(mval[1]) - 0.5f * region->winy - snode->yof) / bufy + 0.5f : 0.0f); BKE_image_release_ibuf(ima, ibuf, lock); @@ -498,13 +498,13 @@ bool ED_space_node_color_sample( /* map the mouse coords to the backdrop image space */ bufx = ibuf->x * snode->zoom; bufy = ibuf->y * snode->zoom; - fx = (bufx > 0.0f ? ((float)mval[0] - 0.5f * region->winx - snode->xof) / bufx + 0.5f : 0.0f); - fy = (bufy > 0.0f ? ((float)mval[1] - 0.5f * region->winy - snode->yof) / bufy + 0.5f : 0.0f); + fx = (bufx > 0.0f ? (float(mval[0]) - 0.5f * region->winx - snode->xof) / bufx + 0.5f : 0.0f); + fy = (bufy > 0.0f ? (float(mval[1]) - 0.5f * region->winy - snode->yof) / bufy + 0.5f : 0.0f); if (fx >= 0.0f && fy >= 0.0f && fx < 1.0f && fy < 1.0f) { const float *fp; uchar *cp; - int x = (int)(fx * ibuf->x), y = (int)(fy * ibuf->y); + int x = int(fx * ibuf->x), y = int(fy * ibuf->y); CLAMP(x, 0, ibuf->x - 1); CLAMP(y, 0, ibuf->y - 1); @@ -555,15 +555,15 @@ static void sample_apply(bContext *C, wmOperator *op, const wmEvent *event) /* map the mouse coords to the backdrop image space */ bufx = ibuf->x * snode->zoom; bufy = ibuf->y * snode->zoom; - fx = (bufx > 0.0f ? ((float)event->mval[0] - 0.5f * region->winx - snode->xof) / bufx + 0.5f : + fx = (bufx > 0.0f ? (float(event->mval[0]) - 0.5f * region->winx - snode->xof) / bufx + 0.5f : 0.0f); - fy = (bufy > 0.0f ? ((float)event->mval[1] - 0.5f * region->winy - snode->yof) / bufy + 0.5f : + fy = (bufy > 0.0f ? (float(event->mval[1]) - 0.5f * region->winy - snode->yof) / bufy + 0.5f : 0.0f); if (fx >= 0.0f && fy >= 0.0f && fx < 1.0f && fy < 1.0f) { const float *fp; uchar *cp; - int x = (int)(fx * ibuf->x), y = (int)(fy * ibuf->y); + int x = int(fx * ibuf->x), y = int(fy * ibuf->y); CLAMP(x, 0, ibuf->x - 1); CLAMP(y, 0, ibuf->y - 1); @@ -584,10 +584,10 @@ static void sample_apply(bContext *C, wmOperator *op, const wmEvent *event) info->col[2] = cp[2]; info->col[3] = cp[3]; - info->colf[0] = (float)cp[0] / 255.0f; - info->colf[1] = (float)cp[1] / 255.0f; - info->colf[2] = (float)cp[2] / 255.0f; - info->colf[3] = (float)cp[3] / 255.0f; + info->colf[0] = float(cp[0]) / 255.0f; + info->colf[1] = float(cp[1]) / 255.0f; + info->colf[2] = float(cp[2]) / 255.0f; + info->colf[3] = float(cp[3]) / 255.0f; copy_v4_v4(info->linearcol, info->colf); IMB_colormanagement_colorspace_to_scene_linear_v4( diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index fae3eb1a143..c993fa57d76 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -5,6 +5,7 @@ * \ingroup spnode */ +#include "DNA_ID.h" #include "DNA_gpencil_types.h" #include "DNA_light_types.h" #include "DNA_material_types.h" @@ -14,6 +15,7 @@ #include "MEM_guardedalloc.h" #include "BKE_context.h" +#include "BKE_gpencil.h" #include "BKE_lib_id.h" #include "BKE_lib_remap.h" #include "BKE_node.h" @@ -27,6 +29,10 @@ #include "UI_resources.h" #include "UI_view2d.h" +#include "DEG_depsgraph.h" + +#include "BLO_read_write.h" + #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" @@ -178,7 +184,7 @@ void ED_node_tree_path_get(SpaceNode *snode, char *value) value += strlen(path->display_name); } else { - sprintf(value, "/%s", path->display_name); + BLI_sprintf(value, "/%s", path->display_name); value += strlen(path->display_name) + 1; } } @@ -188,6 +194,13 @@ void ED_node_set_active_viewer_key(SpaceNode *snode) { bNodeTreePath *path = (bNodeTreePath *)snode->treepath.last; if (snode->nodetree && path) { + /* A change in active viewer may result in the change of the output node used by the + * compositor, so we need to get notified about such changes. */ + if (snode->nodetree->active_viewer_key.value != path->parent_key.value) { + DEG_id_tag_update(&snode->nodetree->id, ID_RECALC_NTREE_OUTPUT); + WM_main_add_notifier(NC_NODE, nullptr); + } + snode->nodetree->active_viewer_key = path->parent_key; } } @@ -216,7 +229,7 @@ float2 space_node_group_offset(const SpaceNode &snode) /* ******************** default callbacks for node space ***************** */ -static SpaceLink *node_create(const ScrArea *UNUSED(area), const Scene *UNUSED(scene)) +static SpaceLink *node_create(const ScrArea * /*area*/, const Scene * /*scene*/) { SpaceNode *snode = MEM_cnew<SpaceNode>("initnode"); snode->spacetype = SPACE_NODE; @@ -297,12 +310,12 @@ static void node_free(SpaceLink *sl) if (snode->runtime) { snode->runtime->linkdrag.reset(); - MEM_freeN(snode->runtime); + MEM_delete(snode->runtime); } } /* spacetype; init callback */ -static void node_init(wmWindowManager *UNUSED(wm), ScrArea *area) +static void node_init(wmWindowManager * /*wm*/, ScrArea *area) { SpaceNode *snode = (SpaceNode *)area->spacedata.first; @@ -637,57 +650,55 @@ static void node_main_region_draw(const bContext *C, ARegion *region) /* ************* dropboxes ************* */ -static bool node_group_drop_poll(bContext *UNUSED(C), wmDrag *drag, const wmEvent *UNUSED(event)) +static bool node_group_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/) { return WM_drag_is_ID_type(drag, ID_NT); } -static bool node_object_drop_poll(bContext *UNUSED(C), wmDrag *drag, const wmEvent *UNUSED(event)) +static bool node_object_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/) { return WM_drag_is_ID_type(drag, ID_OB); } -static bool node_collection_drop_poll(bContext *UNUSED(C), - wmDrag *drag, - const wmEvent *UNUSED(event)) +static bool node_collection_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/) { return WM_drag_is_ID_type(drag, ID_GR); } -static bool node_ima_drop_poll(bContext *UNUSED(C), wmDrag *drag, const wmEvent *UNUSED(event)) +static bool node_ima_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/) { if (drag->type == WM_DRAG_PATH) { /* rule might not work? */ - return (ELEM(drag->icon, 0, ICON_FILE_IMAGE, ICON_FILE_MOVIE)); + return ELEM(drag->icon, 0, ICON_FILE_IMAGE, ICON_FILE_MOVIE); } return WM_drag_is_ID_type(drag, ID_IM); } -static bool node_mask_drop_poll(bContext *UNUSED(C), wmDrag *drag, const wmEvent *UNUSED(event)) +static bool node_mask_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/) { return WM_drag_is_ID_type(drag, ID_MSK); } -static void node_group_drop_copy(bContext *UNUSED(C), wmDrag *drag, wmDropBox *drop) +static void node_group_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop) { ID *id = WM_drag_get_local_ID_or_import_from_asset(drag, 0); - RNA_int_set(drop->ptr, "session_uuid", (int)id->session_uuid); + RNA_int_set(drop->ptr, "session_uuid", int(id->session_uuid)); } -static void node_id_drop_copy(bContext *UNUSED(C), wmDrag *drag, wmDropBox *drop) +static void node_id_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop) { ID *id = WM_drag_get_local_ID_or_import_from_asset(drag, 0); - RNA_int_set(drop->ptr, "session_uuid", (int)id->session_uuid); + RNA_int_set(drop->ptr, "session_uuid", int(id->session_uuid)); } -static void node_id_path_drop_copy(bContext *UNUSED(C), wmDrag *drag, wmDropBox *drop) +static void node_id_path_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop) { ID *id = WM_drag_get_local_ID_or_import_from_asset(drag, 0); if (id) { - RNA_int_set(drop->ptr, "session_uuid", (int)id->session_uuid); + RNA_int_set(drop->ptr, "session_uuid", int(id->session_uuid)); RNA_struct_property_unset(drop->ptr, "filepath"); } else if (drag->path[0]) { @@ -736,7 +747,7 @@ static void node_dropboxes() /* ************* end drop *********** */ /* add handlers, stuff you only do once or on area/region changes */ -static void node_header_region_init(wmWindowManager *UNUSED(wm), ARegion *region) +static void node_header_region_init(wmWindowManager * /*wm*/, ARegion *region) { ED_region_header_init(region); } @@ -820,6 +831,9 @@ static void node_region_listener(const wmRegionListenerParams *params) ED_region_tag_redraw(region); } break; + case NC_VIEWER_PATH: + ED_region_tag_redraw(region); + break; } } @@ -973,7 +987,7 @@ static void node_id_remap_cb(ID *old_id, ID *new_id, void *user_data) } } -static void node_id_remap(ScrArea *UNUSED(area), SpaceLink *slink, const IDRemapper *mappings) +static void node_id_remap(ScrArea * /*area*/, SpaceLink *slink, const IDRemapper *mappings) { /* Although we should be able to perform all the mappings in a single go this lead to issues when * running the python test cases. Somehow the nodetree/edittree weren't updated to the new @@ -1011,6 +1025,81 @@ static void node_space_subtype_item_extend(bContext *C, EnumPropertyItem **item, } } +static void node_blend_read_data(BlendDataReader *reader, SpaceLink *sl) +{ + SpaceNode *snode = (SpaceNode *)sl; + + if (snode->gpd) { + BLO_read_data_address(reader, &snode->gpd); + BKE_gpencil_blend_read_data(reader, snode->gpd); + } + + BLO_read_list(reader, &snode->treepath); + snode->edittree = nullptr; + snode->runtime = nullptr; +} + +static void node_blend_read_lib(BlendLibReader *reader, ID *parent_id, SpaceLink *sl) +{ + SpaceNode *snode = (SpaceNode *)sl; + + /* node tree can be stored locally in id too, link this first */ + BLO_read_id_address(reader, parent_id->lib, &snode->id); + BLO_read_id_address(reader, parent_id->lib, &snode->from); + + bNodeTree *ntree = snode->id ? ntreeFromID(snode->id) : nullptr; + if (ntree) { + snode->nodetree = ntree; + } + else { + BLO_read_id_address(reader, parent_id->lib, &snode->nodetree); + } + + bNodeTreePath *path; + for (path = static_cast<bNodeTreePath *>(snode->treepath.first); path; path = path->next) { + if (path == snode->treepath.first) { + /* first nodetree in path is same as snode->nodetree */ + path->nodetree = snode->nodetree; + } + else { + BLO_read_id_address(reader, parent_id->lib, &path->nodetree); + } + + if (!path->nodetree) { + break; + } + } + + /* remaining path entries are invalid, remove */ + bNodeTreePath *path_next; + for (; path; path = path_next) { + path_next = path->next; + + BLI_remlink(&snode->treepath, path); + MEM_freeN(path); + } + + /* edittree is just the last in the path, + * set this directly since the path may have been shortened above */ + if (snode->treepath.last) { + path = static_cast<bNodeTreePath *>(snode->treepath.last); + snode->edittree = path->nodetree; + } + else { + snode->edittree = nullptr; + } +} + +static void node_blend_write(BlendWriter *writer, SpaceLink *sl) +{ + SpaceNode *snode = (SpaceNode *)sl; + BLO_write_struct(writer, SpaceNode, snode); + + LISTBASE_FOREACH (bNodeTreePath *, path, &snode->treepath) { + BLO_write_struct(writer, bNodeTreePath, path); + } +} + } // namespace blender::ed::space_node void ED_spacetype_node() @@ -1038,6 +1127,9 @@ void ED_spacetype_node() st->space_subtype_item_extend = node_space_subtype_item_extend; st->space_subtype_get = node_space_subtype_get; st->space_subtype_set = node_space_subtype_set; + st->blend_read_data = node_blend_read_data; + st->blend_read_lib = node_blend_read_lib; + st->blend_write = node_blend_write; /* regions: main window */ art = MEM_cnew<ARegionType>("spacetype node region"); @@ -1088,5 +1180,8 @@ void ED_spacetype_node() art->draw = node_toolbar_region_draw; BLI_addhead(&st->regiontypes, art); + WM_menutype_add(MEM_new<MenuType>(__func__, add_catalog_assets_menu_type())); + WM_menutype_add(MEM_new<MenuType>(__func__, add_root_catalogs_menu_type())); + BKE_spacetype_register(st); } |