Nodes: Panel declarations for grouping sockets #108649

Merged
Lukas Tönne merged 27 commits from LukasTonne/blender:node-socket-categories into main 2023-06-14 18:02:46 +02:00
13 changed files with 674 additions and 7 deletions

View File

@ -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.
LukasTonne marked this conversation as resolved Outdated

Period at the end of the comment.

Period at the end of the comment.
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,
)

View File

@ -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()

I know I started this with the socket list, but I'm regretting it now. Maybe we can skip exposing the name property separate from the list. It should be clear that you can rename in the list.

I know I started this with the socket list, but I'm regretting it now. Maybe we can skip exposing the name property separate from the list. It should be clear that you can rename in the list.

Not sure, we may want to add more settings to categories, such as options to close categories by default, doc strings, etc.

Not sure, we may want to add more settings to categories, such as options to close categories by default, doc strings, etc.

Ideally i'd like to display the node tree declarations in a way that resembles the final node output, to make it more intuitive. The vertical UI layout of the node editor sidebar makes that a bit challenging. But lets say we could arrange it any way we like, then you'd have inputs on the left and outputs on the right. Categories would show in a kind of tree view matching the panels in modifiers. Socket details and category details would be displayed close to active element.

Ideally i'd like to display the node tree declarations in a way that resembles the final node output, to make it more intuitive. The vertical UI layout of the node editor sidebar makes that a bit challenging. But lets say we could arrange it any way we like, then you'd have inputs on the left and outputs on the right. Categories would show in a kind of tree view matching the panels in modifiers. Socket details and category details would be displayed close to active element. ![](/attachments/b3fb4846-83b0-4d4b-b172-097ff3be0091)

Hmm, all I mean is that there's no need to have the separate name property outside of the list. Not that displaying more options doesn't make sense.

For he other stuff, the inputs and outputs used to be side-by-side, but it doesn't work well with Blender's single-column UI layout paradigm. But that can be discussed more later.

Hmm, all I mean is that there's no need to have the separate name property outside of the list. Not that displaying more options doesn't make sense. For he other stuff, the inputs and outputs used to be side-by-side, but it doesn't work well with Blender's single-column UI layout paradigm. But that can be discussed more later.
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,

View File

@ -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")),
),
)

View File

@ -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);
LukasTonne marked this conversation as resolved Outdated

This comment fits on one line

This comment fits on one line
Review

This is currently in the public API because the NODE_OT_tree_socket_move operator works directly on DNA and needs to call this after it moves sockets. That operator should also use an API method so the update function does not need to be exposed. I suggest a separate fix.

This is currently in the public API because the `NODE_OT_tree_socket_move` operator works directly on DNA and needs to call this after it moves sockets. That operator should also use an API method so the update function does not need to be exposed. I suggest a separate fix.
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.
LukasTonne marked this conversation as resolved Outdated

There's meant to be a colon after the name here: \param name:

There's meant to be a colon after the name here: `\param name:`
* \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

View File

@ -515,7 +515,15 @@ inline blender::Span<bNode *> bNodeTree::root_frames() const
return this->runtime->root_frames;
}
/** \} */
inline blender::Span<const bNodePanel *> bNodeTree::panels() const
{
return blender::Span(panels_array, panels_num);
}
inline blender::MutableSpan<bNodePanel *> bNodeTree::panels_for_write()
{
return blender::MutableSpan(panels_array, panels_num);
}
/* -------------------------------------------------------------------- */
/** \name #bNode Inline Methods

View File

@ -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<bNodePanel **>(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<bNodePanel *>(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<bNodeSocketRuntime>(__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<void **>(&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<const bNodePanel *>;
static int node_socket_panel_cmp(void *panel_index_map_v, const void *a, const void *b)
{
const PanelIndexMap &panel_index_map = *static_cast<const PanelIndexMap *>(panel_index_map_v);
const bNodeSocket *sock_a = static_cast<const bNodeSocket *>(a);
const bNodeSocket *sock_b = static_cast<const bNodeSocket *>(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. */
LukasTonne marked this conversation as resolved Outdated

IMO __func__ for the allocation string is nicer than specifying one manually. It just takes up less visual space. I've also seen other developers preferring that too.

IMO `__func__` for the allocation string is nicer than specifying one manually. It just takes up less visual space. I've also seen other developers preferring that too.
BKE_ntree_update_tag_interface(ntree);
}
LukasTonne marked this conversation as resolved Outdated

Don't have a strong preference, but this seems a bit more idiomatic and clear to me:
std::copy(old_categories.begin(), old_categories.end(), new_categories.data());

Don't have a strong preference, but this seems a bit more idiomatic and clear to me: ` std::copy(old_categories.begin(), old_categories.end(), new_categories.data());`
bool ntreeContainsPanel(const bNodeTree *ntree, const bNodePanel *panel)
{
return ntree->panels().contains(const_cast<bNodePanel *>(panel));
}
int ntreeGetPanelIndex(const bNodeTree *ntree, const bNodePanel *panel)
{
return ntree->panels().first_index_try(const_cast<bNodePanel *>(panel));
}
bNodePanel *ntreeAddPanel(bNodeTree *ntree, const char *name, int flag)
{
bNodePanel **old_panels_array = ntree->panels_array;
const Span<const bNodePanel *> old_panels = ntree->panels();
ntree->panels_array = MEM_cnew_array<bNodePanel *>(ntree->panels_num + 1, __func__);
++ntree->panels_num;
const MutableSpan<bNodePanel *> new_panels = ntree->panels_for_write();
std::copy(const_cast<bNodePanel **>(old_panels.begin()),
const_cast<bNodePanel **>(old_panels.end()),
new_panels.data());
bNodePanel *new_panel = MEM_cnew<bNodePanel>(__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<const bNodePanel *> old_panels = ntree->panels();
ntree->panels_array = MEM_cnew_array<bNodePanel *>(ntree->panels_num + 1, __func__);
++ntree->panels_num;
const MutableSpan<bNodePanel *> 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<bNodePanel **>(old_panels_front.begin()),
const_cast<bNodePanel **>(old_panels_front.end()),
new_panels.data());
std::copy(const_cast<bNodePanel **>(old_panels_back.begin()),
const_cast<bNodePanel **>(old_panels_back.end()),
new_panels.drop_front(index + 1).data());
bNodePanel *new_panel = MEM_cnew<bNodePanel>(__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<const bNodePanel *> old_panels = ntree->panels();
ntree->panels_array = MEM_cnew_array<bNodePanel *>(ntree->panels_num - 1, __func__);
--ntree->panels_num;
const MutableSpan<bNodePanel *> 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<bNodePanel **>(old_panels_front.begin()),
const_cast<bNodePanel **>(old_panels_front.end()),
new_panels.data());
std::copy(const_cast<bNodePanel **>(old_panels_back.begin()),
const_cast<bNodePanel **>(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<bNodePanel *> panels = ntree->panels_for_write();
if (old_index == new_index) {
return;
}
else if (old_index < new_index) {
const Span<bNodePanel *> 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<bNodePanel *> 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,

View File

@ -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) {

View File

@ -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*/,

View File

@ -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);

View File

@ -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<const bNodeSocket *> interface_inputs() const;
blender::Span<const bNodeSocket *> interface_outputs() const;
blender::Span<const bNodePanel *> panels() const;
LukasTonne marked this conversation as resolved Outdated

panels() should return Span<const bNodePanel *>, so a const bNodeSocket * doesn't give you a mutable bNodePanel

`panels()` should return `Span<const bNodePanel *>`, so a `const bNodeSocket *` doesn't give you a mutable `bNodePanel`
blender::MutableSpan<bNodePanel *> panels_for_write();
#endif
} bNodeTree;

View File

@ -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;

View File

@ -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<bNodeTree *>(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);

View File

@ -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)