diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index 28dd65375bc..157f53c5397 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -9,6 +9,7 @@ from bpy.types import ( from bpy.props import ( BoolProperty, CollectionProperty, + EnumProperty, FloatVectorProperty, StringProperty, ) @@ -243,11 +244,93 @@ class NODE_OT_tree_path_parent(Operator): return {'FINISHED'} +class NodePanelOperator(): + @classmethod + def poll(cls, context): + snode = context.space_data + if snode is None: + return False + tree = snode.edit_tree + if tree is None: + return False + if tree.is_embedded_data: + return False + return True + + +class NODE_OT_panel_add(NodePanelOperator, Operator): + '''Add a new panel to the tree''' + bl_idname = "node.panel_add" + bl_label = "Add Panel" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + snode = context.space_data + tree = snode.edit_tree + panels = tree.panels + + # Remember index to move the item. + dst_index = min(panels.active_index + 1, len(panels)) + panels.new("Panel") + panels.move(len(panels) - 1, dst_index) + panels.active_index = dst_index + + return {'FINISHED'} + + +class NODE_OT_panel_remove(NodePanelOperator, Operator): + '''Remove a panel from the tree''' + bl_idname = "node.panel_remove" + bl_label = "Remove Panel" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + snode = context.space_data + tree = snode.edit_tree + panels = tree.panels + + if panels.active: + panels.remove(panels.active) + panels.active_index = min(panels.active_index, len(panels) - 1) + + return {'FINISHED'} + + +class NODE_OT_panel_move(NodePanelOperator, Operator): + '''Move a panel to another position''' + bl_idname = "node.panel_move" + bl_label = "Move Panel" + bl_options = {'REGISTER', 'UNDO'} + + direction: EnumProperty( + name="Direction", + items=[('UP', "Up", ""), ('DOWN', "Down", "")], + default='UP', + ) + + def execute(self, context): + snode = context.space_data + tree = snode.edit_tree + panels = tree.panels + + if self.direction == 'UP' and panels.active_index > 0: + panels.move(panels.active_index, panels.active_index - 1) + panels.active_index -= 1 + elif self.direction == 'DOWN' and panels.active_index < len(panels) - 1: + panels.move(panels.active_index, panels.active_index + 1) + panels.active_index += 1 + + return {'FINISHED'} + + classes = ( NodeSetting, NODE_OT_add_node, NODE_OT_add_simulation_zone, NODE_OT_collapse_hide_unused_toggle, + NODE_OT_panel_add, + NODE_OT_panel_remove, + NODE_OT_panel_move, NODE_OT_tree_path_parent, ) diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index f00a2be4912..b5aa793f77e 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -960,6 +960,67 @@ class NODE_PT_node_tree_interface_outputs(NodeTreeInterfacePanel): self.draw_socket_list(context, "OUT", "outputs", "active_output") +class NODE_UL_panels(bpy.types.UIList): + def draw_item(self, context, layout, _data, item, icon, _active_data, _active_propname, _index): + row = layout.row(align=True) + row.prop(item, "name", text="", emboss=False, icon_value=icon) + + +class NODE_PT_panels(Panel): + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_category = "Group" + bl_label = "Node Panels" + + @classmethod + def poll(cls, context): + if not context.preferences.experimental.use_node_panels: + return False + + snode = context.space_data + if snode is None: + return False + tree = snode.edit_tree + if tree is None: + return False + if tree.is_embedded_data: + return False + return True + + def draw(self, context): + layout = self.layout + snode = context.space_data + tree = snode.edit_tree + + split = layout.row() + + split.template_list( + "NODE_UL_panels", + "", + tree, + "panels", + tree.panels, + "active_index") + + ops_col = split.column() + + add_remove_col = ops_col.column(align=True) + add_remove_col.operator("node.panel_add", icon='ADD', text="") + add_remove_col.operator("node.panel_remove", icon='REMOVE', text="") + + ops_col.separator() + + up_down_col = ops_col.column(align=True) + props = up_down_col.operator("node.panel_move", icon='TRIA_UP', text="") + props.direction = 'UP' + props = up_down_col.operator("node.panel_move", icon='TRIA_DOWN', text="") + props.direction = 'DOWN' + + active_panel = tree.panels.active + if active_panel is not None: + layout.prop(active_panel, "name") + + class NODE_UL_simulation_zone_items(bpy.types.UIList): def draw_item(self, context, layout, _data, item, icon, _active_data, _active_propname, _index): if self.layout_type in {'DEFAULT', 'COMPACT'}: @@ -1103,6 +1164,8 @@ classes = ( NODE_UL_interface_sockets, NODE_PT_node_tree_interface_inputs, NODE_PT_node_tree_interface_outputs, + NODE_UL_panels, + NODE_PT_panels, NODE_UL_simulation_zone_items, NODE_PT_simulation_zone_items, diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index 00e82f83685..8bf033ecc7b 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -2410,6 +2410,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ("blender/blender/projects/10", "Pipeline, Assets & IO Project Page")), ({"property": "use_override_templates"}, ("blender/blender/issues/73318", "Milestone 4")), ({"property": "use_new_volume_nodes"}, ("blender/blender/issues/103248", "#103248")), + ({"property": "use_node_panels"}, ("blender/blender/issues/105248", "#105248")), ), ) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 98adde6711b..3a114843343 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -542,6 +542,9 @@ void ntreeBlendWrite(struct BlendWriter *writer, struct bNodeTree *ntree); /** \name Node Tree Interface * \{ */ +/** Run this after relevant changes to panels to ensure sockets remain sorted by panel. */ +void ntreeEnsureSocketInterfacePanelOrder(bNodeTree *ntree); + void ntreeRemoveSocketInterface(bNodeTree *ntree, bNodeSocket *sock); struct bNodeSocket *ntreeAddSocketInterface(struct bNodeTree *ntree, @@ -549,6 +552,54 @@ struct bNodeSocket *ntreeAddSocketInterface(struct bNodeTree *ntree, const char *idname, const char *name); +/** Set the panel of the interface socket. */ +void ntreeSetSocketInterfacePanel(bNodeTree *ntree, bNodeSocket *sock, bNodePanel *panel); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Node Tree Socket Panels + * \{ */ + +/** + * Check if a panel is part of the node tree. + * \return True if the panel is part of the node tree. + */ +bool ntreeContainsPanel(const bNodeTree *ntree, const bNodePanel *panel); + +/** + * Index of a panel in the node tree. + * \return Index of the panel in the node tree or -1 if the tree does not contain the panel. + */ +int ntreeGetPanelIndex(const bNodeTree *ntree, const bNodePanel *panel); + +/** + * Add a new panel to the node tree. + * \param name: Name of the new panel. + * \param flag: Flags of the new panel. + */ +bNodePanel *ntreeAddPanel(bNodeTree *ntree, const char *name, int flag); + +/** + * Insert a new panel in the node tree. + * \param name: Name of the new panel. + * \param flag: Flags of the new panel. + * \param index: Index at which to insert the panel. + */ +bNodePanel *ntreeInsertPanel(bNodeTree *ntree, const char *name, int flag, int index); + +/** Remove a panel from the node tree. */ +void ntreeRemovePanel(bNodeTree *ntree, bNodePanel *panel); + +/** Remove all panels from the node tree. */ +void ntreeClearPanels(bNodeTree *ntree); + +/** + * Move a panel up or down in the node tree. + * \param index: Index to which to move the panel. + */ +void ntreeMovePanel(bNodeTree *ntree, bNodePanel *panel, int new_index); + /** \} */ /* -------------------------------------------------------------------- */ @@ -867,16 +918,16 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i /* NOTE: types are needed to restore callbacks, don't change values. */ -//#define SH_NODE_MATERIAL 100 +// #define SH_NODE_MATERIAL 100 #define SH_NODE_RGB 101 #define SH_NODE_VALUE 102 #define SH_NODE_MIX_RGB_LEGACY 103 #define SH_NODE_VALTORGB 104 #define SH_NODE_RGBTOBW 105 #define SH_NODE_SHADERTORGB 106 -//#define SH_NODE_TEXTURE 106 +// #define SH_NODE_TEXTURE 106 #define SH_NODE_NORMAL 107 -//#define SH_NODE_GEOMETRY 108 +// #define SH_NODE_GEOMETRY 108 #define SH_NODE_MAPPING 109 #define SH_NODE_CURVE_VEC 110 #define SH_NODE_CURVE_RGB 111 @@ -884,7 +935,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define SH_NODE_MATH 115 #define SH_NODE_VECTOR_MATH 116 #define SH_NODE_SQUEEZE 117 -//#define SH_NODE_MATERIAL_EXT 118 +// #define SH_NODE_MATERIAL_EXT 118 #define SH_NODE_INVERT 119 #define SH_NODE_SEPRGB_LEGACY 120 #define SH_NODE_COMBRGB_LEGACY 121 diff --git a/source/blender/blenkernel/BKE_node_runtime.hh b/source/blender/blenkernel/BKE_node_runtime.hh index 0208c7beecb..049061a14aa 100644 --- a/source/blender/blenkernel/BKE_node_runtime.hh +++ b/source/blender/blenkernel/BKE_node_runtime.hh @@ -515,7 +515,15 @@ inline blender::Span bNodeTree::root_frames() const return this->runtime->root_frames; } -/** \} */ +inline blender::Span bNodeTree::panels() const +{ + return blender::Span(panels_array, panels_num); +} + +inline blender::MutableSpan bNodeTree::panels_for_write() +{ + return blender::MutableSpan(panels_array, panels_num); +} /* -------------------------------------------------------------------- */ /** \name #bNode Inline Methods diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index d79a141aff2..dd0135f8953 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -203,6 +203,14 @@ static void ntree_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, cons BLI_addtail(&ntree_dst->outputs, dst_socket); } + /* copy panels */ + ntree_dst->panels_array = static_cast(MEM_dupallocN(ntree_src->panels_array)); + ntree_dst->panels_num = ntree_src->panels_num; + for (bNodePanel *&panel_ptr : ntree_dst->panels_for_write()) { + panel_ptr = static_cast(MEM_dupallocN(panel_ptr)); + panel_ptr->name = BLI_strdup(panel_ptr->name); + } + /* copy preview hash */ if (ntree_src->previews && (flag & LIB_ID_COPY_NO_PREVIEW) == 0) { bNodeInstanceHashIterator iter; @@ -292,6 +300,13 @@ static void ntree_free_data(ID *id) MEM_freeN(sock); } + /* free panels */ + for (bNodePanel *panel : ntree->panels_for_write()) { + MEM_SAFE_FREE(panel->name); + MEM_SAFE_FREE(panel); + } + MEM_SAFE_FREE(ntree->panels_array); + /* free preview hash */ if (ntree->previews) { BKE_node_instance_hash_free(ntree->previews, (bNodeInstanceValueFP)node_preview_free); @@ -659,6 +674,12 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) write_node_socket_interface(writer, sock); } + BLO_write_pointer_array(writer, ntree->panels_num, ntree->panels_array); + for (const bNodePanel *panel : ntree->panels()) { + BLO_write_struct(writer, bNodePanel, panel); + BLO_write_string(writer, panel->name); + } + BKE_previewimg_blend_write(writer, ntree->preview); } @@ -687,6 +708,7 @@ static void direct_link_node_socket(BlendDataReader *reader, bNodeSocket *sock) BLO_read_data_address(reader, &sock->storage); BLO_read_data_address(reader, &sock->default_value); BLO_read_data_address(reader, &sock->default_attribute_name); + BLO_read_data_address(reader, &sock->panel); sock->runtime = MEM_new(__func__); } @@ -879,6 +901,12 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree) BLO_read_data_address(reader, &link->tosock); } + BLO_read_pointer_array(reader, reinterpret_cast(&ntree->panels_array)); + for (const int i : IndexRange(ntree->panels_num)) { + BLO_read_data_address(reader, &ntree->panels_array[i]); + BLO_read_data_address(reader, &ntree->panels_array[i]->name); + } + /* TODO: should be dealt by new generic cache handling of IDs... */ ntree->previews = nullptr; @@ -3659,6 +3687,19 @@ static bNodeSocket *make_socket_interface(bNodeTree *ntree, return sock; } +using PanelIndexMap = blender::VectorSet; + +static int node_socket_panel_cmp(void *panel_index_map_v, const void *a, const void *b) +{ + const PanelIndexMap &panel_index_map = *static_cast(panel_index_map_v); + const bNodeSocket *sock_a = static_cast(a); + const bNodeSocket *sock_b = static_cast(b); + return panel_index_map.index_of_try(sock_a->panel) > + panel_index_map.index_of_try(sock_b->panel) ? + 1 : + 0; +} + bNodeSocket *ntreeFindSocketInterface(bNodeTree *ntree, const eNodeSocketInOut in_out, const char *identifier) @@ -3674,6 +3715,18 @@ bNodeSocket *ntreeFindSocketInterface(bNodeTree *ntree, } // namespace blender::bke +void ntreeEnsureSocketInterfacePanelOrder(bNodeTree *ntree) +{ + if (!U.experimental.use_node_panels) { + return; + } + + /* Store panel index for sorting. */ + blender::bke::PanelIndexMap panel_index_map(ntree->panels()); + BLI_listbase_sort_r(&ntree->inputs, blender::bke::node_socket_panel_cmp, &panel_index_map); + BLI_listbase_sort_r(&ntree->outputs, blender::bke::node_socket_panel_cmp, &panel_index_map); +} + bNodeSocket *ntreeAddSocketInterface(bNodeTree *ntree, const eNodeSocketInOut in_out, const char *idname, @@ -3686,10 +3739,23 @@ bNodeSocket *ntreeAddSocketInterface(bNodeTree *ntree, else if (in_out == SOCK_OUT) { BLI_addtail(&ntree->outputs, iosock); } + + ntreeEnsureSocketInterfacePanelOrder(ntree); + BKE_ntree_update_tag_interface(ntree); return iosock; } +void ntreeSetSocketInterfacePanel(bNodeTree *ntree, bNodeSocket *socket, bNodePanel *panel) +{ + BLI_assert(panel == nullptr || ntreeContainsPanel(ntree, panel)); + + socket->panel = panel; + + ntreeEnsureSocketInterfacePanelOrder(ntree); + BKE_ntree_update_tag_interface(ntree); +} + namespace blender::bke { bNodeSocket *ntreeInsertSocketInterface(bNodeTree *ntree, @@ -3705,6 +3771,9 @@ bNodeSocket *ntreeInsertSocketInterface(bNodeTree *ntree, else if (in_out == SOCK_OUT) { BLI_insertlinkbefore(&ntree->outputs, next_sock, iosock); } + + ntreeEnsureSocketInterfacePanelOrder(ntree); + BKE_ntree_update_tag_interface(ntree); return iosock; } @@ -3760,9 +3829,166 @@ void ntreeRemoveSocketInterface(bNodeTree *ntree, bNodeSocket *sock) blender::bke::node_socket_interface_free(ntree, sock, true); MEM_freeN(sock); + /* No need to resort by panel, removing doesn't change anything. */ + BKE_ntree_update_tag_interface(ntree); } +bool ntreeContainsPanel(const bNodeTree *ntree, const bNodePanel *panel) +{ + return ntree->panels().contains(const_cast(panel)); +} + +int ntreeGetPanelIndex(const bNodeTree *ntree, const bNodePanel *panel) +{ + return ntree->panels().first_index_try(const_cast(panel)); +} + +bNodePanel *ntreeAddPanel(bNodeTree *ntree, const char *name, int flag) +{ + bNodePanel **old_panels_array = ntree->panels_array; + const Span old_panels = ntree->panels(); + ntree->panels_array = MEM_cnew_array(ntree->panels_num + 1, __func__); + ++ntree->panels_num; + const MutableSpan new_panels = ntree->panels_for_write(); + + std::copy(const_cast(old_panels.begin()), + const_cast(old_panels.end()), + new_panels.data()); + + bNodePanel *new_panel = MEM_cnew(__func__); + *new_panel = {BLI_strdup(name), flag, ntree->next_panel_identifier++}; + new_panels[new_panels.size() - 1] = new_panel; + + MEM_SAFE_FREE(old_panels_array); + + /* No need to sort sockets, nothing is using the new panel yet */ + + return new_panel; +} + +bNodePanel *ntreeInsertPanel(bNodeTree *ntree, const char *name, int flag, int index) +{ + if (!blender::IndexRange(ntree->panels().size() + 1).contains(index)) { + return nullptr; + } + + bNodePanel **old_panels_array = ntree->panels_array; + const Span old_panels = ntree->panels(); + ntree->panels_array = MEM_cnew_array(ntree->panels_num + 1, __func__); + ++ntree->panels_num; + const MutableSpan new_panels = ntree->panels_for_write(); + + Span old_panels_front = old_panels.take_front(index); + Span old_panels_back = old_panels.drop_front(index); + std::copy(const_cast(old_panels_front.begin()), + const_cast(old_panels_front.end()), + new_panels.data()); + std::copy(const_cast(old_panels_back.begin()), + const_cast(old_panels_back.end()), + new_panels.drop_front(index + 1).data()); + + bNodePanel *new_panel = MEM_cnew(__func__); + *new_panel = {BLI_strdup(name), flag, ntree->next_panel_identifier++}; + new_panels[index] = new_panel; + + MEM_SAFE_FREE(old_panels_array); + + /* No need to sort sockets, nothing is using the new panel yet */ + + return new_panel; +} + +void ntreeRemovePanel(bNodeTree *ntree, bNodePanel *panel) +{ + const int index = ntreeGetPanelIndex(ntree, panel); + if (index < 0) { + return; + } + + /* Remove references */ + LISTBASE_FOREACH (bNodeSocket *, iosock, &ntree->inputs) { + if (iosock->panel == panel) { + iosock->panel = nullptr; + } + } + LISTBASE_FOREACH (bNodeSocket *, iosock, &ntree->outputs) { + if (iosock->panel == panel) { + iosock->panel = nullptr; + } + } + + bNodePanel **old_panels_array = ntree->panels_array; + const Span old_panels = ntree->panels(); + ntree->panels_array = MEM_cnew_array(ntree->panels_num - 1, __func__); + --ntree->panels_num; + const MutableSpan new_panels = ntree->panels_for_write(); + + Span old_panels_front = old_panels.take_front(index); + Span old_panels_back = old_panels.drop_front(index + 1); + std::copy(const_cast(old_panels_front.begin()), + const_cast(old_panels_front.end()), + new_panels.data()); + std::copy(const_cast(old_panels_back.begin()), + const_cast(old_panels_back.end()), + new_panels.drop_front(index).data()); + + MEM_SAFE_FREE(panel->name); + MEM_SAFE_FREE(panel); + MEM_SAFE_FREE(old_panels_array); + + ntreeEnsureSocketInterfacePanelOrder(ntree); +} + +void ntreeClearPanels(bNodeTree *ntree) +{ + /* Remove references */ + LISTBASE_FOREACH (bNodeSocket *, iosock, &ntree->inputs) { + iosock->panel = nullptr; + } + LISTBASE_FOREACH (bNodeSocket *, iosock, &ntree->outputs) { + iosock->panel = nullptr; + } + + for (bNodePanel *panel : ntree->panels_for_write()) { + MEM_SAFE_FREE(panel->name); + MEM_SAFE_FREE(panel); + } + MEM_SAFE_FREE(ntree->panels_array); + ntree->panels_array = nullptr; + ntree->panels_num = 0; + + /* No need to sort sockets, only null panel exists, relative order remains unchanged. */ +} + +void ntreeMovePanel(bNodeTree *ntree, bNodePanel *panel, int new_index) +{ + const int old_index = ntreeGetPanelIndex(ntree, panel); + if (old_index < 0) { + return; + } + + const MutableSpan panels = ntree->panels_for_write(); + if (old_index == new_index) { + return; + } + else if (old_index < new_index) { + const Span moved_panels = panels.slice(old_index + 1, new_index - old_index); + bNodePanel *tmp = panels[old_index]; + std::copy(moved_panels.begin(), moved_panels.end(), panels.drop_front(old_index).data()); + panels[new_index] = tmp; + } + else /* old_index > new_index */ { + const Span moved_panels = panels.slice(new_index, old_index - new_index); + bNodePanel *tmp = panels[old_index]; + std::copy_backward( + moved_panels.begin(), moved_panels.end(), panels.drop_front(old_index + 1).data()); + panels[new_index] = tmp; + } + + ntreeEnsureSocketInterfacePanelOrder(ntree); +} + namespace blender::bke { static bool ntree_contains_tree_exec(const bNodeTree *tree_to_search_in, diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 16184d8c671..d4ddd362f2c 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -13,6 +13,8 @@ #include "DNA_modifier_types.h" #include "DNA_movieclip_types.h" +#include "DNA_genfile.h" + #include "BLI_assert.h" #include "BLI_listbase.h" #include "BLI_set.hh" @@ -198,7 +200,7 @@ static void versioning_remove_microfacet_sharp_distribution(bNodeTree *ntree) } } -void blo_do_versions_400(FileData * /*fd*/, Library * /*lib*/, Main *bmain) +void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) { if (!MAIN_VERSION_ATLEAST(bmain, 400, 1)) { LISTBASE_FOREACH (Mesh *, mesh, &bmain->meshes) { diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index c0794325b9d..6baf16caaa4 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -1433,6 +1433,9 @@ static void std_node_socket_interface_draw(bContext * /*C*/, uiLayout *layout, P bNodeSocket *sock = (bNodeSocket *)ptr->data; int type = sock->typeinfo->type; + PointerRNA tree_ptr; + RNA_id_pointer_create(ptr->owner_id, &tree_ptr); + uiLayout *col = uiLayoutColumn(layout, false); switch (type) { @@ -1477,6 +1480,10 @@ static void std_node_socket_interface_draw(bContext * /*C*/, uiLayout *layout, P if (sock->in_out == SOCK_IN && node_tree->type == NTREE_GEOMETRY) { uiItemR(col, ptr, "hide_in_modifier", DEFAULT_FLAGS, nullptr, 0); } + + if (U.experimental.use_node_panels) { + uiItemPointerR(col, ptr, "panel", &tree_ptr, "panels", nullptr, 0); + } } static void node_socket_virtual_draw_color(bContext * /*C*/, diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index 2b030231ce0..eeb680d78bd 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -2198,6 +2198,8 @@ static int ntree_socket_add_exec(bContext *C, wmOperator *op) ntree, in_out, active_sock->idname, active_sock->next, active_sock->name); /* XXX this only works for actual sockets, not interface templates! */ // nodeSocketCopyValue(sock, &ntree_ptr, active_sock, &ntree_ptr); + /* Inherit socket panel from the active socket interface. */ + sock->panel = active_sock->panel; } else { /* XXX TODO: define default socket type for a tree! */ @@ -2589,6 +2591,8 @@ static int ntree_socket_move_exec(bContext *C, wmOperator *op) } } + ntreeEnsureSocketInterfacePanelOrder(ntree); + BKE_ntree_update_tag_interface(ntree); ED_node_tree_propagate_change(C, CTX_data_main(C), ntree); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index f0bd113fc77..91670cb8c71 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -168,6 +168,9 @@ typedef struct bNodeSocket { /** Custom data for inputs, only UI writes in this. */ bNodeStack ns DNA_DEPRECATED; + /* UI panel of the socket. */ + struct bNodePanel *panel; + bNodeSocketRuntimeHandle *runtime; #ifdef __cplusplus @@ -530,6 +533,13 @@ typedef struct bNodeLink { #define NTREE_CHUNKSIZE_512 512 #define NTREE_CHUNKSIZE_1024 1024 +/** Panel in node tree for grouping sockets. */ +typedef struct bNodePanel { + char *name; + int flag; + int _pad; +} bNodePanel; + /* the basis for a Node tree, all links and nodes reside internal here */ /* only re-usable node trees are in the library though, * materials and textures allocate own tree struct */ @@ -593,6 +603,13 @@ typedef struct bNodeTree { /** Image representing what the node group does. */ struct PreviewImage *preview; + /* UI panels */ + struct bNodePanel **panels_array; + int panels_num; + int active_panel; + int next_panel_identifier; + char _pad2[4]; + bNodeTreeRuntimeHandle *runtime; #ifdef __cplusplus @@ -656,6 +673,9 @@ typedef struct bNodeTree { /** Inputs and outputs of the entire node group. */ blender::Span interface_inputs() const; blender::Span interface_outputs() const; + + blender::Span panels() const; + blender::MutableSpan panels_for_write(); #endif } bNodeTree; diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 68b88e2a47e..48bbb490b17 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -680,7 +680,8 @@ typedef struct UserDef_Experimental { char enable_overlay_next; char enable_workbench_next; char use_new_volume_nodes; - char _pad[4]; + char use_node_panels; + char _pad[3]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 0c18792f6de..36809edf8b5 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -3152,6 +3152,30 @@ static IDProperty **rna_NodeSocketInterface_idprops(PointerRNA *ptr) return &sock->prop; } +static void rna_NodeSocketInterface_panel_set(PointerRNA *ptr, + PointerRNA value, + struct ReportList *reports) +{ + bNodeSocket *socket = (bNodeSocket *)ptr->data; + bNodeTree *ntree = (bNodeTree *)ptr->owner_id; + bNodePanel *panel = (bNodePanel *)value.data; + + if (panel && !ntreeContainsPanel(ntree, panel)) { + BKE_report(reports, RPT_ERROR, "Panel is not in the node tree interface"); + return; + } + + ntreeSetSocketInterfacePanel(ntree, socket, panel); +} + +static bool rna_NodeSocketInterface_panel_poll(PointerRNA *ptr, PointerRNA value) +{ + bNodeTree *ntree = (bNodeTree *)ptr->owner_id; + bNodePanel *panel = (bNodePanel *)value.data; + + return panel == NULL || ntreeContainsPanel(ntree, panel); +} + static void rna_NodeSocketInterface_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr) { bNodeTree *ntree = reinterpret_cast(ptr->owner_id); @@ -3271,6 +3295,89 @@ static void rna_NodeSocketStandard_value_and_relation_update(struct bContext *C, DEG_relations_tag_update(bmain); } +/* ******** Node Socket Panels ******** */ + +static void rna_NodePanel_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr) +{ + bNodeTree *ntree = (bNodeTree *)ptr->owner_id; + BKE_ntree_update_tag_interface(ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); +} + +static bNodePanel *rna_NodeTree_panels_new(bNodeTree *ntree, + Main *bmain, + ReportList *reports, + const char *name) +{ + bNodePanel *panel = ntreeAddPanel(ntree, name, 0); + + if (panel == NULL) { + BKE_report(reports, RPT_ERROR, "Unable to create panel"); + } + else { + BKE_ntree_update_tag_interface(ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); + } + + return panel; +} + +static void rna_NodeTree_panels_remove(bNodeTree *ntree, Main *bmain, bNodePanel *panel) +{ + ntreeRemovePanel(ntree, panel); + + BKE_ntree_update_tag_interface(ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + +static void rna_NodeTree_panels_clear(bNodeTree *ntree, Main *bmain) +{ + ntreeClearPanels(ntree); + + BKE_ntree_update_tag_interface(ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + +static void rna_NodeTree_panels_move(bNodeTree *ntree, Main *bmain, int from_index, int to_index) +{ + if (from_index < 0 || from_index >= ntree->panels_num || to_index < 0 || + to_index >= ntree->panels_num) + { + return; + } + + ntreeMovePanel(ntree, ntree->panels_array[from_index], to_index); + + BKE_ntree_update_tag_interface(ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + +static PointerRNA rna_NodeTree_active_panel_get(PointerRNA *ptr) +{ + bNodeTree *ntree = (bNodeTree *)ptr->data; + bNodePanel *panel = NULL; + if (ntree->active_panel >= 0 && ntree->active_panel < ntree->panels_num) { + panel = ntree->panels_array[ntree->active_panel]; + } + + PointerRNA r_ptr; + RNA_pointer_create(ptr->owner_id, &RNA_NodePanel, panel, &r_ptr); + return r_ptr; +} + +static void rna_NodeTree_active_panel_set(PointerRNA *ptr, + PointerRNA value, + struct ReportList * /*reports*/) +{ + bNodePanel *panel = (bNodePanel *)value.data; + bNodeTree *ntree = (bNodeTree *)ptr->data; + ntree->active_panel = ntreeGetPanelIndex(ntree, panel); +} + /* ******** Node Types ******** */ static void rna_NodeInternalSocketTemplate_name_get(PointerRNA *ptr, char *value) @@ -11655,6 +11762,14 @@ static void rna_def_node_socket_interface(BlenderRNA *brna) "Don't show the input value in the geometry nodes modifier interface"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); + prop = RNA_def_property(srna, "panel", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_funcs( + prop, NULL, "rna_NodeSocketInterface_panel_set", NULL, "rna_NodeSocketInterface_panel_poll"); + RNA_def_property_struct_type(prop, "NodePanel"); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Panel", "Panel to group sockets together in the UI"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); + prop = RNA_def_property(srna, "attribute_domain", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); RNA_def_property_ui_text( @@ -12924,6 +13039,23 @@ static void rna_def_node_link(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Is Hidden", "Link is hidden due to invisible sockets"); } +static void rna_def_node_socket_panel(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "NodePanel", NULL); + RNA_def_struct_ui_text(srna, "NodePanel", "Panel in the node group interface"); + RNA_def_struct_sdna(srna, "bNodePanel"); + RNA_def_struct_ui_icon(srna, ICON_NODE); + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "name"); + RNA_def_property_ui_text(prop, "Name", "Name of the socket panel"); + RNA_def_struct_name_property(srna, prop); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodePanel_update"); +} + static void rna_def_nodetree_nodes_api(BlenderRNA *brna, PropertyRNA *cprop) { StructRNA *srna; @@ -13063,6 +13195,63 @@ static void rna_def_node_tree_sockets_api(BlenderRNA *brna, PropertyRNA *cprop, RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); } +static void rna_def_node_tree_socket_panels_api(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + PropertyRNA *prop; + PropertyRNA *parm; + FunctionRNA *func; + + RNA_def_property_srna(cprop, "NodePanels"); + srna = RNA_def_struct(brna, "NodePanels", NULL); + RNA_def_struct_sdna(srna, "bNodeTree"); + RNA_def_struct_ui_text( + srna, "Node Tree Socket Panels", "Collection of socket panels in a node tree"); + + prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, NULL, "active_panel"); + RNA_def_property_ui_text(prop, "Active Index", "Index of the active panel"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_NODE, NULL); + + prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "NodePanel"); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_pointer_funcs( + prop, "rna_NodeTree_active_panel_get", "rna_NodeTree_active_panel_set", NULL, NULL); + RNA_def_property_ui_text(prop, "Active", "Active panel"); + RNA_def_property_update(prop, NC_NODE, NULL); + + func = RNA_def_function(srna, "new", "rna_NodeTree_panels_new"); + RNA_def_function_ui_description(func, "Add a new panel to the tree"); + RNA_def_function_flag(func, FUNC_USE_MAIN | FUNC_USE_REPORTS); + parm = RNA_def_string(func, "name", NULL, MAX_NAME, "Name", ""); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + /* return value */ + parm = RNA_def_pointer(func, "panel", "NodePanel", "", "New panel"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_NodeTree_panels_remove"); + RNA_def_function_ui_description(func, "Remove a panel from the tree"); + RNA_def_function_flag(func, FUNC_USE_MAIN); + parm = RNA_def_pointer(func, "panel", "NodePanel", "", "The panel to remove"); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + + func = RNA_def_function(srna, "clear", "rna_NodeTree_panels_clear"); + RNA_def_function_ui_description(func, "Remove all panels from the tree"); + RNA_def_function_flag(func, FUNC_USE_MAIN); + + func = RNA_def_function(srna, "move", "rna_NodeTree_panels_move"); + RNA_def_function_ui_description(func, "Move a panel to another position"); + RNA_def_function_flag(func, FUNC_USE_MAIN); + parm = RNA_def_int( + func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the panel to move", 0, 10000); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + parm = RNA_def_int( + func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the panel", 0, 10000); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); +} + static void rna_def_nodetree(BlenderRNA *brna) { StructRNA *srna; @@ -13165,6 +13354,13 @@ static void rna_def_nodetree(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_update(prop, NC_NODE, NULL); + prop = RNA_def_property(srna, "panels", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "panels_array", "panels_num"); + RNA_def_property_struct_type(prop, "NodePanel"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Panels", "UI panels for structuring the node tree interface"); + rna_def_node_tree_socket_panels_api(brna, prop); + /* exposed as a function for runtime interface type properties */ func = RNA_def_function(srna, "interface_update", "rna_NodeTree_interface_update"); RNA_def_function_ui_description(func, "Updated node group interface"); @@ -13435,6 +13631,7 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_simulation_state_item(brna); rna_def_function_node(brna); + rna_def_node_socket_panel(brna); rna_def_nodetree(brna); rna_def_node_socket_standard_types(brna); diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index ea0e960b047..e702a87fd73 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -6742,6 +6742,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) prop = RNA_def_property(srna, "use_new_volume_nodes", PROP_BOOLEAN, PROP_NONE); RNA_def_property_ui_text( prop, "New Volume Nodes", "Enables visibility of the new Volume nodes in the UI"); + + prop = RNA_def_property(srna, "use_node_panels", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_ui_text( + prop, "Node Panels", "Enable node panels UI for grouping sockets in node groups"); } static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop)