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 724 additions and 5 deletions
Showing only changes of commit 909430f5dc - Show all commits

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 NodeSocketCategoryOperator():
@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_socket_category_add(NodeSocketCategoryOperator, Operator):
'''Add a new socket category to the tree'''
bl_idname = "node.function_parameter_add"
bl_label = "Add Socket Category"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
snode = context.space_data
tree = snode.edit_tree
categories = tree.socket_categories
# 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(categories.active_index + 1, len(categories))
categories.new("Category")
categories.move(len(categories) - 1, dst_index)
categories.active_index = dst_index
return {'FINISHED'}
class NODE_OT_socket_category_remove(NodeSocketCategoryOperator, Operator):
'''Remove a socket category from the tree'''
bl_idname = "node.function_parameter_remove"
bl_label = "Remove Socket Category"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
snode = context.space_data
tree = snode.edit_tree
categories = tree.socket_categories
if categories.active:
categories.remove(categories.active)
categories.active_index = min(categories.active_index, len(categories) - 1)
return {'FINISHED'}
class NODE_OT_socket_category_move(NodeSocketCategoryOperator, Operator):
'''Move a socket category to another position'''
bl_idname = "node.function_parameter_move"
bl_label = "Move Socket Category"
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
categories = tree.socket_categories
if self.direction == 'UP' and categories.active_index > 0:
categories.move(categories.active_index, categories.active_index - 1)
categories.active_index -= 1
elif self.direction == 'DOWN' and categories.active_index < len(categories) - 1:
categories.move(categories.active_index, categories.active_index + 1)
categories.active_index += 1
return {'FINISHED'}
classes = (
NodeSetting,
NODE_OT_add_node,
NODE_OT_add_simulation_zone,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_socket_category_add,
NODE_OT_socket_category_remove,
NODE_OT_socket_category_move,
NODE_OT_tree_path_parent,
)

View File

@ -954,6 +954,64 @@ class NODE_PT_node_tree_interface_outputs(NodeTreeInterfacePanel):
self.draw_socket_list(context, "OUT", "outputs", "active_output")
class NODE_UL_socket_categories(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_socket_categories(Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = "Group"
bl_label = "Socket Categories"
@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
def draw(self, context):
layout = self.layout
snode = context.space_data
tree = snode.edit_tree
split = layout.row()
split.template_list(
"NODE_UL_socket_categories",
"",
tree,
"socket_categories",
tree.socket_categories,
"active_index")
ops_col = split.column()
add_remove_col = ops_col.column(align=True)
add_remove_col.operator("node.function_parameter_add", icon='ADD', text="")
add_remove_col.operator("node.function_parameter_remove", icon='REMOVE', text="")
ops_col.separator()
up_down_col = ops_col.column(align=True)
props = up_down_col.operator("node.function_parameter_move", icon='TRIA_UP', text="")
props.direction = 'UP'
props = up_down_col.operator("node.function_parameter_move", icon='TRIA_DOWN', text="")
props.direction = 'DOWN'
active_category = tree.socket_categories.active
if active_category is not None:
layout.prop(active_category, "name")

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.
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'}:
@ -1097,6 +1155,8 @@ classes = (
NODE_UL_interface_sockets,
NODE_PT_node_tree_interface_inputs,
NODE_PT_node_tree_interface_outputs,
NODE_UL_socket_categories,
NODE_PT_socket_categories,
NODE_UL_simulation_zone_items,
NODE_PT_simulation_zone_items,

View File

@ -115,6 +115,7 @@ set(SRC_DNA_DEFAULTS_INC
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_meta_defaults.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_modifier_defaults.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_movieclip_defaults.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_node_defaults.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_object_defaults.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_particle_defaults.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_pointcloud_defaults.h

View File

@ -542,6 +542,12 @@ void ntreeBlendWrite(struct BlendWriter *writer, struct bNodeTree *ntree);
/** \name Node Tree Interface
* \{ */
/**
* Run this after relevant changes to categories
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.
* to ensure sockets remain sorted by category.
*/
void ntreeEnsureSocketCategoryOrder(bNodeTree *ntree);
void ntreeRemoveSocketInterface(bNodeTree *ntree, bNodeSocket *sock);
struct bNodeSocket *ntreeAddSocketInterface(struct bNodeTree *ntree,
@ -549,6 +555,47 @@ struct bNodeSocket *ntreeAddSocketInterface(struct bNodeTree *ntree,
const char *idname,
const char *name);
/** Set the category of the interface socket. */
void ntreeSetSocketInterfaceCategory(bNodeTree *ntree,
bNodeSocket *sock,
bNodeSocketCategory *category);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Node Tree Socket Categories
* \{ */
/**
* Add a new socket category to the node tree.
* \param name Name of the new category.
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:`
* \param name Flags of the new category.
*/
bNodeSocketCategory *ntreeAddSocketCategory(bNodeTree *ntree, const char *name, int flag);
/**
* Insert a new socket category in the node tree.
* \param name Name of the new category.
* \param name Flags of the new category.
* \param index Index at which to insert the category.
*/
bNodeSocketCategory *ntreeInsertSocketCategory(bNodeTree *ntree,
const char *name,
int flag,
int index);
/** Remove a socket category from the node tree. */
void ntreeRemoveSocketCategory(bNodeTree *ntree, bNodeSocketCategory *category);
/** Remove all socket categories from the node tree. */
void ntreeClearSocketCategories(bNodeTree *ntree);
/**
* Move a socket category up or down in the node tree.
* \param index Index to which to move the category.
*/
void ntreeMoveSocketCategory(bNodeTree *ntree, bNodeSocketCategory *category, int new_index);
/** \} */
/* -------------------------------------------------------------------- */
@ -867,16 +914,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 +931,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

@ -516,6 +516,16 @@ inline blender::Span<bNode *> bNodeTree::root_frames() const
return this->runtime->root_frames;
}
inline blender::Span<bNodeSocketCategory> bNodeTree::socket_categories() const
{
return blender::Span(socket_categories_array, socket_categories_num);
}
inline blender::MutableSpan<bNodeSocketCategory> bNodeTree::socket_categories_for_write()
{
return blender::MutableSpan(socket_categories_array, socket_categories_num);
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -645,6 +645,12 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
write_node_socket_interface(writer, sock);
}
BLO_write_struct_array(
writer, bNodeSocketCategory, ntree->socket_categories_num, ntree->socket_categories_array);
for (const bNodeSocketCategory &category : ntree->socket_categories()) {
BLO_write_string(writer, category.name);
}
BKE_previewimg_blend_write(writer, ntree->preview);
}
@ -865,6 +871,11 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree)
BLO_read_data_address(reader, &link->tosock);
}
BLO_read_data_address(reader, &ntree->socket_categories_array);
for (bNodeSocketCategory &category : ntree->socket_categories_for_write()) {
BLO_read_data_address(reader, &category.name);
}
/* TODO: should be dealt by new generic cache handling of IDs... */
ntree->previews = nullptr;
@ -3647,10 +3658,18 @@ static bNodeSocket *make_socket_interface(bNodeTree *ntree,
STRNCPY(sock->name, name);
sock->storage = nullptr;
sock->flag |= SOCK_COLLAPSED;
sock->category_index = -1;
return sock;
}
static int node_socket_category_cmp(const void *a, const void *b)
{
const bNodeSocket *sock_a = static_cast<const bNodeSocket *>(a);
const bNodeSocket *sock_b = static_cast<const bNodeSocket *>(b);
return (sock_a->category_index > sock_b->category_index) ? 1 : 0;
}
bNodeSocket *ntreeFindSocketInterface(bNodeTree *ntree,
const eNodeSocketInOut in_out,
const char *identifier)
@ -3666,6 +3685,12 @@ bNodeSocket *ntreeFindSocketInterface(bNodeTree *ntree,
} // namespace blender::bke
void ntreeEnsureSocketCategoryOrder(bNodeTree *ntree)
{
BLI_listbase_sort(&ntree->inputs, blender::bke::node_socket_category_cmp);
BLI_listbase_sort(&ntree->outputs, blender::bke::node_socket_category_cmp);
}
bNodeSocket *ntreeAddSocketInterface(bNodeTree *ntree,
const eNodeSocketInOut in_out,
const char *idname,
@ -3678,10 +3703,30 @@ bNodeSocket *ntreeAddSocketInterface(bNodeTree *ntree,
else if (in_out == SOCK_OUT) {
BLI_addtail(&ntree->outputs, iosock);
}
ntreeEnsureSocketCategoryOrder(ntree);
BKE_ntree_update_tag_interface(ntree);
LukasTonne marked this conversation as resolved Outdated

VectorSet and VectorSet::index_of` are probably a more natural choice here, with a bit less boilerplate.

`VectorSet` and VectorSet::index_of` are probably a more natural choice here, with a bit less boilerplate.
return iosock;
}
void ntreeSetSocketInterfaceCategory(bNodeTree *ntree,
bNodeSocket *socket,
bNodeSocketCategory *category)
{
if (category == NULL) {
socket->category_index = -1;
return;
}
socket->category_index = category - ntree->socket_categories_array;
BLI_assert(ntree->socket_categories().index_range().contains(socket->category_index));
ntreeEnsureSocketCategoryOrder(ntree);
BKE_ntree_update_tag_interface(ntree);
}
namespace blender::bke {
bNodeSocket *ntreeInsertSocketInterface(bNodeTree *ntree,
@ -3697,6 +3742,9 @@ bNodeSocket *ntreeInsertSocketInterface(bNodeTree *ntree,
else if (in_out == SOCK_OUT) {
BLI_insertlinkbefore(&ntree->outputs, next_sock, iosock);
}
ntreeEnsureSocketCategoryOrder(ntree);
BKE_ntree_update_tag_interface(ntree);
return iosock;
}
@ -3752,11 +3800,177 @@ void ntreeRemoveSocketInterface(bNodeTree *ntree, bNodeSocket *sock)
blender::bke::node_socket_interface_free(ntree, sock, true);
MEM_freeN(sock);
/* No need to resort by category, removing doesn't change anything. */
BKE_ntree_update_tag_interface(ntree);
}
namespace blender::bke {
/* Fix socket category indices after changes. */
static void remap_socket_categories(bNodeTree &ntree, std::function<int(int)> index_fn)
LukasTonne marked this conversation as resolved Outdated

Better to use FunctionRef than std::function here I think, for the reasons mentioned in the header.

Better to use `FunctionRef` than `std::function` here I think, for the reasons mentioned in the header.

Not needed any more, sockets now store category UID instead of simple index, so category changes don't require remapping.

Not needed any more, sockets now store category UID instead of simple index, so category changes don't require remapping.
{
LISTBASE_FOREACH (bNodeSocket *, socket, &ntree.inputs) {
socket->category_index = index_fn(socket->category_index);
BLI_assert(socket->category_index == -1 ||
ntree.socket_categories().index_range().contains(socket->category_index));
}
LISTBASE_FOREACH (bNodeSocket *, socket, &ntree.outputs) {
socket->category_index = index_fn(socket->category_index);
BLI_assert(socket->category_index == -1 ||
ntree.socket_categories().index_range().contains(socket->category_index));
}
}
} // namespace blender::bke
bNodeSocketCategory *ntreeAddSocketCategory(bNodeTree *ntree, const char *name, int flag)
{
bNodeSocketCategory *old_categories_array = ntree->socket_categories_array;
const Span<bNodeSocketCategory> old_categories = ntree->socket_categories();
ntree->socket_categories_array = MEM_cnew_array<bNodeSocketCategory>(
ntree->socket_categories_num + 1, "socket categories");
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.
++ntree->socket_categories_num;
const MutableSpan<bNodeSocketCategory> new_categories = ntree->socket_categories_for_write();
for (const int i : new_categories.index_range().drop_back(1)) {
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());`
new_categories[i] = old_categories[i];
}
bNodeSocketCategory &new_category = new_categories[new_categories.size() - 1];
new_category = {BLI_strdup(name), flag};
MEM_SAFE_FREE(old_categories_array);
/* No need to remap socket categories, in this case old indices stay the same. */
/* No need to sort sockets, nothing is using the new category yet */
return &new_category;
}
bNodeSocketCategory *ntreeInsertSocketCategory(bNodeTree *ntree,
const char *name,
int flag,
int index)
{
if (!blender::IndexRange(ntree->socket_categories().size() + 1).contains(index)) {
return nullptr;
}
bNodeSocketCategory *old_categories_array = ntree->socket_categories_array;
const Span<bNodeSocketCategory> old_categories = ntree->socket_categories();
ntree->socket_categories_array = MEM_cnew_array<bNodeSocketCategory>(
ntree->socket_categories_num + 1, "socket categories");
++ntree->socket_categories_num;
const MutableSpan<bNodeSocketCategory> new_categories = ntree->socket_categories_for_write();
for (const int i : new_categories.index_range().take_front(index)) {
new_categories[i] = old_categories[i];
}
for (const int i : new_categories.index_range().drop_front(index)) {
new_categories[i + 1] = old_categories[i];
}
bNodeSocketCategory &new_category = new_categories[index];
new_category = {BLI_strdup(name), flag};
MEM_SAFE_FREE(old_categories_array);
blender::bke::remap_socket_categories(*ntree, [index](const int old_category_index) {
return old_category_index < index ? old_category_index : old_category_index + 1;
});
/* No need to sort sockets, nothing is using the new category yet */
return &new_category;
}
void ntreeRemoveSocketCategory(bNodeTree *ntree, bNodeSocketCategory *category)
{
const int index = category - ntree->socket_categories_array;
if (!ntree->socket_categories().contains_ptr(category)) {
return;
}
bNodeSocketCategory *old_categories_array = ntree->socket_categories_array;
const Span<bNodeSocketCategory> old_categories = ntree->socket_categories();
ntree->socket_categories_array = MEM_cnew_array<bNodeSocketCategory>(
ntree->socket_categories_num - 1, "socket categories");
--ntree->socket_categories_num;
const MutableSpan<bNodeSocketCategory> new_categories = ntree->socket_categories_for_write();
for (const int i : new_categories.index_range().take_front(index)) {
new_categories[i] = old_categories[i];
}
for (const int i : new_categories.index_range().drop_front(index)) {
new_categories[i] = old_categories[i + 1];
}
MEM_SAFE_FREE(old_categories_array);
blender::bke::remap_socket_categories(*ntree, [index](const int old_category_index) {
return old_category_index < index ? old_category_index :
(old_category_index > index ? old_category_index - 1 : -1);
});
ntreeEnsureSocketCategoryOrder(ntree);
}
void ntreeClearSocketCategories(bNodeTree *ntree)
{
MEM_SAFE_FREE(ntree->socket_categories_array);
ntree->socket_categories_array = nullptr;
ntree->socket_categories_num = 0;
blender::bke::remap_socket_categories(*ntree, [](const int /*old_index*/) { return -1; });
/* No need to sort sockets, only null category exists, relative order remains unchanged. */
}
void ntreeMoveSocketCategory(bNodeTree *ntree, bNodeSocketCategory *category, int new_index)
{
const int old_index = category - ntree->socket_categories_array;
if (!ntree->socket_categories().contains_ptr(category)) {
return;
}
const MutableSpan<bNodeSocketCategory> categories = ntree->socket_categories_for_write();
if (old_index == new_index) {
return;
}
else if (old_index < new_index) {
const bNodeSocketCategory tmp = categories[old_index];
for (int i = old_index; i < new_index; ++i) {
categories[i] = categories[i + 1];
}
categories[new_index] = tmp;
blender::bke::remap_socket_categories(
*ntree, [old_index, new_index](const int old_category_index) {
return old_category_index < old_index || old_category_index > new_index ?
old_category_index :
(old_category_index == old_index ? new_index : old_category_index - 1);
});
}
else /* old_index > new_index */ {
const bNodeSocketCategory tmp = categories[old_index];
for (int i = old_index; i > new_index; --i) {
categories[i] = categories[i - 1];
}
categories[new_index] = tmp;
blender::bke::remap_socket_categories(
*ntree, [old_index, new_index](const int old_category_index) {
return old_category_index < new_index || old_category_index > old_index ?
old_category_index :
(old_category_index == old_index ? new_index : old_category_index + 1);
});
}
ntreeEnsureSocketCategoryOrder(ntree);
}
namespace blender::bke {
static bool ntree_contains_tree_exec(const bNodeTree *tree_to_search_in,
const bNodeTree *tree_to_search_for,
Set<const bNodeTree *> &already_passed)

View File

@ -12,6 +12,8 @@
#include "DNA_movieclip_types.h"
#include "DNA_genfile.h"
#include "BLI_assert.h"
#include "BLI_listbase.h"
#include "BLI_set.hh"
@ -101,7 +103,7 @@ static void version_geometry_nodes_add_realize_instance_nodes(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) {
@ -135,5 +137,17 @@ void blo_do_versions_400(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
*/
{
/* Keep this block, even when empty. */
if (!DNA_struct_elem_find(fd->filesdna, "bNodeSocket", "int", "category_index")) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
LISTBASE_FOREACH (bNodeSocket *, socket, &ntree->inputs) {
socket->category_index = -1;
}
LISTBASE_FOREACH (bNodeSocket *, socket, &ntree->outputs) {
socket->category_index = -1;
}
}
FOREACH_NODETREE_END;
}
}
}

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,8 @@ 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);
}
uiItemPointerR(col, ptr, "category", &tree_ptr, "socket_categories", nullptr, 0);
}
static void node_socket_virtual_draw_color(bContext * /*C*/,

View File

@ -2191,6 +2191,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 category from the active socket interface. */
sock->category_index = active_sock->category_index;
}
else {
/* XXX TODO: define default socket type for a tree! */
@ -2582,6 +2584,8 @@ static int ntree_socket_move_exec(bContext *C, wmOperator *op)
}
}
ntreeEnsureSocketCategoryOrder(ntree);
BKE_ntree_update_tag_interface(ntree);
ED_node_tree_propagate_change(C, CTX_data_main(C), ntree);

View File

@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup DNA
*/
#pragma once
/* Struct members on own line. */
/* clang-format off */
/* -------------------------------------------------------------------- */
/** \name bArmature Struct
* \{ */
#define _DNA_DEFAULT_bNodeSocket \
{ \
.category_index = -1, \
}
/** \} */
/* clang-format on */

View File

@ -168,6 +168,10 @@ typedef struct bNodeSocket {
/** Custom data for inputs, only UI writes in this. */
bNodeStack ns DNA_DEPRECATED;
/* UI category index of the socket. */
int category_index;
int _pad2;
bNodeSocketRuntimeHandle *runtime;
#ifdef __cplusplus
@ -531,6 +535,12 @@ typedef struct bNodeLink {
#define NTREE_CHUNKSIZE_512 512
#define NTREE_CHUNKSIZE_1024 1024
typedef struct bNodeSocketCategory {
char *name;
int flag;
int _pad;
} bNodeSocketCategory;
/* 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 */
@ -594,6 +604,11 @@ typedef struct bNodeTree {
/** Image representing what the node group does. */
struct PreviewImage *preview;
/* UI categories for sockets */
struct bNodeSocketCategory *socket_categories_array;
int socket_categories_num;
int active_socket_category;
bNodeTreeRuntimeHandle *runtime;
#ifdef __cplusplus
@ -657,6 +672,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<bNodeSocketCategory> socket_categories() const;
blender::MutableSpan<bNodeSocketCategory> socket_categories_for_write();
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`
#endif
} bNodeTree;

View File

@ -126,6 +126,7 @@
#include "DNA_meta_defaults.h"
#include "DNA_modifier_defaults.h"
#include "DNA_movieclip_defaults.h"
#include "DNA_node_defaults.h"
#include "DNA_object_defaults.h"
#include "DNA_particle_defaults.h"
#include "DNA_pointcloud_defaults.h"

View File

@ -3164,6 +3164,57 @@ static IDProperty **rna_NodeSocketInterface_idprops(PointerRNA *ptr)
return &sock->prop;
}
static PointerRNA rna_NodeSocketInterface_category_get(PointerRNA *ptr)
{
bNodeSocket *socket = (bNodeSocket *)ptr->data;
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
const int index = socket->category_index;
bNodeSocketCategory *category = NULL;
if (index >= 0 && index < ntree->socket_categories_num) {
category = &ntree->socket_categories_array[index];
}
PointerRNA r_ptr;
RNA_pointer_create(&ntree->id, &RNA_NodeSocketCategory, category, &r_ptr);
return r_ptr;
}
static void rna_NodeSocketInterface_category_set(PointerRNA *ptr,
PointerRNA value,
struct ReportList *reports)
{
bNodeSocket *socket = (bNodeSocket *)ptr->data;
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
bNodeSocketCategory *category = (bNodeSocketCategory *)value.data;
const size_t index = category - ntree->socket_categories_array;
if (index < 0 || index >= ntree->socket_categories_num) {
BKE_report(reports, RPT_ERROR, "Category is not in the node tree interface");
return;
}
ntreeSetSocketInterfaceCategory(ntree, socket, category);
}
static bool rna_NodeSocketInterface_category_poll(PointerRNA *ptr, PointerRNA value)
{
bNodeSocket *socket = (bNodeSocket *)ptr->data;
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
bNodeSocketCategory *category = (bNodeSocketCategory *)value.data;
LukasTonne marked this conversation as resolved Outdated

Super picky but this newline isn't helpful IMO, better to more closely connect the variable declaration and the null check.

Super picky but this newline isn't helpful IMO, better to more closely connect the variable declaration and the null check.
if (category == NULL) {
return true;
}
const int index = category - ntree->socket_categories_array;
if (index < 0 || index >= ntree->socket_categories_num) {
return false;
}
return true;
}
static void rna_NodeSocketInterface_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr)
{
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
@ -3283,6 +3334,96 @@ static void rna_NodeSocketStandard_value_and_relation_update(struct bContext *C,
DEG_relations_tag_update(bmain);
}
/* ******** Node Socket Categories ******** */
static void rna_NodeSocketCategory_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr)
{
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
BKE_ntree_update_tag_interface(ntree);
ED_node_tree_propagate_change(NULL, bmain, ntree);
}
static bNodeSocketCategory *rna_NodeTree_socket_categories_new(bNodeTree *ntree,
Main *bmain,
ReportList *reports,
const char *name)
{
bNodeSocketCategory *category = ntreeAddSocketCategory(ntree, name, 0);
if (category == NULL) {
BKE_report(reports, RPT_ERROR, "Unable to create socket category");
}
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 category;
}
static void rna_NodeTree_socket_categories_remove(bNodeTree *ntree,
Main *bmain,
bNodeSocketCategory *category)
{
ntreeRemoveSocketCategory(ntree, category);
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_socket_categories_clear(bNodeTree *ntree, Main *bmain)
{
ntreeClearSocketCategories(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_socket_categories_move(bNodeTree *ntree,
Main *bmain,
int from_index,
int to_index)
{
if (from_index < 0 || from_index >= ntree->socket_categories_num || to_index < 0 ||
to_index >= ntree->socket_categories_num)
{
return;
}
ntreeMoveSocketCategory(ntree, &ntree->socket_categories_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_socket_category_get(PointerRNA *ptr)
{
bNodeTree *ntree = (bNodeTree *)ptr->data;
bNodeSocketCategory *category = NULL;
if (ntree->active_socket_category >= 0 &&
ntree->active_socket_category < ntree->socket_categories_num)
{
category = &ntree->socket_categories_array[ntree->active_socket_category];
}
PointerRNA r_ptr;
RNA_pointer_create(ptr->owner_id, &RNA_NodeSocketCategory, category, &r_ptr);
return r_ptr;
}
static void rna_NodeTree_active_socket_category_set(PointerRNA *ptr,
PointerRNA value,
struct ReportList *UNUSED(reports))
{
bNodeSocketCategory *category = (bNodeSocketCategory *)value.data;
bNodeTree *ntree = (bNodeTree *)ptr->data;
ntree->active_socket_category = category - ntree->socket_categories_array;
}
/* ******** Node Types ******** */
static void rna_NodeInternalSocketTemplate_name_get(PointerRNA *ptr, char *value)
@ -11610,6 +11751,18 @@ 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, "category", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_funcs(prop,
"rna_NodeSocketInterface_category_get",
"rna_NodeSocketInterface_category_set",
NULL,
"rna_NodeSocketInterface_category_poll");
RNA_def_property_struct_type(prop, "NodeSocketCategory");
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(
prop, "Socket Category", "Category 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(
@ -12878,6 +13031,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_category(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "NodeSocketCategory", NULL);
RNA_def_struct_ui_text(srna, "NodeSocketCategory", "Group of sockets in node tree interface");
RNA_def_struct_sdna(srna, "bNodeSocketCategory");
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 category");
RNA_def_struct_name_property(srna, prop);
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketCategory_update");
}
static void rna_def_nodetree_nodes_api(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
@ -13017,6 +13187,66 @@ static void rna_def_node_tree_sockets_api(BlenderRNA *brna, PropertyRNA *cprop,
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
}
static void rna_def_node_tree_socket_categories_api(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
PropertyRNA *prop;
PropertyRNA *parm;
FunctionRNA *func;
RNA_def_property_srna(cprop, "NodeSocketCategories");
srna = RNA_def_struct(brna, "NodeSocketCategories", NULL);
RNA_def_struct_sdna(srna, "bNodeTree");
RNA_def_struct_ui_text(
srna, "Node Tree Socket Categories", "Collection of socket categories in a node tree");
prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, NULL, "active_socket_category");
RNA_def_property_ui_text(prop, "Active Index", "Index of the active category");
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, "NodeSocketCategory");
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_pointer_funcs(prop,
"rna_NodeTree_active_socket_category_get",
"rna_NodeTree_active_socket_category_set",
NULL,
NULL);
RNA_def_property_ui_text(prop, "Active", "Active category");
RNA_def_property_update(prop, NC_NODE, NULL);
func = RNA_def_function(srna, "new", "rna_NodeTree_socket_categories_new");
RNA_def_function_ui_description(func, "Add a new socket category 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, 0, PARM_REQUIRED);
/* return value */
parm = RNA_def_pointer(func, "category", "NodeSocketCategory", "", "New category");
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "remove", "rna_NodeTree_socket_categories_remove");
RNA_def_function_ui_description(func, "Remove a socket category from the tree");
RNA_def_function_flag(func, FUNC_USE_MAIN);
parm = RNA_def_pointer(func, "category", "NodeSocketCategory", "", "The category to remove");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
func = RNA_def_function(srna, "clear", "rna_NodeTree_socket_categories_clear");
RNA_def_function_ui_description(func, "Remove all categories from the tree");
RNA_def_function_flag(func, FUNC_USE_MAIN);
func = RNA_def_function(srna, "move", "rna_NodeTree_socket_categories_move");
RNA_def_function_ui_description(func, "Move a socket category 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 category to move", 0, 10000);
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
parm = RNA_def_int(
func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the category", 0, 10000);
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
}
static void rna_def_nodetree(BlenderRNA *brna)
{
StructRNA *srna;
@ -13119,6 +13349,14 @@ 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, "socket_categories", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, NULL, "socket_categories_array", "socket_categories_num");
RNA_def_property_struct_type(prop, "NodeSocketCategory");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(
prop, "Socket Categories", "Socket categories for structuring the node tree interface");
rna_def_node_tree_socket_categories_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");
@ -13389,6 +13627,7 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_simulation_state_item(brna);
rna_def_function_node(brna);
rna_def_node_socket_category(brna);
rna_def_nodetree(brna);
rna_def_node_socket_standard_types(brna);