diff --git a/scripts/startup/bl_ui/node_add_menu.py b/scripts/startup/bl_ui/node_add_menu.py index ae23abd92fb..5bb066e53c5 100644 --- a/scripts/startup/bl_ui/node_add_menu.py +++ b/scripts/startup/bl_ui/node_add_menu.py @@ -25,33 +25,48 @@ def add_node_type(layout, node_type, *, label=None, poll=None): return props -def draw_node_group_add_menu(context, layout): - """Add items to the layout used for interacting with node groups.""" +def node_groups(context): + """All node groups allowed in current context.""" space_node = context.space_data node_tree = space_node.edit_tree all_node_groups = context.blend_data.node_groups + if node_tree is None: + return None + def group_allowed_in_context(group): + if group.bl_idname is node_tree.bl_idname: + return False + if group.name.startswith('.'): + return False + if group.contains_tree(node_tree): + return False + return True + + return [group for group in all_node_groups if group_allowed_in_context(group)] + + +def draw_node_group_add_menu(context, layout, groups = None): + """Add items to the layout used for interacting with node groups.""" + space_node = context.space_data + node_tree = space_node.edit_tree + if groups is None: + groups = node_groups(context) + + all_node_groups = context.blend_data.node_groups if node_tree in all_node_groups.values(): layout.separator() add_node_type(layout, "NodeGroupInput") add_node_type(layout, "NodeGroupOutput") - if node_tree: + if node_tree and groups: from nodeitems_builtins import node_tree_group_type - groups = [ - group for group in context.blend_data.node_groups - if (group.bl_idname == node_tree.bl_idname and - not group.contains_tree(node_tree) and - not group.name.startswith('.')) - ] - if groups: - layout.separator() - for group in groups: - props = add_node_type(layout, node_tree_group_type[group.bl_idname], label=group.name) - ops = props.settings.add() - ops.name = "node_tree" - ops.value = "bpy.data.node_groups[%r]" % group.name + layout.separator() + for group in groups: + props = add_node_type(layout, node_tree_group_type[group.bl_idname], label=group.name) + ops = props.settings.add() + ops.name = "node_tree" + ops.value = "bpy.data.node_groups[%r]" % group.name def draw_assets_for_catalog(layout, catalog_path): diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index afd0614da4b..9be9984f893 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -456,10 +456,13 @@ class NODE_MT_category_GEO_OUTPUT(Menu): bl_idname = "NODE_MT_category_GEO_OUTPUT" bl_label = "Output" - def draw(self, _context): + def draw(self, context): layout = self.layout node_add_menu.add_node_type(layout, "NodeGroupOutput") node_add_menu.add_node_type(layout, "GeometryNodeViewer") + groups = node_add_menu.node_groups(context) + groups = [group for group in groups if group.is_viewer] + node_add_menu.draw_node_group_add_menu(context, layout, groups) node_add_menu.draw_assets_for_catalog(layout, self.bl_label) diff --git a/source/blender/blenkernel/BKE_compute_contexts.hh b/source/blender/blenkernel/BKE_compute_contexts.hh index f8b0260e6fc..13ff2951f9f 100644 --- a/source/blender/blenkernel/BKE_compute_contexts.hh +++ b/source/blender/blenkernel/BKE_compute_contexts.hh @@ -64,6 +64,31 @@ class NodeGroupComputeContext : public ComputeContext { void print_current_in_line(std::ostream &stream) const override; }; +class NodeViewerGroupComputeContext : public ComputeContext { + private: + static constexpr const char *s_static_type = "NODE_GROUP"; + + int32_t node_id_; + +#ifdef DEBUG + std::string debug_node_name_; +#endif + + public: + NodeViewerGroupComputeContext(const ComputeContext *parent, + int32_t node_id, + const std::optional &cached_hash = {}); + NodeViewerGroupComputeContext(const ComputeContext *parent, const bNode &node); + + int32_t node_id() const + { + return node_id_; + } + + private: + void print_current_in_line(std::ostream &stream) const override; +}; + class SimulationZoneComputeContext : public ComputeContext { private: static constexpr const char *s_static_type = "SIMULATION_ZONE"; diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index 0b8e9b87841..8fc2d385751 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -125,6 +125,10 @@ bool nodeLinkIsSelected(const bNodeLink *link); void nodeInternalRelink(bNodeTree *ntree, bNode *node); +bool node_is_viewer_group(const bNode &node); + +int get_internal_link_type_priority(const bNodeSocketType &from, const bNodeSocketType &to); + float2 nodeToView(const bNode *node, float2 loc); float2 nodeFromView(const bNode *node, float2 view_loc); diff --git a/source/blender/blenkernel/BKE_node_runtime.hh b/source/blender/blenkernel/BKE_node_runtime.hh index 07da363415f..1acc584f7c6 100644 --- a/source/blender/blenkernel/BKE_node_runtime.hh +++ b/source/blender/blenkernel/BKE_node_runtime.hh @@ -589,6 +589,11 @@ inline blender::Span bNodeTree::interface_items( return this->tree_interface.runtime->items_; } +inline bool bNodeTree::is_viewer() const +{ + return this->flag & NTREE_IS_VIEWER; +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/BKE_viewer_path.h b/source/blender/blenkernel/BKE_viewer_path.h index aa78593c81e..3ce2d7f3978 100644 --- a/source/blender/blenkernel/BKE_viewer_path.h +++ b/source/blender/blenkernel/BKE_viewer_path.h @@ -56,6 +56,7 @@ ModifierViewerPathElem *BKE_viewer_path_elem_new_modifier(void); GroupNodeViewerPathElem *BKE_viewer_path_elem_new_group_node(void); SimulationZoneViewerPathElem *BKE_viewer_path_elem_new_simulation_zone(void); ViewerNodeViewerPathElem *BKE_viewer_path_elem_new_viewer_node(void); +ViewerNodeGroupViewerPathElem *BKE_viewer_path_elem_new_viewer_node_group(void); RepeatZoneViewerPathElem *BKE_viewer_path_elem_new_repeat_zone(void); ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src); bool BKE_viewer_path_elem_equal(const ViewerPathElem *a, diff --git a/source/blender/blenkernel/intern/compute_contexts.cc b/source/blender/blenkernel/intern/compute_contexts.cc index ac47233b535..eae7d654107 100644 --- a/source/blender/blenkernel/intern/compute_contexts.cc +++ b/source/blender/blenkernel/intern/compute_contexts.cc @@ -64,6 +64,48 @@ void NodeGroupComputeContext::print_current_in_line(std::ostream &stream) const stream << "Node ID: " << node_id_; } +NodeViewerGroupComputeContext::NodeViewerGroupComputeContext( + const ComputeContext *parent, + const int32_t node_id, + const std::optional &cached_hash) + : ComputeContext(s_static_type, parent), node_id_(node_id) +{ + if (cached_hash.has_value()) { + hash_ = *cached_hash; + } + else { + /* Mix static type and node id into a single buffer so that only a single call to #mix_in is + * necessary. */ + const int type_size = strlen(s_static_type); + const int buffer_size = type_size + 1 + sizeof(int32_t); + DynamicStackBuffer<64, 8> buffer_owner(buffer_size, 8); + char *buffer = static_cast(buffer_owner.buffer()); + memcpy(buffer, s_static_type, type_size + 1); + memcpy(buffer + type_size + 1, &node_id_, sizeof(int32_t)); + hash_.mix_in(buffer, buffer_size); + } +} + +NodeViewerGroupComputeContext::NodeViewerGroupComputeContext(const ComputeContext *parent, + const bNode &node) + : NodeViewerGroupComputeContext(parent, node.identifier) +{ +#ifdef DEBUG + debug_node_name_ = node.name; +#endif +} + +void NodeViewerGroupComputeContext::print_current_in_line(std::ostream &stream) const +{ +#ifdef DEBUG + if (!debug_node_name_.empty()) { + stream << "Viewer Node: " << debug_node_name_; + return; + } +#endif + stream << "Viewer Node ID: " << node_id_; +} + SimulationZoneComputeContext::SimulationZoneComputeContext(const ComputeContext *parent, const int32_t output_node_id) : ComputeContext(s_static_type, parent), output_node_id_(output_node_id) diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 940dd1d46af..4cba92a09a9 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -3702,6 +3702,12 @@ void nodeSetActive(bNodeTree *ntree, bNode *node) namespace blender::bke { +bool node_is_viewer_group(const bNode &node) +{ + const bNodeTree *tree = reinterpret_cast(node.id); + return node.is_group() && (tree != nullptr) && tree->is_viewer(); +} + void nodeSetSocketAvailability(bNodeTree *ntree, bNodeSocket *sock, const bool is_available) { if (is_available == sock->is_available()) { diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index b7b5f208e61..617f103c9d3 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -82,11 +82,11 @@ namespace blender::bke { * `< 0`: never connect these types. * `>= 0`: priority of connection (higher values chosen first). */ -static int get_internal_link_type_priority(const bNodeSocketType *from, const bNodeSocketType *to) +int get_internal_link_type_priority(const bNodeSocketType &from, const bNodeSocketType &to) { - switch (to->type) { + switch (to.type) { case SOCK_RGBA: - switch (from->type) { + switch (from.type) { case SOCK_RGBA: return 4; case SOCK_FLOAT: @@ -98,7 +98,7 @@ static int get_internal_link_type_priority(const bNodeSocketType *from, const bN } return -1; case SOCK_VECTOR: - switch (from->type) { + switch (from.type) { case SOCK_VECTOR: return 4; case SOCK_FLOAT: @@ -110,7 +110,7 @@ static int get_internal_link_type_priority(const bNodeSocketType *from, const bN } return -1; case SOCK_FLOAT: - switch (from->type) { + switch (from.type) { case SOCK_FLOAT: return 5; case SOCK_INT: @@ -124,7 +124,7 @@ static int get_internal_link_type_priority(const bNodeSocketType *from, const bN } return -1; case SOCK_INT: - switch (from->type) { + switch (from.type) { case SOCK_INT: return 5; case SOCK_FLOAT: @@ -138,7 +138,7 @@ static int get_internal_link_type_priority(const bNodeSocketType *from, const bN } return -1; case SOCK_BOOLEAN: - switch (from->type) { + switch (from.type) { case SOCK_BOOLEAN: return 5; case SOCK_INT: @@ -155,7 +155,7 @@ static int get_internal_link_type_priority(const bNodeSocketType *from, const bN /* The rest of the socket types only allow an internal link if both the input and output socket * have the same type. If the sockets are custom, we check the idname instead. */ - if (to->type == from->type && (to->type != SOCK_CUSTOM || STREQ(to->idname, from->idname))) { + if (to.type == from.type && (to.type != SOCK_CUSTOM || STREQ(to.idname, from.idname))) { return 1; } @@ -485,6 +485,8 @@ class NodeTreeMainUpdater { this->remove_unused_previews_when_necessary(ntree); this->make_node_previews_dirty(ntree); + this->propagate_viewers(ntree); + this->propagate_runtime_flags(ntree); if (ntree.type == NTREE_GEOMETRY) { if (node_field_inferencing::update_field_inferencing(ntree)) { @@ -664,8 +666,8 @@ class NodeTreeMainUpdater { if (input_socket->flag & SOCK_NO_INTERNAL_LINK) { continue; } - const int priority = get_internal_link_type_priority(input_socket->typeinfo, - output_socket->typeinfo); + const int priority = get_internal_link_type_priority(*input_socket->typeinfo, + *output_socket->typeinfo); if (priority < 0) { continue; } @@ -733,6 +735,37 @@ class NodeTreeMainUpdater { } } + void propagate_viewers(bNodeTree &ntree) + { + ntree.ensure_topology_cache(); + ntree.ensure_interface_cache(); + ntree.flag &= ~NTREE_IS_VIEWER; + if (!ntree.interface_outputs().is_empty()) { + return; + } + + const Span viewers = ntree.nodes_by_type("GeometryNodeViewer"); + const Span groups = ntree.group_nodes(); + + const bNode *viewer_node = viewers.size() != 1 ? nullptr : viewers.first(); + const bNode *viewer_group = [&]() -> bNode * { + bNode *viewer_group = nullptr; + for (bNode *group_node : groups) { + if (bke::node_is_viewer_group(*group_node)) { + if (viewer_group != nullptr) { + return nullptr; + } + viewer_group = group_node; + } + } + return viewer_group; + }(); + + if ((viewer_node == nullptr) != (viewer_group == nullptr)) { + ntree.flag |= NTREE_IS_VIEWER; + } + } + void propagate_runtime_flags(const bNodeTree &ntree) { ntree.ensure_topology_cache(); @@ -892,6 +925,9 @@ class NodeTreeMainUpdater { if (node.typeinfo->nclass == NODE_CLASS_OUTPUT) { return true; } + if (bke::node_is_viewer_group(node)) { + return true; + } if (node.type == NODE_GROUP_OUTPUT) { return true; } diff --git a/source/blender/blenkernel/intern/viewer_path.cc b/source/blender/blenkernel/intern/viewer_path.cc index 9fbf8304370..4b8c9d33854 100644 --- a/source/blender/blenkernel/intern/viewer_path.cc +++ b/source/blender/blenkernel/intern/viewer_path.cc @@ -80,6 +80,11 @@ void BKE_viewer_path_blend_write(BlendWriter *writer, const ViewerPath *viewer_p BLO_write_struct(writer, GroupNodeViewerPathElem, typed_elem); break; } + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: { + const auto *typed_elem = reinterpret_cast(elem); + BLO_write_struct(writer, ViewerNodeGroupViewerPathElem, typed_elem); + break; + } case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: { const auto *typed_elem = reinterpret_cast(elem); BLO_write_struct(writer, SimulationZoneViewerPathElem, typed_elem); @@ -109,6 +114,7 @@ void BKE_viewer_path_blend_read_data(BlendDataReader *reader, ViewerPath *viewer case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: case VIEWER_PATH_ELEM_TYPE_ID: { break; @@ -133,6 +139,7 @@ void BKE_viewer_path_foreach_id(LibraryForeachIDData *data, ViewerPath *viewer_p } case VIEWER_PATH_ELEM_TYPE_MODIFIER: case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { @@ -153,6 +160,7 @@ void BKE_viewer_path_id_remap(ViewerPath *viewer_path, const IDRemapper *mapping } case VIEWER_PATH_ELEM_TYPE_MODIFIER: case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { @@ -181,6 +189,9 @@ ViewerPathElem *BKE_viewer_path_elem_new(const ViewerPathElemType type) case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: { return &make_elem(type)->base; } + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: { + return &make_elem(type)->base; + } case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: { return &make_elem(type)->base; } @@ -224,6 +235,12 @@ ViewerNodeViewerPathElem *BKE_viewer_path_elem_new_viewer_node() BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_VIEWER_NODE)); } +ViewerNodeGroupViewerPathElem *BKE_viewer_path_elem_new_viewer_node_group() +{ + return reinterpret_cast( + BKE_viewer_path_elem_new(VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP)); +} + RepeatZoneViewerPathElem *BKE_viewer_path_elem_new_repeat_zone() { return reinterpret_cast( @@ -257,6 +274,12 @@ ViewerPathElem *BKE_viewer_path_elem_copy(const ViewerPathElem *src) new_elem->node_id = old_elem->node_id; break; } + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: { + const auto *old_elem = reinterpret_cast(src); + auto *new_elem = reinterpret_cast(dst); + new_elem->node_id = old_elem->node_id; + break; + } case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: { const auto *old_elem = reinterpret_cast(src); auto *new_elem = reinterpret_cast(dst); @@ -303,6 +326,11 @@ bool BKE_viewer_path_elem_equal(const ViewerPathElem *a, const auto *b_elem = reinterpret_cast(b); return a_elem->node_id == b_elem->node_id; } + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: { + const auto *a_elem = reinterpret_cast(a); + const auto *b_elem = reinterpret_cast(b); + return a_elem->node_id == b_elem->node_id; + } case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: { const auto *a_elem = reinterpret_cast(a); const auto *b_elem = reinterpret_cast(b); @@ -329,6 +357,7 @@ void BKE_viewer_path_elem_free(ViewerPathElem *elem) switch (ViewerPathElemType(elem->type)) { case VIEWER_PATH_ELEM_TYPE_ID: case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { diff --git a/source/blender/editors/include/ED_viewer_path.hh b/source/blender/editors/include/ED_viewer_path.hh index fb4ebb47453..633dc4c1a59 100644 --- a/source/blender/editors/include/ED_viewer_path.hh +++ b/source/blender/editors/include/ED_viewer_path.hh @@ -40,7 +40,14 @@ struct ViewerPathForGeometryNodesViewer { blender::StringRefNull modifier_name; /* Contains only group node and simulation zone elements. */ blender::Vector node_path; - int32_t viewer_node_id; + Vector node_ids; +}; + +struct ViewerPathMemory { + ViewerPath viewer_group_path; + + ViewerPathMemory(); + ~ViewerPathMemory(); }; /** @@ -48,14 +55,14 @@ struct ViewerPathForGeometryNodesViewer { * work. */ std::optional parse_geometry_nodes_viewer( - const ViewerPath &viewer_path); + const ViewerPath &viewer_path, ViewerPathMemory &memory); /** * Finds the node referenced by the #ViewerPath within the provided editor. If no node is * referenced, null is returned. When two different editors show the same node group but in a * different context, it's possible that the same node is active in one editor but not the other. */ -bNode *find_geometry_nodes_viewer(const ViewerPath &viewer_path, SpaceNode &snode); +bNode *find_geometry_nodes_viewer_in_space(const ViewerPath &viewer_path, SpaceNode &snode); /** * Checks if the node referenced by the viewer path and its entire context still exists. The node diff --git a/source/blender/editors/screen/workspace_listen.cc b/source/blender/editors/screen/workspace_listen.cc index 08cf10b5a9a..795bbc377e2 100644 --- a/source/blender/editors/screen/workspace_listen.cc +++ b/source/blender/editors/screen/workspace_listen.cc @@ -2,6 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include + #include "BKE_context.h" #include "ED_screen.hh" @@ -34,6 +36,8 @@ static void validate_viewer_paths(bContext &C, WorkSpace &workspace) break; } + std::cout << __func__ << std::endl; + WM_event_add_notifier(&C, NC_VIEWER_PATH, nullptr); } diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index f8250f2651e..6c7977bbb80 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -1063,8 +1063,12 @@ static int node_get_colorid(TreeDrawContext &tree_draw_ctx, const bNode &node) return TH_NODE_VECTOR; case NODE_CLASS_OP_FILTER: return TH_NODE_FILTER; - case NODE_CLASS_GROUP: + case NODE_CLASS_GROUP: { + if (bke::node_is_viewer_group(node)) { + return &node == tree_draw_ctx.active_geometry_nodes_viewer ? TH_NODE_OUTPUT : TH_NODE; + } return TH_NODE_GROUP; + } case NODE_CLASS_INTERFACE: return TH_NODE_INTERFACE; case NODE_CLASS_MATTE: @@ -2984,7 +2988,7 @@ static void node_draw_basis(const bContext &C, ""); UI_block_emboss_set(&block, UI_EMBOSS); } - if (node.type == GEO_NODE_VIEWER) { + if (node.type == GEO_NODE_VIEWER || bke::node_is_viewer_group(node)) { const bool is_active = &node == tree_draw_ctx.active_geometry_nodes_viewer; iconofs -= iconbutw; UI_block_emboss_set(&block, UI_EMBOSS_NONE); @@ -4114,7 +4118,7 @@ static void draw_nodetree(const bContext &C, log->ensure_node_run_time(); } const WorkSpace *workspace = CTX_wm_workspace(&C); - tree_draw_ctx.active_geometry_nodes_viewer = viewer_path::find_geometry_nodes_viewer( + tree_draw_ctx.active_geometry_nodes_viewer = viewer_path::find_geometry_nodes_viewer_in_space( workspace->viewer_path, *snode); } else if (ntree.type == NTREE_COMPOSIT) { diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index 1c818c1b7bc..a080c21ddbf 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -691,7 +691,7 @@ void ED_node_set_active( } nodeSetActive(ntree, node); - if (node->type == NODE_GROUP) { + if (node->is_group() && !blender::bke::node_is_viewer_group(*node)) { return; } @@ -813,10 +813,10 @@ void ED_node_set_active( } } else if (ntree->type == NTREE_GEOMETRY) { - if (node->type == GEO_NODE_VIEWER) { + if (node->type == GEO_NODE_VIEWER || blender::bke::node_is_viewer_group(*node)) { if ((node->flag & NODE_DO_OUTPUT) == 0) { for (bNode *node_iter : ntree->all_nodes()) { - if (node_iter->type == GEO_NODE_VIEWER) { + if (node_iter->type == GEO_NODE_VIEWER || blender::bke::node_is_viewer_group(*node)) { node_iter->flag &= ~NODE_DO_OUTPUT; } } @@ -1684,21 +1684,14 @@ 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); + bNode *active_viewer = viewer_path::find_geometry_nodes_viewer_in_space(workspace.viewer_path, + snode); - for (bNode *node : snode.edittree->all_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); - } + if (active_viewer == nullptr) { + return OPERATOR_FINISHED; } - + active_viewer->flag &= ~NODE_DO_OUTPUT; + BKE_ntree_update_tag_node_property(snode.edittree, active_viewer); ED_node_tree_propagate_change(C, CTX_data_main(C), snode.edittree); return OPERATOR_FINISHED; diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index 51752ab4c7f..f83f2a1c029 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -50,6 +50,7 @@ #include "BLT_translation.h" #include "NOD_node_declaration.hh" +#include "NOD_socket.hh" #include "NOD_socket_declarations.hh" #include "NOD_socket_declarations_geometry.hh" @@ -413,6 +414,28 @@ namespace viewer_linking { /** \name Link Viewer Operator * \{ */ +/* Find index of socket int viewer that the best to connect to a to_viewer. */ +static std::optional best_socket_of_viewer(const bNode &viewer, const bNodeSocket &to_viewer) +{ + const Span inputs = viewer.input_sockets(); + if (inputs.is_empty()) { + return std::nullopt; + } + + int max_value = 0; + std::optional index_of_max; + for (const int index : inputs.index_range()) { + const bNodeSocket &socket = *inputs[index]; + const int priority = bke::get_internal_link_type_priority(*to_viewer.typeinfo, + *socket.typeinfo); + if (priority > max_value) { + max_value = priority; + index_of_max.emplace(index); + } + } + return index_of_max; +} + /* Depending on the node tree type, different socket types are supported by viewer nodes. */ static bool socket_can_be_viewed(const bNode &node, const bNodeSocket &socket) { @@ -425,19 +448,13 @@ static bool socket_can_be_viewed(const bNode &node, const bNodeSocket &socket) if (socket.owner_tree().type != NTREE_GEOMETRY) { return true; } - return ELEM(socket.typeinfo->type, - SOCK_GEOMETRY, - SOCK_FLOAT, - SOCK_VECTOR, - SOCK_INT, - SOCK_BOOLEAN, - SOCK_ROTATION, - SOCK_RGBA); + return true; } /** * Find the socket to link to in a viewer node. */ + static bNodeSocket *node_link_viewer_get_socket(bNodeTree &ntree, bNode &viewer_node, bNodeSocket &src_socket) @@ -466,7 +483,8 @@ static bNodeSocket *node_link_viewer_get_socket(bNodeTree &ntree, static bool is_viewer_node(const bNode &node) { - return ELEM(node.type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER); + return ELEM(node.type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER) || + bke::node_is_viewer_group(node); } static bool is_viewer_socket_in_viewer(const bNodeSocket &socket) @@ -593,16 +611,60 @@ static void finalize_viewer_link(const bContext &C, 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) +static Array all_view_nodes(const bNodeTree &tree) { + return {}; +} + +static Array all_connected_viewer(const bNodeTree &tree, const bNode &bnode_to_view) +{ + return {}; +} + +static const bNode *active_viewer_in_tree(const bNodeTree &tree) +{ + return {}; +} + +static int link_view_sockets(const bContext &C, + SpaceNode &snode, + bNodeTree &btree, + bNodeSocket &bsocket_to_view, + bNodeSocket &bsocket_of_viewer) +{ + bNode &node = bsocket_to_view.owner_node(); + bNode &viewer = bsocket_of_viewer.owner_node(); + + bNodeLink *viewer_link = nullptr; + for (bNodeLink *link : btree.all_links()) { + if (link->tosock == &bsocket_of_viewer) { + viewer_link = link; + break; + } + } + if (viewer_link == nullptr) { + viewer_link = nodeAddLink(&btree, &node, &bsocket_to_view, &viewer, &bsocket_of_viewer); + } + else { + viewer_link->fromnode = &node; + viewer_link->fromsock = &bsocket_to_view; + BKE_ntree_update_tag_link_changed(&btree); + } + finalize_viewer_link(C, snode, viewer, *viewer_link); + return OPERATOR_CANCELLED; +} + +static bNodeSocket *best_view_socket_find_or_new(const bNodeTree &btree, + const bNodeSocket &bsocket_to_view, + const Span viewers, + const Span connected_viewers, + const bNode *active_viewer) +{ + return {}; + /* bNode *viewer_node = nullptr; - /* Try to find a viewer that is already active. */ for (bNode *node : btree.all_nodes()) { - if (is_viewer_node(*node)) { + if (is_viewer_node(*node) || bke::node_is_viewer_group(*node)) { if (node->flag & NODE_DO_OUTPUT) { viewer_node = node; break; @@ -610,7 +672,6 @@ static int view_socket(const bContext &C, } } - /* 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; @@ -635,46 +696,357 @@ static int view_socket(const bContext &C, socket_location.y / UI_SCALE_FAC}; viewer_node = add_static_node(C, viewer_type, location); } - - bNodeSocket *viewer_bsocket = node_link_viewer_get_socket(btree, *viewer_node, bsocket_to_view); - if (viewer_bsocket == nullptr) { - return OPERATOR_CANCELLED; - } - bNodeLink *viewer_link = nullptr; - LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &btree.links) { - if (link->tosock == viewer_bsocket) { - viewer_link = link; - break; - } - } - if (viewer_link == nullptr) { - viewer_link = nodeAddLink( - &btree, &bnode_to_view, &bsocket_to_view, viewer_node, viewer_bsocket); - } - else { - viewer_link->fromnode = &bnode_to_view; - viewer_link->fromsock = &bsocket_to_view; - BKE_ntree_update_tag_link_changed(&btree); - } - finalize_viewer_link(C, snode, *viewer_node, *viewer_link); - return OPERATOR_CANCELLED; + */ } -static int node_link_viewer(const bContext &C, bNode &bnode_to_view, bNodeSocket *bsocket_to_view) +struct ViewerSocketTrait { + std::function(bNodeTree &)> finalize; + + eNodeSocketDatatype type; + StringRef socket_name; + StringRef node_name; + + bool node_is_connected = false; + + float prioriti = 1.0f; +}; + +static void traits_for_viewer(Vector &r_traits) +{ + static const Array viewer_types = { + SOCK_BOOLEAN, SOCK_FLOAT, SOCK_RGBA, SOCK_INT, SOCK_ROTATION, SOCK_VECTOR}; + + static const auto new_viewer = [](bNodeTree &tree) -> bNode & { + return *nodeAddStaticNode(nullptr, &tree, GEO_NODE_VIEWER); + }; + + ViewerSocketTrait trait; + trait.type = SOCK_GEOMETRY; + trait.socket_name = "Geometry"; + trait.node_name = "Viewer"; + trait.prioriti = 1.0f; + + trait.finalize = [=](bNodeTree &tree) -> std::pair { + bNode &viewer = new_viewer(tree); + tree.ensure_topology_cache(); + return {&viewer, viewer.input_sockets().first()}; + }; + + r_traits.append(std::move(trait)); + + for (const eNodeSocketDatatype socket_type : viewer_types) { + ViewerSocketTrait trait; + trait.type = socket_type; + trait.socket_name = "Value"; + trait.node_name = "Viewer"; + trait.prioriti = 0.5f; + + trait.finalize = [=](bNodeTree &tree) -> std::pair { + bNode &viewer = new_viewer(tree); + tree.ensure_topology_cache(); + + for (bNodeSocket *socket : viewer.input_sockets().drop_front(1)) { + const eNodeSocketDatatype other_socket_type = eNodeSocketDatatype(socket->type); + if (socket_type == other_socket_type) { + NodeGeometryViewer &storage = *static_cast(viewer.storage); + storage.data_type = *bke::socket_type_to_custom_data_type(other_socket_type); + viewer.typeinfo->updatefunc(&tree, &viewer); + return {&viewer, socket}; + } + } + + BLI_assert_unreachable(); + return {}; + }; + + r_traits.append(std::move(trait)); + } +} + +static void traits_for_viewers(const Span viewers, + const Span depend_on_node, + Vector &r_traits) +{ + static const Array viewer_types = { + SOCK_BOOLEAN, SOCK_FLOAT, SOCK_RGBA, SOCK_INT, SOCK_ROTATION, SOCK_VECTOR}; + for (const bNode *viewer : viewers) { + + if (!viewer->input_sockets().first()->is_directly_linked()) { + ViewerSocketTrait trait; + trait.type = SOCK_GEOMETRY; + trait.socket_name = "Geometry"; + trait.node_name = viewer->name; + trait.prioriti = 1.0f; + + trait.node_is_connected = depend_on_node[viewer->index()]; + + const int32_t identifiers = viewer->identifier; + trait.finalize = [=](bNodeTree &tree) -> std::pair { + tree.ensure_topology_cache(); + bNode &viewer = *tree.node_by_id(identifiers); + return {&viewer, viewer.input_sockets().first()}; + }; + + r_traits.append(std::move(trait)); + } + + bool value_is_linked = false; + + for (const bNodeSocket *socket : viewer->input_sockets().drop_front(1)) { + value_is_linked |= socket->is_directly_linked(); + } + + if (!value_is_linked) { + for (const eNodeSocketDatatype socket_type : viewer_types) { + ViewerSocketTrait trait; + trait.type = socket_type; + trait.socket_name = "Value"; + trait.node_name = viewer->name; + trait.prioriti = 1.0f; + + trait.node_is_connected = depend_on_node[viewer->index()]; + + const int32_t identifiers = viewer->identifier; + NodeGeometryViewer &storage = *static_cast(viewer->storage); + + if (storage.data_type == *bke::socket_type_to_custom_data_type(socket_type)) { + trait.prioriti = 2.0f; + } + + trait.finalize = [=](bNodeTree &tree) -> std::pair { + tree.ensure_topology_cache(); + bNode &viewer = *tree.node_by_id(identifiers); + for (bNodeSocket *socket : viewer.input_sockets().drop_front(1)) { + const eNodeSocketDatatype other_socket_type = eNodeSocketDatatype(socket->type); + if (socket_type == other_socket_type) { + NodeGeometryViewer &storage = *static_cast(viewer.storage); + storage.data_type = *bke::socket_type_to_custom_data_type(other_socket_type); + viewer.typeinfo->updatefunc(&tree, &viewer); + return {&viewer, socket}; + } + } + BLI_assert_unreachable(); + return {}; + }; + + r_traits.append(std::move(trait)); + } + } + } +} + +static void traits_for_groups(Main &bmain, Vector &r_traits) +{ + LISTBASE_FOREACH (bNodeTree *, group, &bmain.nodetrees) { + if (!group->is_viewer()) { + continue; + } + + group->ensure_topology_cache(); + group->ensure_interface_cache(); + + const Span inputs = group->interface_inputs(); + for (const int index : inputs.index_range()) { + const bNodeTreeInterfaceSocket &input = *inputs[index]; + + ViewerSocketTrait trait; + trait.type = eNodeSocketDatatype(input.socket_typeinfo()->type); + trait.socket_name = input.name; + trait.node_name = group->id.name; + trait.prioriti = 3.0f; + + trait.finalize = [=](bNodeTree &tree) -> std::pair { + tree.ensure_topology_cache(); + + bNode *node_group = nodeAddStaticNode(nullptr, &tree, NODE_GROUP); + node_group->id = &group->id; + id_us_plus(&group->id); + tree.ensure_topology_cache(); + bke::node_field_inferencing::update_field_inferencing(*group); + nodes::update_node_declaration_and_sockets(tree, *node_group); + tree.ensure_topology_cache(); + + return {node_group, node_group->input_sockets()[index]}; + }; + + r_traits.append(std::move(trait)); + } + } +} + +static void traits_for_exist_groups(const Span groups, + Vector &r_traits) +{ +} + +int get_link_type_priority(const eNodeSocketDatatype from, const eNodeSocketDatatype to) +{ + switch (to) { + case SOCK_RGBA: + switch (from) { + case SOCK_RGBA: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_INT: + return 2; + case SOCK_BOOLEAN: + return 1; + default: + break; + } + return -1; + case SOCK_VECTOR: + switch (from) { + case SOCK_VECTOR: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_INT: + return 2; + case SOCK_BOOLEAN: + return 1; + default: + break; + } + return -1; + case SOCK_FLOAT: + switch (from) { + case SOCK_FLOAT: + return 5; + case SOCK_INT: + return 4; + case SOCK_BOOLEAN: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + default: + break; + } + return -1; + case SOCK_INT: + switch (from) { + case SOCK_INT: + return 5; + case SOCK_FLOAT: + return 4; + case SOCK_BOOLEAN: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + default: + break; + } + return -1; + case SOCK_BOOLEAN: + switch (from) { + case SOCK_BOOLEAN: + return 5; + case SOCK_INT: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + default: + break; + } + return -1; + default: + break; + } + + if (to == from) { + return 1; + } + + return -1; +} + +static int node_link_viewer(const bContext &C, bNode &bnode_to_view, bNodeSocket *node_socket) { SpaceNode &snode = *CTX_wm_space_node(&C); bNodeTree *btree = snode.edittree; btree->ensure_topology_cache(); - if (bsocket_to_view == nullptr) { - bsocket_to_view = determine_socket_to_view(bnode_to_view); + const Span viewers = btree->nodes_by_type("GeometryNodeViewer"); + const Span groups = btree->group_nodes(); + // const Array connected_viewers = all_connected_viewer(*btree, bnode_to_view); + const bNode *active_viewer = active_viewer_in_tree(*btree); + + if (node_socket == nullptr) { + node_socket = determine_socket_to_view(bnode_to_view); } - if (bsocket_to_view == nullptr) { + if (node_socket == nullptr) { return OPERATOR_CANCELLED; } - return view_socket(C, snode, *btree, bnode_to_view, *bsocket_to_view); + const Span left_to_right = btree->toposort_left_to_right(); + Array depend_on_node(left_to_right.size(), false); + for (const bNode *node : left_to_right) { + for (const bNodeSocket *input : node->input_sockets()) { + for (const bNodeSocket *output_socket : input->directly_linked_sockets()) { + const bNode &other = output_socket->owner_node(); + if (&other == &bnode_to_view) { + depend_on_node[node->index()] = true; + } + depend_on_node[node->index()] |= depend_on_node[other.index()]; + } + } + } + + Vector traits; + traits_for_viewer(traits); + traits_for_viewers(viewers, depend_on_node, traits); + // traits_for_viewers({active_viewer}, traits); + traits_for_exist_groups(groups, traits); + traits_for_groups(*CTX_data_main(&C), traits); + + if (traits.is_empty()) { + return OPERATOR_CANCELLED; + } + + const ViewerSocketTrait &viewer_trait = *std::max_element( + traits.begin(), + traits.end(), + [&](const ViewerSocketTrait &a, const ViewerSocketTrait &b) -> bool { + const bool to_connected = a.node_is_connected != b.node_is_connected; + const bool connected_node = b.node_is_connected; + + const int a_type_priority = get_link_type_priority(a.type, + eNodeSocketDatatype(node_socket->type)); + const int b_type_priority = get_link_type_priority(b.type, + eNodeSocketDatatype(node_socket->type)); + if (ELEM(-1, a_type_priority, b_type_priority)) { + return a_type_priority < b_type_priority; + } + /* Avoid conversions if possible. */ + if (ELEM(4, a_type_priority, b_type_priority) && a_type_priority != b_type_priority) { + return a_type_priority < b_type_priority; + } + + if (to_connected) { + const float connection_factor = connected_node ? 5.0f : 0.2f; + return a.prioriti < b.prioriti * connection_factor; + } + + return a.prioriti < b.prioriti; + }); + + if (-1 == get_link_type_priority(viewer_trait.type, eNodeSocketDatatype(node_socket->type))) { + return OPERATOR_CANCELLED; + } + + auto [viewer_node, viewer_socket] = viewer_trait.finalize(*btree); + btree->ensure_topology_cache(); + return link_view_sockets(C, snode, *btree, *node_socket, *viewer_socket); } /** \} */ diff --git a/source/blender/editors/space_node/node_select.cc b/source/blender/editors/space_node/node_select.cc index 9dc531cda76..9ea7609edab 100644 --- a/source/blender/editors/space_node/node_select.cc +++ b/source/blender/editors/space_node/node_select.cc @@ -6,6 +6,8 @@ * \ingroup spnode */ +#include + #include #include #include @@ -676,14 +678,18 @@ static bool node_mouse_select(bContext *C, return false; } + std::cout << __func__ << std::endl; + bool active_texture_changed = false; bool viewer_node_changed = false; if ((node != nullptr) && (node_was_selected == false || params->select_passthrough == false)) { viewer_node_changed = (node->flag & NODE_DO_OUTPUT) == 0 && node->type == GEO_NODE_VIEWER; ED_node_set_active(&bmain, &snode, snode.edittree, node, &active_texture_changed); + std::cout << __LINE__ << std::endl; } else if (node != nullptr && node->type == GEO_NODE_VIEWER) { viewer_path::activate_geometry_node(bmain, snode, *node); + std::cout << __LINE__ << std::endl; } ED_node_set_active_viewer_key(&snode); tree_draw_order_update(node_tree); @@ -691,6 +697,7 @@ static bool node_mouse_select(bContext *C, viewer_node_changed) { DEG_id_tag_update(&snode.edittree->id, ID_RECALC_COPY_ON_WRITE); + std::cout << __LINE__ << std::endl; } WM_event_add_notifier(C, NC_NODE | NA_SELECTED, nullptr); diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index 83dbd8f9727..fc0008b447f 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -255,8 +255,10 @@ static void spreadsheet_update_context(const bContext *C) case SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE: { WorkSpace *workspace = CTX_wm_workspace(C); if (sspreadsheet->flag & SPREADSHEET_FLAG_PINNED) { + ed::viewer_path::ViewerPathMemory memory; const std::optional parsed_path = - blender::ed::viewer_path::parse_geometry_nodes_viewer(sspreadsheet->viewer_path); + blender::ed::viewer_path::parse_geometry_nodes_viewer(sspreadsheet->viewer_path, + memory); if (parsed_path.has_value()) { if (blender::ed::viewer_path::exists_geometry_nodes_viewer(*parsed_path)) { /* The pinned path is still valid, do nothing. */ @@ -271,8 +273,9 @@ static void spreadsheet_update_context(const bContext *C) } } /* Now try to update the viewer path from the workspace. */ + ed::viewer_path::ViewerPathMemory memory; const std::optional workspace_parsed_path = - blender::ed::viewer_path::parse_geometry_nodes_viewer(workspace->viewer_path); + blender::ed::viewer_path::parse_geometry_nodes_viewer(workspace->viewer_path, memory); if (workspace_parsed_path.has_value()) { if (BKE_viewer_path_equal(&sspreadsheet->viewer_path, &workspace->viewer_path)) { /* Nothing changed. */ diff --git a/source/blender/editors/util/ed_viewer_path.cc b/source/blender/editors/util/ed_viewer_path.cc index a0edcd5d984..8fb30c3b963 100644 --- a/source/blender/editors/util/ed_viewer_path.cc +++ b/source/blender/editors/util/ed_viewer_path.cc @@ -2,12 +2,15 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "ED_viewer_path.hh" +#include + #include "ED_screen.hh" +#include "ED_viewer_path.hh" #include "BKE_compute_contexts.hh" #include "BKE_context.h" #include "BKE_main.h" +#include "BKE_modifier.h" #include "BKE_node_runtime.hh" #include "BKE_node_tree_zones.hh" #include "BKE_workspace.h" @@ -132,14 +135,25 @@ static void viewer_path_for_geometry_node(const SpaceNode &snode, BLI_addtail(&r_dst.path, zone_elem); } - ViewerNodeViewerPathElem *viewer_node_elem = BKE_viewer_path_elem_new_viewer_node(); - viewer_node_elem->node_id = node.identifier; - viewer_node_elem->base.ui_name = BLI_strdup(node.name); - BLI_addtail(&r_dst.path, viewer_node_elem); + if (node.type == GEO_NODE_VIEWER) { + ViewerNodeViewerPathElem *viewer_node_elem = BKE_viewer_path_elem_new_viewer_node(); + viewer_node_elem->node_id = node.identifier; + viewer_node_elem->base.ui_name = BLI_strdup(node.name); + BLI_addtail(&r_dst.path, viewer_node_elem); + } + + if (bke::node_is_viewer_group(node)) { + ViewerNodeGroupViewerPathElem *viewer_node_group_elem = + BKE_viewer_path_elem_new_viewer_node_group(); + viewer_node_group_elem->node_id = node.identifier; + viewer_node_group_elem->base.ui_name = BLI_strdup(node.name); + BLI_addtail(&r_dst.path, viewer_node_group_elem); + } } void activate_geometry_node(Main &bmain, SpaceNode &snode, bNode &node) { + std::cout << __func__ << ": " << node.name << std::endl; wmWindowManager *wm = (wmWindowManager *)bmain.wm.first; if (wm == nullptr) { return; @@ -213,7 +227,7 @@ Object *parse_object_only(const ViewerPath &viewer_path) } std::optional parse_geometry_nodes_viewer( - const ViewerPath &viewer_path) + const ViewerPath &viewer_path, ViewerPathMemory &memory) { Vector elems_vec; LISTBASE_FOREACH (const ViewerPathElem *, item, &viewer_path.path) { @@ -249,8 +263,12 @@ std::optional parse_geometry_nodes_viewer( } remaining_elems = remaining_elems.drop_front(1); Vector node_path; - for (const ViewerPathElem *elem : remaining_elems.drop_back(1)) { + for (const ViewerPathElem *elem : remaining_elems) { + if (ELEM(elem->type, VIEWER_PATH_ELEM_TYPE_VIEWER_NODE)) { + continue; + } if (!ELEM(elem->type, + VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP, VIEWER_PATH_ELEM_TYPE_GROUP_NODE, VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE, VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE)) @@ -260,16 +278,106 @@ std::optional parse_geometry_nodes_viewer( node_path.append(elem); } const ViewerPathElem *last_elem = remaining_elems.last(); - if (last_elem->type != VIEWER_PATH_ELEM_TYPE_VIEWER_NODE) { - return std::nullopt; + if (last_elem->type == VIEWER_PATH_ELEM_TYPE_VIEWER_NODE) { + const int32_t viewer_node_id = + reinterpret_cast(last_elem)->node_id; + return ViewerPathForGeometryNodesViewer{root_ob, modifier_name, node_path, {viewer_node_id}}; } - const int32_t viewer_node_id = - reinterpret_cast(last_elem)->node_id; - return ViewerPathForGeometryNodesViewer{root_ob, modifier_name, node_path, viewer_node_id}; + if (last_elem->type == VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP) { + Vector viewer_groups; + + const ModifierData *md = BKE_modifiers_findby_name(reinterpret_cast(root_id), + modifier_name); + const NodesModifierData &nmd = *reinterpret_cast(md); + + const bNodeTree *tree = nmd.node_group; + for (const ViewerPathElem *elem : node_path) { + if (tree == nullptr) { + return std::nullopt; + } + switch (ViewerPathElemType(elem->type)) { + case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: { + const auto &group_elem = *reinterpret_cast(elem); + tree->ensure_topology_cache(); + const bNode *node = tree->node_by_id(group_elem.node_id); + if (node == nullptr) { + return std::nullopt; + } + BLI_assert(node->is_group()); + tree = reinterpret_cast(node->id); + break; + } + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: { + const auto &group_elem = *reinterpret_cast(elem); + tree->ensure_topology_cache(); + const bNode *node = tree->node_by_id(group_elem.node_id); + if (node == nullptr) { + return std::nullopt; + } + viewer_groups.append(group_elem.node_id); + BLI_assert(node->is_group()); + tree = reinterpret_cast(node->id); + break; + } + default: + break; + } + } + + while (true) { + tree->ensure_topology_cache(); + BLI_assert(tree->is_viewer()); + + const int32_t local_viewer = [&]() -> int32_t { + const Span viewers = tree->nodes_by_type("GeometryNodeViewer"); + if (viewers.size() == 1) { + return viewers.first()->identifier; + } + BLI_assert(viewers.is_empty()); + const Span groups = tree->group_nodes(); + const bNode &viewer_group = **std::find_if( + groups.begin(), groups.end(), [](const bNode *node) -> bool { + return bke::node_is_viewer_group(*node); + }); + return viewer_group.identifier; + }(); + + const bke::bNodeTreeZones *zones = tree->zones(); + + for (const bNodeTreeZone *zone : zones->get_zone_stack_for_node(local_viewer)) { + ViewerPathElem *zone_elem = viewer_path_elem_for_zone(*zone); + BLI_addtail(&memory.viewer_group_path.path, zone_elem); + node_path.append(zone_elem); + } + + const bNode *viewer = tree->node_by_id(local_viewer); + if (viewer == nullptr) { + return std::nullopt; + } + viewer_groups.append(viewer->identifier); + if (!bke::node_is_viewer_group(*viewer)) { + break; + } + GroupNodeViewerPathElem *group_elem = BKE_viewer_path_elem_new_group_node(); + group_elem->node_id = local_viewer; + ViewerPathElem *elem = reinterpret_cast(group_elem); + + tree = reinterpret_cast(viewer->id); + BLI_assert(tree != nullptr); + + BLI_addtail(&memory.viewer_group_path.path, elem); + node_path.append(elem); + } + + return ViewerPathForGeometryNodesViewer{ + root_ob, modifier_name, node_path, std::move(viewer_groups)}; + } + return std::nullopt; } bool exists_geometry_nodes_viewer(const ViewerPathForGeometryNodesViewer &parsed_viewer_path) { + // TODO const NodesModifierData *modifier = nullptr; LISTBASE_FOREACH (const ModifierData *, md, &parsed_viewer_path.object->modifiers) { if (md->type != eModifierType_Nodes) { @@ -343,7 +451,7 @@ bool exists_geometry_nodes_viewer(const ViewerPathForGeometryNodesViewer &parsed } } - const bNode *viewer_node = ngroup->node_by_id(parsed_viewer_path.viewer_node_id); + const bNode *viewer_node = ngroup->node_by_id(parsed_viewer_path.node_ids.last()); if (viewer_node == nullptr) { return false; } @@ -364,7 +472,10 @@ UpdateActiveGeometryNodesViewerResult update_active_geometry_nodes_viewer(const return UpdateActiveGeometryNodesViewerResult::NotActive; } const ViewerPathElem *last_elem = static_cast(viewer_path.path.last); - if (last_elem->type != VIEWER_PATH_ELEM_TYPE_VIEWER_NODE) { + if (!ELEM(last_elem->type, + VIEWER_PATH_ELEM_TYPE_VIEWER_NODE, + VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP)) + { return UpdateActiveGeometryNodesViewerResult::NotActive; } const int32_t viewer_node_id = @@ -427,21 +538,30 @@ UpdateActiveGeometryNodesViewerResult update_active_geometry_nodes_viewer(const return UpdateActiveGeometryNodesViewerResult::NotActive; } -bNode *find_geometry_nodes_viewer(const ViewerPath &viewer_path, SpaceNode &snode) +bNode *find_geometry_nodes_viewer_in_space(const ViewerPath &viewer_path, SpaceNode &snode) { /* Viewer path is only valid if the context object is set. */ if (snode.id == nullptr || GS(snode.id->name) != ID_OB) { return nullptr; } + ViewerPathMemory memory; const std::optional parsed_viewer_path = - parse_geometry_nodes_viewer(viewer_path); + parse_geometry_nodes_viewer(viewer_path, memory); if (!parsed_viewer_path.has_value()) { return nullptr; } snode.edittree->ensure_topology_cache(); - bNode *possible_viewer = snode.edittree->node_by_id(parsed_viewer_path->viewer_node_id); + bNode *possible_viewer = [&]() -> bNode * { + for (const int32_t identifier : parsed_viewer_path->node_ids) { + if (bNode *viewer = snode.edittree->node_by_id(identifier)) { + return viewer; + } + } + return nullptr; + }(); + if (possible_viewer == nullptr) { return nullptr; } @@ -484,8 +604,23 @@ bNode *find_geometry_nodes_viewer(const ViewerPath &viewer_path, SpaceNode &snod elem.iteration); return true; } + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: { + const auto &elem = reinterpret_cast(elem_generic); + compute_context_builder.push(elem.node_id); + return true; + } } return false; } +ViewerPathMemory::ViewerPathMemory() +{ + BKE_viewer_path_init(&viewer_group_path); +} + +ViewerPathMemory::~ViewerPathMemory() +{ + BKE_viewer_path_clear(&viewer_group_path); +} + } // namespace blender::ed::viewer_path diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 3df53a58dd0..ae2a1ca10cb 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -726,6 +726,8 @@ typedef struct bNodeTree { blender::Vector &r_node_ids) const; const bNode *find_nested_node(int32_t nested_node_id) const; + bool is_viewer() const; + /** * Update a run-time cache for the node tree based on it's current state. This makes many methods * available which allow efficient lookup for topology information (like neighboring sockets). @@ -827,6 +829,9 @@ enum { * NOTE: DEPRECATED, use (id->tag & LIB_TAG_LOCALIZED) instead. */ // NTREE_IS_LOCALIZED = 1 << 5, + + /** Viewer subtype of node tree. */ + NTREE_IS_VIEWER, }; /* tree->execution_mode */ diff --git a/source/blender/makesdna/DNA_viewer_path_types.h b/source/blender/makesdna/DNA_viewer_path_types.h index 0c942962bb8..3de46b6b0e2 100644 --- a/source/blender/makesdna/DNA_viewer_path_types.h +++ b/source/blender/makesdna/DNA_viewer_path_types.h @@ -16,6 +16,7 @@ typedef enum ViewerPathElemType { VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE = 3, VIEWER_PATH_ELEM_TYPE_VIEWER_NODE = 4, VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE = 5, + VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP = 6, } ViewerPathElemType; typedef struct ViewerPathElem { @@ -42,6 +43,13 @@ typedef struct GroupNodeViewerPathElem { char _pad1[4]; } GroupNodeViewerPathElem; +typedef struct ViewerNodeGroupViewerPathElem { + ViewerPathElem base; + + int32_t node_id; + char _pad1[4]; +} ViewerNodeGroupViewerPathElem; + typedef struct SimulationZoneViewerPathElem { ViewerPathElem base; diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 1b7a22dbcee..1b30d314301 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -10047,6 +10047,12 @@ static void rna_def_nodetree(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Grease Pencil Data", "Grease Pencil data-block"); RNA_def_property_update(prop, NC_NODE, nullptr); + prop = RNA_def_property(srna, "is_viewer", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", NTREE_IS_VIEWER); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Is Viewer Group", "Node Group can be used as Viewer node"); + RNA_def_property_update(prop, NC_NODE | ND_DISPLAY, nullptr); + prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_enum_items(prop, static_type_items); diff --git a/source/blender/makesrna/intern/rna_space.cc b/source/blender/makesrna/intern/rna_space.cc index db7b30204cc..97fa410bac7 100644 --- a/source/blender/makesrna/intern/rna_space.cc +++ b/source/blender/makesrna/intern/rna_space.cc @@ -3350,6 +3350,8 @@ static StructRNA *rna_viewer_path_elem_refine(PointerRNA *ptr) return &RNA_ModifierViewerPathElem; case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: return &RNA_GroupNodeViewerPathElem; + case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP: + return &RNA_ViewerNodeGroupViewerPathElem; case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: return &RNA_SimulationZoneViewerPathElem; case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: @@ -8093,6 +8095,11 @@ static const EnumPropertyItem viewer_path_elem_type_items[] = { {VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE, "SIMULATION_ZONE", ICON_NONE, "Simulation Zone", ""}, {VIEWER_PATH_ELEM_TYPE_VIEWER_NODE, "VIEWER_NODE", ICON_NONE, "Viewer Node", ""}, {VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE, "REPEAT_ZONE", ICON_NONE, "Repeat", ""}, + {VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP, + "VIEWER_NODE_GROUP", + ICON_NONE, + "Viewer Node Group", + ""}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -8182,6 +8189,17 @@ static void rna_def_viewer_node_viewer_path_elem(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Node ID", ""); } +static void rna_def_viewer_node_group_viewer_path_elem(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "ViewerNodeGroupViewerPathElem", "ViewerPathElem"); + + prop = RNA_def_property(srna, "node_id", PROP_INT, PROP_NONE); + RNA_def_property_ui_text(prop, "Node ID", ""); +} + static void rna_def_viewer_path(BlenderRNA *brna) { StructRNA *srna; @@ -8194,6 +8212,7 @@ static void rna_def_viewer_path(BlenderRNA *brna) rna_def_simulation_zone_viewer_path_elem(brna); rna_def_repeat_zone_viewer_path_elem(brna); rna_def_viewer_node_viewer_path_elem(brna); + rna_def_viewer_node_group_viewer_path_elem(brna); srna = RNA_def_struct(brna, "ViewerPath", nullptr); RNA_def_struct_ui_text(srna, "Viewer Path", "Path to data that is viewed"); diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index f523eb826c6..49ce7489529 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -578,8 +578,9 @@ static void find_side_effect_nodes_for_viewer_path( const ModifierEvalContext &ctx, nodes::GeoNodesSideEffectNodes &r_side_effect_nodes) { + ed::viewer_path::ViewerPathMemory memory; const std::optional parsed_path = - ed::viewer_path::parse_geometry_nodes_viewer(viewer_path); + ed::viewer_path::parse_geometry_nodes_viewer(viewer_path, memory); if (!parsed_path.has_value()) { return; } @@ -592,7 +593,6 @@ static void find_side_effect_nodes_for_viewer_path( ComputeContextBuilder compute_context_builder; compute_context_builder.push(parsed_path->modifier_name); - for (const ViewerPathElem *elem : parsed_path->node_path) { if (!ed::viewer_path::add_compute_context_for_viewer_path_elem(*elem, compute_context_builder)) { @@ -601,7 +601,7 @@ static void find_side_effect_nodes_for_viewer_path( } try_add_side_effect_node( - *compute_context_builder.current(), parsed_path->viewer_node_id, nmd, r_side_effect_nodes); + *compute_context_builder.current(), parsed_path->node_ids.last(), nmd, r_side_effect_nodes); } static void find_side_effect_nodes(const NodesModifierData &nmd, diff --git a/source/blender/nodes/intern/geometry_nodes_log.cc b/source/blender/nodes/intern/geometry_nodes_log.cc index 6b63d9958a0..a50909b2151 100644 --- a/source/blender/nodes/intern/geometry_nodes_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -551,10 +551,38 @@ Map GeoModifierLog::get_tree_log_by_zone_fo return log_by_zone; } +static const bNodeTree *top_tree_from_viewer_path(const Span node_path, + const bNodeTree &root_node_tree) +{ + const bNodeTree *iter_node_tree = &root_node_tree; + for (const ViewerPathElem *elem : node_path) { + if (elem->type == VIEWER_PATH_ELEM_TYPE_GROUP_NODE) { + const auto &typed_elem = *reinterpret_cast(elem); + const bNode *node_group = iter_node_tree->node_by_id(typed_elem.node_id); + if (node_group != nullptr && node_group->id != nullptr) { + iter_node_tree = reinterpret_cast(node_group->id); + continue; + } + return nullptr; + } + if (elem->type == VIEWER_PATH_ELEM_TYPE_VIEWER_NODE_GROUP) { + const auto &typed_elem = *reinterpret_cast(elem); + const bNode *node_group = iter_node_tree->node_by_id(typed_elem.node_id); + if (node_group != nullptr && node_group->id != nullptr) { + iter_node_tree = reinterpret_cast(node_group->id); + continue; + } + return nullptr; + } + } + return iter_node_tree; +} + const ViewerNodeLog *GeoModifierLog::find_viewer_node_log_for_path(const ViewerPath &viewer_path) { + ed::viewer_path::ViewerPathMemory memory; const std::optional parsed_path = - ed::viewer_path::parse_geometry_nodes_viewer(viewer_path); + ed::viewer_path::parse_geometry_nodes_viewer(viewer_path, memory); if (!parsed_path.has_value()) { return nullptr; } @@ -583,12 +611,82 @@ const ViewerNodeLog *GeoModifierLog::find_viewer_node_log_for_path(const ViewerP return nullptr; } } + + { + const bNodeTree *iter_node_tree = top_tree_from_viewer_path(parsed_path->node_path, + *nmd->node_group); + const bNode *viewer_node = iter_node_tree->node_by_id(parsed_path->node_ids.last()); + if (bke::node_is_viewer_group(*viewer_node)) { + compute_context_builder.push(parsed_path->node_ids.last()); + + const auto lookup_viewer = [&](const bNodeTree &tree) -> const bNode * { + BLI_assert(tree.is_viewer()); + tree.ensure_topology_cache(); + const Span viewers = tree.nodes_by_type("GeometryNodeViewer"); + const Span groups = tree.group_nodes(); + if (viewers.size() == 1) { + return viewers.first(); + } + for (const bNode *group_node : groups) { + if (bke::node_is_viewer_group(*group_node)) { + return group_node; + } + } + return nullptr; + }; + + iter_node_tree = iter_node_tree = reinterpret_cast(viewer_node->id); + while (const bNode *viewer = lookup_viewer(*iter_node_tree)) { + const bke::bNodeTreeZones *tree_zones = iter_node_tree->zones(); + if (tree_zones == nullptr) { + return nullptr; + } + for (const bke::bNodeTreeZone *zone : + tree_zones->get_zone_stack_for_node(viewer->identifier)) { + if (zone->output_node == nullptr) { + return nullptr; + } + const bNode &zone_output = *zone->output_node; + switch (zone_output.type) { + case GEO_NODE_SIMULATION_OUTPUT: { + compute_context_builder.push( + zone_output.identifier); + break; + } + case GEO_NODE_REPEAT_OUTPUT: { + compute_context_builder.push(zone_output.identifier, + 0); + break; + } + default: { + BLI_assert_unreachable(); + } + } + } + if (viewer->type == GEO_NODE_VIEWER) { + const ComputeContextHash context_hash = compute_context_builder.hash(); + nodes::geo_eval_log::GeoTreeLog &tree_log = modifier_log->get_tree_log(context_hash); + tree_log.ensure_viewer_node_logs(); + + const ViewerNodeLog *viewer_log = tree_log.viewer_node_logs.lookup_default( + viewer->identifier, nullptr); + return viewer_log; + } + compute_context_builder.push(viewer->identifier); + iter_node_tree = reinterpret_cast(viewer->id); + if (iter_node_tree == nullptr) { + return nullptr; + } + } + } + } + const ComputeContextHash context_hash = compute_context_builder.hash(); nodes::geo_eval_log::GeoTreeLog &tree_log = modifier_log->get_tree_log(context_hash); tree_log.ensure_viewer_node_logs(); const ViewerNodeLog *viewer_log = tree_log.viewer_node_logs.lookup_default( - parsed_path->viewer_node_id, nullptr); + parsed_path->node_ids.last(), nullptr); return viewer_log; }