Geometry Nodes: Menu Switch Node

This patch adds support for _Menu Switch_ nodes and enum definitions in
node trees more generally. The design is based on the outcome of the
[2022 Nodes Workshop](https://code.blender.org/2022/11/geometry-nodes-workshop-2022/#menu-switch).

The _Menu Switch_ node is an advanced version of the _Switch_ node which
has a customizable **menu input socket** instead of a simple boolean.
The _items_ of this menu are owned by the node itself. Each item has a
name and description and unique identifier that is used internally. A
menu _socket_ represents a concrete value out of the list of items.

To enable selection of an enum value for unconnected sockets the menu is
presented as a dropdown list like built-in enums. When the socket is
connected a shared pointer to the enum definition is propagated along
links and stored in socket default values. This allows node groups to
expose a menu from an internal menu switch as a parameter. The enum
definition is a runtime copy of the enum items in DNA that allows
sharing.

A menu socket can have multiple connections, which can lead to
ambiguity. If two or more different menu source nodes are connected to a
socket it gets marked as _undefined_. Any connection to an undefined
menu socket is invalid as a hint to users that there is a problem. A
warning/error is also shown on nodes with undefined menu sockets.

At runtime the value of a menu socket is the simple integer identifier.
This can also be a field in geometry nodes. The identifier is unique
within each enum definition, and it is persistent even when items are
added, removed, or changed. Changing the name of an item does not affect
the internal identifier, so users can rename enum items without breaking
existing input values. This also persists if, for example, a linked node
group is temporarily unavailable.

Pull Request: https://projects.blender.org/blender/blender/pulls/113445
This commit is contained in:
Lukas Tönne 2024-01-26 12:40:01 +01:00
parent dd0798927f
commit 5ad49f4142
36 changed files with 1867 additions and 19 deletions

View File

@ -386,6 +386,61 @@ class NODE_OT_interface_item_remove(NodeInterfaceOperator, Operator):
return {'FINISHED'}
class NODE_OT_enum_definition_item_add(Operator):
'''Add an enum item to the definition'''
bl_idname = "node.enum_definition_item_add"
bl_label = "Add Item"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
node = context.active_node
enum_def = node.enum_definition
item = enum_def.enum_items.new("Item")
enum_def.active_index = enum_def.enum_items[:].index(item)
return {'FINISHED'}
class NODE_OT_enum_definition_item_remove(Operator):
'''Remove the selected enum item from the definition'''
bl_idname = "node.enum_definition_item_remove"
bl_label = "Remove Item"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
node = context.active_node
enum_def = node.enum_definition
item = enum_def.active_item
if item:
enum_def.enum_items.remove(item)
enum_def.active_index = min(max(enum_def.active_index, 0), len(enum_def.enum_items) - 1)
return {'FINISHED'}
class NODE_OT_enum_definition_item_move(Operator):
'''Remove the selected enum item from the definition'''
bl_idname = "node.enum_definition_item_move"
bl_label = "Move Item"
bl_options = {'REGISTER', 'UNDO'}
direction: EnumProperty(
name="Direction",
description="Move up or down",
items=[("UP", "Up", ""), ("DOWN", "Down", "")]
)
def execute(self, context):
node = context.active_node
enum_def = node.enum_definition
index = enum_def.active_index
if self.direction == 'UP':
enum_def.enum_items.move(index, index - 1)
enum_def.active_index = min(max(index - 1, 0), len(enum_def.enum_items) - 1)
else:
enum_def.enum_items.move(index, index + 1)
enum_def.active_index = min(max(index + 1, 0), len(enum_def.enum_items) - 1)
return {'FINISHED'}
classes = (
NodeSetting,
@ -397,4 +452,7 @@ classes = (
NODE_OT_interface_item_duplicate,
NODE_OT_interface_item_remove,
NODE_OT_tree_path_parent,
NODE_OT_enum_definition_item_add,
NODE_OT_enum_definition_item_remove,
NODE_OT_enum_definition_item_move,
)

View File

@ -543,6 +543,7 @@ class NODE_MT_category_GEO_UTILITIES(Menu):
layout.menu("NODE_MT_category_GEO_UTILITIES_MATH")
layout.menu("NODE_MT_category_GEO_UTILITIES_ROTATION")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeMenuSwitch")
node_add_menu.add_node_type(layout, "FunctionNodeRandomValue")
node_add_menu.add_repeat_zone(layout, label="Repeat Zone")
node_add_menu.add_node_type(layout, "GeometryNodeSwitch")

View File

@ -1213,6 +1213,60 @@ class NODE_PT_index_switch_node_items(Panel):
row.operator("node.index_switch_item_remove", icon='REMOVE', text="").index = i
class NODE_UL_enum_definition_items(bpy.types.UIList):
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index):
layout.prop(item, "name", text="", emboss=False, icon_value=icon)
class NODE_PT_menu_switch_items(Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = "Node"
bl_label = "Menu Switch"
@classmethod
def poll(cls, context):
snode = context.space_data
if snode is None:
return False
node = context.active_node
if node is None or node.bl_idname != "GeometryNodeMenuSwitch":
return False
return True
def draw(self, context):
node = context.active_node
layout = self.layout
split = layout.row()
split.template_list(
"NODE_UL_enum_definition_items",
"",
node.enum_definition,
"enum_items",
node.enum_definition,
"active_index")
ops_col = split.column()
add_remove_col = ops_col.column(align=True)
add_remove_col.operator("node.enum_definition_item_add", icon='ADD', text="")
add_remove_col.operator("node.enum_definition_item_remove", icon='REMOVE', text="")
ops_col.separator()
up_down_col = ops_col.column(align=True)
props = up_down_col.operator("node.enum_definition_item_move", icon='TRIA_UP', text="")
props.direction = 'UP'
props = up_down_col.operator("node.enum_definition_item_move", icon='TRIA_DOWN', text="")
props.direction = 'DOWN'
active_item = node.enum_definition.active_item
if active_item is not None:
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(active_item, "description")
# Grease Pencil properties
class NODE_PT_annotation(AnnotationDataPanel, Panel):
bl_space_type = 'NODE_EDITOR'
@ -1285,6 +1339,8 @@ classes = (
NODE_PT_bake_node_items,
NODE_PT_index_switch_node_items,
NODE_PT_repeat_zone_items,
NODE_UL_enum_definition_items,
NODE_PT_menu_switch_items,
NODE_PT_active_node_properties,
node_panel(EEVEE_MATERIAL_PT_settings),

View File

@ -1328,6 +1328,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_GET_NAMED_GRID 2121
#define GEO_NODE_STORE_NAMED_GRID 2122
#define GEO_NODE_SORT_ELEMENTS 2123
#define GEO_NODE_MENU_SWITCH 2124
/** \} */

View File

@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <string>
#include "BLI_implicit_sharing.hh"
#include "BLI_vector.hh"
namespace blender::bke {
/* Flags for #bNodeSocketValueMenu. */
enum NodeSocketValueMenuRuntimeFlag {
/* Socket has conflicting menu connections and cannot resolve items. */
NODE_MENU_ITEMS_CONFLICT = (1 << 0),
};
/* -------------------------------------------------------------------- */
/** \name Runtime enum items list.
* \{ */
/**
* Runtime copy of #NodeEnumItem for use in #RuntimeNodeEnumItems.
*/
struct RuntimeNodeEnumItem {
std::string name;
std::string description;
/* Immutable unique identifier. */
int identifier;
};
/**
* Shared immutable list of enum items.
* These are owned by a node and can be referenced by node sockets.
*/
struct RuntimeNodeEnumItems : ImplicitSharingMixin {
Vector<RuntimeNodeEnumItem> items;
void delete_self() override
{
delete this;
}
};
/** \} */
} // namespace blender::bke

View File

@ -158,6 +158,7 @@ static const bNodeSocketStaticTypeInfo node_socket_subtypes[] = {
{"NodeSocketCollection", "NodeTreeInterfaceSocketCollection", SOCK_COLLECTION, PROP_NONE},
{"NodeSocketTexture", "NodeTreeInterfaceSocketTexture", SOCK_TEXTURE, PROP_NONE},
{"NodeSocketMaterial", "NodeTreeInterfaceSocketMaterial", SOCK_MATERIAL, PROP_NONE},
{"NodeSocketMenu", "NodeTreeInterfaceSocketMenu", SOCK_MENU, PROP_NONE},
};
template<typename Fn> bool socket_data_to_static_type(const eNodeSocketDatatype type, const Fn &fn)
@ -199,6 +200,9 @@ template<typename Fn> bool socket_data_to_static_type(const eNodeSocketDatatype
case SOCK_MATERIAL:
fn.template operator()<bNodeSocketValueMaterial>();
return true;
case SOCK_MENU:
fn.template operator()<bNodeSocketValueMenu>();
return true;
case SOCK_CUSTOM:
case SOCK_SHADER:

View File

@ -232,6 +232,7 @@ set(SRC
intern/multires_versioning.cc
intern/nla.cc
intern/node.cc
intern/node_enum_definition.cc
intern/node_runtime.cc
intern/node_socket_value.cc
intern/node_tree_anonymous_attributes.cc
@ -456,6 +457,7 @@ set(SRC
BKE_nla.h
BKE_node.h
BKE_node.hh
BKE_node_enum.hh
BKE_node_runtime.hh
BKE_node_socket_value.hh
BKE_node_tree_anonymous_attributes.hh

View File

@ -57,6 +57,7 @@ static size_t idp_size_table[] = {
sizeof(double), /* #IDP_DOUBLE */
0, /* #IDP_IDPARRAY (no fixed size). */
sizeof(int8_t), /* #IDP_BOOLEAN */
sizeof(int), /* #IDP_ENUM */
};
/* -------------------------------------------------------------------- */

View File

@ -67,6 +67,7 @@
#include "BKE_lib_query.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_anonymous_attributes.hh"
#include "BKE_node_tree_interface.hh"
@ -357,6 +358,7 @@ static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
case SOCK_MENU:
break;
}
}
@ -701,6 +703,10 @@ static void write_node_socket_default_value(BlendWriter *writer, const bNodeSock
case SOCK_ROTATION:
BLO_write_struct(writer, bNodeSocketValueRotation, sock->default_value);
break;
case SOCK_MENU: {
BLO_write_struct(writer, bNodeSocketValueMenu, sock->default_value);
break;
}
case SOCK_CUSTOM:
/* Custom node sockets where default_value is defined uses custom properties for storage. */
break;
@ -853,6 +859,17 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
if (node->type == GEO_NODE_BAKE) {
blender::nodes::BakeItemsAccessor::blend_write(writer, *node);
}
if (node->type == GEO_NODE_MENU_SWITCH) {
const NodeMenuSwitch &storage = *static_cast<const NodeMenuSwitch *>(node->storage);
BLO_write_struct_array(writer,
NodeEnumItem,
storage.enum_definition.items_num,
storage.enum_definition.items_array);
for (const NodeEnumItem &item : storage.enum_definition.items()) {
BLO_write_string(writer, item.name);
BLO_write_string(writer, item.description);
}
}
}
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
@ -921,6 +938,7 @@ static bool is_node_socket_supported(const bNodeSocket *sock)
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_ROTATION:
case SOCK_MENU:
return true;
}
return false;
@ -937,6 +955,18 @@ static void direct_link_node_socket(BlendDataReader *reader, bNodeSocket *sock)
BLO_read_data_address(reader, &sock->default_value);
BLO_read_data_address(reader, &sock->default_attribute_name);
sock->runtime = MEM_new<bNodeSocketRuntime>(__func__);
switch (eNodeSocketDatatype(sock->type)) {
case SOCK_MENU: {
bNodeSocketValueMenu &default_value = *sock->default_value_typed<bNodeSocketValueMenu>();
/* Clear runtime data. */
default_value.enum_items = nullptr;
default_value.runtime_flag = 0;
break;
}
default:
break;
}
}
static void direct_link_node_socket_list(BlendDataReader *reader, ListBase *socket_list)
@ -1099,6 +1129,15 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree)
blender::nodes::BakeItemsAccessor::blend_read_data(reader, *node);
break;
}
case GEO_NODE_MENU_SWITCH: {
NodeMenuSwitch &storage = *static_cast<NodeMenuSwitch *>(node->storage);
BLO_read_data_address(reader, &storage.enum_definition.items_array);
for (const NodeEnumItem &item : storage.enum_definition.items()) {
BLO_read_data_address(reader, &item.name);
BLO_read_data_address(reader, &item.description);
}
break;
}
default:
break;
@ -1826,6 +1865,7 @@ static void socket_id_user_increment(bNodeSocket *sock)
case SOCK_ROTATION:
case SOCK_INT:
case SOCK_STRING:
case SOCK_MENU:
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
@ -1872,6 +1912,7 @@ static bool socket_id_user_decrement(bNodeSocket *sock)
case SOCK_ROTATION:
case SOCK_INT:
case SOCK_STRING:
case SOCK_MENU:
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
@ -1931,6 +1972,7 @@ void nodeModifySocketType(bNodeTree *ntree,
case SOCK_COLLECTION:
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_MENU:
break;
}
}
@ -2065,6 +2107,8 @@ const char *nodeStaticSocketType(const int type, const int subtype)
return "NodeSocketTexture";
case SOCK_MATERIAL:
return "NodeSocketMaterial";
case SOCK_MENU:
return "NodeSocketMenu";
case SOCK_CUSTOM:
break;
}
@ -2146,6 +2190,8 @@ const char *nodeStaticSocketInterfaceTypeNew(const int type, const int subtype)
return "NodeTreeInterfaceSocketTexture";
case SOCK_MATERIAL:
return "NodeTreeInterfaceSocketMaterial";
case SOCK_MENU:
return "NodeTreeInterfaceSocketMenu";
case SOCK_CUSTOM:
break;
}
@ -2183,6 +2229,8 @@ const char *nodeStaticSocketLabel(const int type, const int /*subtype*/)
return "Texture";
case SOCK_MATERIAL:
return "Material";
case SOCK_MENU:
return "Menu";
case SOCK_CUSTOM:
break;
}
@ -2222,6 +2270,13 @@ static void node_socket_free(bNodeSocket *sock, const bool do_id_user)
if (do_id_user) {
socket_id_user_decrement(sock);
}
if (sock->type == SOCK_MENU) {
auto &default_value_menu = *sock->default_value_typed<bNodeSocketValueMenu>();
if (default_value_menu.enum_items) {
/* Release shared data pointer. */
default_value_menu.enum_items->remove_user_and_delete_if_last();
}
}
MEM_freeN(sock->default_value);
}
if (sock->default_attribute_name) {
@ -2551,6 +2606,14 @@ static void node_socket_copy(bNodeSocket *sock_dst, const bNodeSocket *sock_src,
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
socket_id_user_increment(sock_dst);
}
if (sock_src->type == SOCK_MENU) {
auto &default_value_menu = *sock_dst->default_value_typed<bNodeSocketValueMenu>();
if (default_value_menu.enum_items) {
/* Copy of shared data pointer. */
default_value_menu.enum_items->add_user();
}
}
}
sock_dst->default_attribute_name = static_cast<char *>(
@ -2691,6 +2754,8 @@ static void *socket_value_storage(bNodeSocket &socket)
return &socket.default_value_typed<bNodeSocketValueMaterial>()->value;
case SOCK_ROTATION:
return &socket.default_value_typed<bNodeSocketValueRotation>()->value_euler;
case SOCK_MENU:
return &socket.default_value_typed<bNodeSocketValueMenu>()->value;
case SOCK_STRING:
/* We don't want do this now! */
return nullptr;

View File

@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string.h"
#include "BLI_string_utils.hh"
#include "DNA_array_utils.hh"
#include "DNA_node_types.h"
#include "BKE_node.h"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
using blender::bke::NodeSocketValueMenuRuntimeFlag;
bool bNodeSocketValueMenu::has_conflict() const
{
return this->runtime_flag & NodeSocketValueMenuRuntimeFlag::NODE_MENU_ITEMS_CONFLICT;
}
blender::Span<NodeEnumItem> NodeEnumDefinition::items() const
{
return {this->items_array, this->items_num};
}
blender::MutableSpan<NodeEnumItem> NodeEnumDefinition::items_for_write()
{
return {this->items_array, this->items_num};
}
NodeEnumItem *NodeEnumDefinition::add_item(blender::StringRef name)
{
const int insert_index = this->items_num;
NodeEnumItem *old_items = this->items_array;
this->items_array = MEM_cnew_array<NodeEnumItem>(this->items_num + 1, __func__);
std::copy_n(old_items, insert_index, this->items_array);
NodeEnumItem &new_item = this->items_array[insert_index];
std::copy_n(old_items + insert_index + 1,
this->items_num - insert_index,
this->items_array + insert_index + 1);
new_item.identifier = this->next_identifier++;
this->set_item_name(new_item, name);
this->items_num++;
MEM_SAFE_FREE(old_items);
return &new_item;
}
static void free_enum_item(NodeEnumItem *item)
{
MEM_SAFE_FREE(item->name);
MEM_SAFE_FREE(item->description);
}
bool NodeEnumDefinition::remove_item(NodeEnumItem &item)
{
if (!this->items().contains_ptr(&item)) {
return false;
}
const int remove_index = &item - this->items().begin();
/* DNA fields are 16 bits, can't use directly. */
int items_num = this->items_num;
int active_index = this->active_index;
blender::dna::array::remove_index(
&this->items_array, &items_num, &active_index, remove_index, free_enum_item);
this->items_num = int16_t(items_num);
this->active_index = int16_t(active_index);
return true;
}
void NodeEnumDefinition::clear()
{
/* DNA fields are 16 bits, can't use directly. */
int items_num = this->items_num;
int active_index = this->active_index;
blender::dna::array::clear(&this->items_array, &items_num, &active_index, free_enum_item);
this->items_num = int16_t(items_num);
this->active_index = int16_t(active_index);
}
bool NodeEnumDefinition::move_item(uint16_t from_index, uint16_t to_index)
{
if (to_index < this->items_num) {
int items_num = this->items_num;
int active_index = this->active_index;
blender::dna::array::move_index(this->items_array, items_num, from_index, to_index);
this->items_num = int16_t(items_num);
this->active_index = int16_t(active_index);
}
return true;
}
const NodeEnumItem *NodeEnumDefinition::active_item() const
{
if (blender::IndexRange(this->items_num).contains(this->active_index)) {
return &this->items()[this->active_index];
}
return nullptr;
}
NodeEnumItem *NodeEnumDefinition::active_item()
{
if (blender::IndexRange(this->items_num).contains(this->active_index)) {
return &this->items_for_write()[this->active_index];
}
return nullptr;
}
void NodeEnumDefinition::active_item_set(NodeEnumItem *item)
{
this->active_index = this->items().contains_ptr(item) ? item - this->items_array : -1;
}
void NodeEnumDefinition::set_item_name(NodeEnumItem &item, blender::StringRef name)
{
char unique_name[MAX_NAME + 4];
STRNCPY(unique_name, name.data());
struct Args {
NodeEnumDefinition *enum_def;
const NodeEnumItem *item;
} args = {this, &item};
const char *default_name = items().is_empty() ? "Name" : items().last().name;
BLI_uniquename_cb(
[](void *arg, const char *name) {
const Args &args = *static_cast<Args *>(arg);
for (const NodeEnumItem &item : args.enum_def->items()) {
if (&item != args.item) {
if (STREQ(item.name, name)) {
return true;
}
}
}
return false;
},
&args,
default_name,
'.',
unique_name,
ARRAY_SIZE(unique_name));
MEM_SAFE_FREE(item.name);
item.name = BLI_strdup(unique_name);
}

View File

@ -6,6 +6,7 @@
#include "BKE_lib_id.hh"
#include "BKE_lib_query.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_tree_interface.hh"
#include "BLI_math_vector.h"
@ -171,6 +172,12 @@ template<> void socket_data_init_impl(bNodeSocketValueMaterial &data)
{
data.value = nullptr;
}
template<> void socket_data_init_impl(bNodeSocketValueMenu &data)
{
data.value = -1;
data.enum_items = nullptr;
data.runtime_flag = 0;
}
static void *make_socket_data(const StringRef socket_type)
{
@ -191,6 +198,13 @@ static void *make_socket_data(const StringRef socket_type)
* \{ */
template<typename T> void socket_data_free_impl(T & /*data*/, const bool /*do_id_user*/) {}
template<> void socket_data_free_impl(bNodeSocketValueMenu &dst, const bool /*do_id_user*/)
{
if (dst.enum_items) {
/* Release shared data pointer. */
dst.enum_items->remove_user_and_delete_if_last();
}
}
static void socket_data_free(bNodeTreeInterfaceSocket &socket, const bool do_id_user)
{
@ -210,6 +224,14 @@ static void socket_data_free(bNodeTreeInterfaceSocket &socket, const bool do_id_
* \{ */
template<typename T> void socket_data_copy_impl(T & /*dst*/, const T & /*src*/) {}
template<>
void socket_data_copy_impl(bNodeSocketValueMenu &dst, const bNodeSocketValueMenu & /*src*/)
{
/* Copy of shared data pointer. */
if (dst.enum_items) {
dst.enum_items->add_user();
}
}
static void socket_data_copy(bNodeTreeInterfaceSocket &dst,
const bNodeTreeInterfaceSocket &src,
@ -304,6 +326,10 @@ inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueMaterial
{
BLO_write_struct(writer, bNodeSocketValueMaterial, &data);
}
inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueMenu &data)
{
BLO_write_struct(writer, bNodeSocketValueMenu, &data);
}
static void socket_data_write(BlendWriter *writer, bNodeTreeInterfaceSocket &socket)
{
@ -323,6 +349,13 @@ template<typename T> void socket_data_read_data_impl(BlendDataReader *reader, T
{
BLO_read_data_address(reader, data);
}
template<> void socket_data_read_data_impl(BlendDataReader *reader, bNodeSocketValueMenu **data)
{
BLO_read_data_address(reader, data);
/* Clear runtime data. */
(*data)->enum_items = nullptr;
(*data)->runtime_flag = 0;
}
static void socket_data_read_data(BlendDataReader *reader, bNodeTreeInterfaceSocket &socket)
{

View File

@ -19,6 +19,7 @@
#include "BKE_image.h"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_anonymous_attributes.hh"
#include "BKE_node_tree_update.hh"
@ -485,6 +486,9 @@ class NodeTreeMainUpdater {
this->propagate_runtime_flags(ntree);
if (ntree.type == NTREE_GEOMETRY) {
if (this->propagate_enum_definitions(ntree)) {
result.interface_changed = true;
}
if (node_field_inferencing::update_field_inferencing(ntree)) {
result.interface_changed = true;
}
@ -799,6 +803,209 @@ class NodeTreeMainUpdater {
}
}
bool propagate_enum_definitions(bNodeTree &ntree)
{
ntree.ensure_interface_cache();
/* Propagation from right to left to determine which enum
* definition to use for menu sockets. */
for (bNode *node : ntree.toposort_right_to_left()) {
const bool node_updated = this->should_update_individual_node(ntree, *node);
if (node->typeinfo->type == GEO_NODE_MENU_SWITCH) {
/* Generate new enum items when the node has changed, otherwise keep existing items. */
if (node_updated) {
const NodeMenuSwitch &storage = *static_cast<NodeMenuSwitch *>(node->storage);
const RuntimeNodeEnumItems *enum_items = this->create_runtime_enum_items(
storage.enum_definition);
bNodeSocket &input = *node->input_sockets()[0];
BLI_assert(input.is_available() && input.type == SOCK_MENU);
this->set_enum_ptr(*input.default_value_typed<bNodeSocketValueMenu>(), enum_items);
}
continue;
}
else {
/* Clear current enum references. */
for (bNodeSocket *socket : node->input_sockets()) {
if (socket->is_available() && socket->type == SOCK_MENU) {
clear_enum_reference(*socket);
}
}
for (bNodeSocket *socket : node->output_sockets()) {
if (socket->is_available() && socket->type == SOCK_MENU) {
clear_enum_reference(*socket);
}
}
}
/* Propagate enum references from output links. */
for (bNodeSocket *output : node->output_sockets()) {
if (output->is_available() && output->type == SOCK_MENU) {
for (const bNodeSocket *input : output->directly_linked_sockets()) {
this->update_socket_enum_definition(
*output->default_value_typed<bNodeSocketValueMenu>(),
*input->default_value_typed<bNodeSocketValueMenu>());
}
}
}
if (node->is_group()) {
/* Node groups expose internal enum definitions. */
if (node->id == nullptr) {
continue;
}
const bNodeTree *group_tree = reinterpret_cast<bNodeTree *>(node->id);
group_tree->ensure_interface_cache();
for (const int socket_i : group_tree->interface_inputs().index_range()) {
bNodeSocket &input = *node->input_sockets()[socket_i];
const bNodeTreeInterfaceSocket &iosocket = *group_tree->interface_inputs()[socket_i];
BLI_assert(STREQ(input.identifier, iosocket.identifier));
if (input.is_available() && input.type == SOCK_MENU) {
BLI_assert(STREQ(iosocket.socket_type, "NodeSocketMenu"));
this->update_socket_enum_definition(
*input.default_value_typed<bNodeSocketValueMenu>(),
*static_cast<bNodeSocketValueMenu *>(iosocket.socket_data));
}
}
}
else if (node->type == GEO_NODE_MENU_SWITCH) {
/* First input is always the node's own menu, propagate only to the enum case inputs. */
const bNodeSocket *output = node->output_sockets().first();
for (bNodeSocket *input : node->input_sockets().drop_front(1)) {
if (input->is_available() && input->type == SOCK_MENU) {
this->update_socket_enum_definition(
*input->default_value_typed<bNodeSocketValueMenu>(),
*output->default_value_typed<bNodeSocketValueMenu>());
}
}
}
else {
/* Propagate over internal relations. */
/* XXX Placeholder implementation just propagates all outputs
* to all inputs for built-in nodes This could perhaps use
* input/output relations to handle propagation generically? */
for (bNodeSocket *input : node->input_sockets()) {
if (input->is_available() && input->type == SOCK_MENU) {
for (const bNodeSocket *output : node->output_sockets()) {
if (output->is_available() && output->type == SOCK_MENU) {
this->update_socket_enum_definition(
*input->default_value_typed<bNodeSocketValueMenu>(),
*output->default_value_typed<bNodeSocketValueMenu>());
}
}
}
}
}
}
/* Build list of new enum items for the node tree interface. */
Vector<bNodeSocketValueMenu> interface_enum_items(ntree.interface_inputs().size(), {0});
for (const bNode *group_input_node : ntree.group_input_nodes()) {
for (const int socket_i : ntree.interface_inputs().index_range()) {
const bNodeSocket &output = *group_input_node->output_sockets()[socket_i];
if (output.is_available() && output.type == SOCK_MENU) {
this->update_socket_enum_definition(interface_enum_items[socket_i],
*output.default_value_typed<bNodeSocketValueMenu>());
}
}
}
/* Move enum items to the interface and detect if anything changed. */
bool changed = false;
for (const int socket_i : ntree.interface_inputs().index_range()) {
bNodeTreeInterfaceSocket &iosocket = *ntree.interface_inputs()[socket_i];
if (STREQ(iosocket.socket_type, "NodeSocketMenu")) {
bNodeSocketValueMenu &dst = *static_cast<bNodeSocketValueMenu *>(iosocket.socket_data);
const bNodeSocketValueMenu &src = interface_enum_items[socket_i];
if (dst.enum_items != src.enum_items || dst.has_conflict() != src.has_conflict()) {
changed = true;
if (dst.enum_items) {
dst.enum_items->remove_user_and_delete_if_last();
}
/* Items are moved, no need to change user count. */
dst.enum_items = src.enum_items;
SET_FLAG_FROM_TEST(dst.runtime_flag, src.has_conflict(), NODE_MENU_ITEMS_CONFLICT);
}
}
}
return changed;
}
/**
* Make a runtime copy of the DNA enum items.
* The runtime items list is shared by sockets.
*/
const RuntimeNodeEnumItems *create_runtime_enum_items(const NodeEnumDefinition &enum_def)
{
RuntimeNodeEnumItems *enum_items = new RuntimeNodeEnumItems();
enum_items->items.reinitialize(enum_def.items_num);
for (const int i : enum_def.items().index_range()) {
const NodeEnumItem &src = enum_def.items()[i];
RuntimeNodeEnumItem &dst = enum_items->items[i];
dst.identifier = src.identifier;
dst.name = src.name ? src.name : "";
dst.description = src.description ? src.description : "";
}
return enum_items;
}
void clear_enum_reference(bNodeSocket &socket)
{
BLI_assert(socket.is_available() && socket.type == SOCK_MENU);
bNodeSocketValueMenu &default_value = *socket.default_value_typed<bNodeSocketValueMenu>();
this->reset_enum_ptr(default_value);
default_value.runtime_flag &= ~NODE_MENU_ITEMS_CONFLICT;
}
void update_socket_enum_definition(bNodeSocketValueMenu &dst, const bNodeSocketValueMenu &src)
{
if (dst.has_conflict()) {
/* Target enum already has a conflict. */
BLI_assert(dst.enum_items == nullptr);
return;
}
if (src.has_conflict()) {
/* Target conflict if any source enum has a conflict. */
this->reset_enum_ptr(dst);
dst.runtime_flag |= NODE_MENU_ITEMS_CONFLICT;
}
else if (!dst.enum_items) {
/* First connection, set the reference. */
this->set_enum_ptr(dst, src.enum_items);
}
else if (src.enum_items && dst.enum_items != src.enum_items) {
/* Error if enum ref does not match other connections. */
this->reset_enum_ptr(dst);
dst.runtime_flag |= NODE_MENU_ITEMS_CONFLICT;
}
}
void reset_enum_ptr(bNodeSocketValueMenu &dst)
{
if (dst.enum_items) {
dst.enum_items->remove_user_and_delete_if_last();
dst.enum_items = nullptr;
}
}
void set_enum_ptr(bNodeSocketValueMenu &dst, const RuntimeNodeEnumItems *enum_items)
{
if (dst.enum_items) {
dst.enum_items->remove_user_and_delete_if_last();
dst.enum_items = nullptr;
}
if (enum_items) {
enum_items->add_user();
dst.enum_items = enum_items;
}
}
void update_link_validation(bNodeTree &ntree)
{
const Span<const bNode *> toposort = ntree.toposort_left_to_right();
@ -810,12 +1017,24 @@ class NodeTreeMainUpdater {
toposort_indices[node.index()] = i;
}
/* Tests if enum references are undefined. */
const auto is_invalid_enum_ref = [](const bNodeSocket &socket) -> bool {
if (socket.type == SOCK_MENU) {
return socket.default_value_typed<bNodeSocketValueMenu>()->enum_items == nullptr;
}
return false;
};
LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
link->flag |= NODE_LINK_VALID;
if (!link->fromsock->is_available() || !link->tosock->is_available()) {
link->flag &= ~NODE_LINK_VALID;
continue;
}
if (is_invalid_enum_ref(*link->fromsock) || is_invalid_enum_ref(*link->tosock)) {
link->flag &= ~NODE_LINK_VALID;
continue;
}
const bNode &from_node = *link->fromnode;
const bNode &to_node = *link->tonode;
if (toposort_indices[from_node.index()] > toposort_indices[to_node.index()]) {
@ -837,8 +1056,8 @@ class NodeTreeMainUpdater {
{
tree.ensure_topology_cache();
/* Compute a hash that represents the node topology connected to the output. This always has to
* be updated even if it is not used to detect changes right now. Otherwise
/* Compute a hash that represents the node topology connected to the output. This always has
* to be updated even if it is not used to detect changes right now. Otherwise
* #btree.runtime.output_topology_hash will go out of date. */
const Vector<const bNodeSocket *> tree_output_sockets = this->find_output_sockets(tree);
const uint32_t old_topology_hash = tree.runtime->output_topology_hash;
@ -852,8 +1071,8 @@ class NodeTreeMainUpdater {
* be used without causing updates all the time currently. In the future we could try to
* handle other drivers better as well.
* Note that this optimization only works in practice when the depsgraph didn't also get a
* copy-on-write tag for the node tree (which happens when changing node properties). It does
* work in a few situations like adding reroutes and duplicating nodes though. */
* copy-on-write tag for the node tree (which happens when changing node properties). It
* does work in a few situations like adding reroutes and duplicating nodes though. */
LISTBASE_FOREACH (const FCurve *, fcurve, &adt->drivers) {
const ChannelDriver *driver = fcurve->driver;
const StringRef expression = driver->expression;
@ -876,7 +1095,8 @@ class NodeTreeMainUpdater {
return true;
}
/* The topology hash can only be used when only topology-changing operations have been done. */
/* The topology hash can only be used when only topology-changing operations have been done.
*/
if (tree.runtime->changed_flag ==
(tree.runtime->changed_flag & (NTREE_CHANGED_LINK | NTREE_CHANGED_REMOVED_NODE)))
{
@ -929,15 +1149,15 @@ class NodeTreeMainUpdater {
}
/**
* Computes a hash that changes when the node tree topology connected to an output node changes.
* Adding reroutes does not have an effect on the hash.
* Computes a hash that changes when the node tree topology connected to an output node
* changes. Adding reroutes does not have an effect on the hash.
*/
uint32_t get_combined_socket_topology_hash(const bNodeTree &tree,
Span<const bNodeSocket *> sockets)
{
if (tree.has_available_link_cycle()) {
/* Return dummy value when the link has any cycles. The algorithm below could be improved to
* handle cycles more gracefully. */
/* Return dummy value when the link has any cycles. The algorithm below could be improved
* to handle cycles more gracefully. */
return 0;
}
Array<uint32_t> hashes = this->get_socket_topology_hashes(tree, sockets);
@ -1121,8 +1341,8 @@ class NodeTreeMainUpdater {
}
}
}
/* The Normal node has a special case, because the value stored in the first output socket
* is used as input in the node. */
/* The Normal node has a special case, because the value stored in the first output
* socket is used as input in the node. */
if (node.type == SH_NODE_NORMAL && socket.index() == 1) {
BLI_assert(STREQ(socket.name, "Dot"));
const bNodeSocket &normal_output = node.output_socket(0);

View File

@ -23,6 +23,7 @@
#include "BKE_image.h"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_scene.h"
@ -1199,6 +1200,7 @@ static const float std_node_socket_colors[][4] = {
{0.62, 0.31, 0.64, 1.0}, /* SOCK_TEXTURE */
{0.92, 0.46, 0.51, 1.0}, /* SOCK_MATERIAL */
{0.65, 0.39, 0.78, 1.0}, /* SOCK_ROTATION */
{0.40, 0.40, 0.40, 1.0}, /* SOCK_MENU */
};
/* Callback for colors that does not depend on the socket pointer argument to get the type. */
@ -1233,6 +1235,7 @@ static const SocketColorFn std_node_socket_color_funcs[] = {
std_node_socket_color_fn<SOCK_TEXTURE>,
std_node_socket_color_fn<SOCK_MATERIAL>,
std_node_socket_color_fn<SOCK_ROTATION>,
std_node_socket_color_fn<SOCK_MENU>,
};
/* draw function for file output node sockets,
@ -1371,6 +1374,27 @@ static void std_node_socket_draw(
break;
}
case SOCK_MENU: {
const bNodeSocketValueMenu *default_value =
sock->default_value_typed<bNodeSocketValueMenu>();
if (default_value->enum_items) {
if (default_value->enum_items->items.is_empty()) {
uiLayout *row = uiLayoutSplit(layout, 0.4f, false);
uiItemL(row, text, ICON_NONE);
uiItemL(row, "No Items", ICON_NONE);
}
else {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
}
else if (default_value->has_conflict()) {
uiItemL(layout, IFACE_("Menu Error"), ICON_ERROR);
}
else {
uiItemL(layout, IFACE_("Menu Undefined"), ICON_QUESTION);
}
break;
}
case SOCK_OBJECT: {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, ICON_NONE);
break;
@ -1498,6 +1522,10 @@ static void std_node_socket_interface_draw(ID *id,
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
break;
}
case SOCK_MENU: {
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
break;
}
case SOCK_SHADER:
case SOCK_GEOMETRY:
break;

View File

@ -434,7 +434,8 @@ static bool socket_can_be_viewed(const bNode &node, const bNodeSocket &socket)
SOCK_INT,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_RGBA);
SOCK_RGBA,
SOCK_MENU);
}
/**
@ -2245,6 +2246,7 @@ static int get_main_socket_priority(const bNodeSocket *socket)
case SOCK_COLLECTION:
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_MENU:
return 6;
}
return -1;

View File

@ -394,6 +394,9 @@ static Vector<NodeLinkItem> ui_node_link_items(NodeLinkArg *arg,
else if (dynamic_cast<const decl::String *>(&socket_decl)) {
item.socket_type = SOCK_STRING;
}
else if (dynamic_cast<const decl::Menu *>(&socket_decl)) {
item.socket_type = SOCK_MENU;
}
else if (dynamic_cast<const decl::Image *>(&socket_decl)) {
item.socket_type = SOCK_IMAGE;
}
@ -994,6 +997,9 @@ static void ui_node_draw_input(uiLayout &layout,
split_wrapper.decorate_column, &inputptr, "default_value", RNA_NO_INDEX);
break;
}
case SOCK_MENU:
uiItemL(sub, RPT_("Unsupported Menu Socket"), ICON_NONE);
break;
case SOCK_CUSTOM:
input.typeinfo->draw(&C, sub, &inputptr, &nodeptr, input.name);
break;

View File

@ -13,6 +13,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_index_range.hh"
#include "BLI_utildefines.h"
namespace blender::dna::array {

View File

@ -40,17 +40,22 @@ namespace blender::bke {
class bNodeTreeZones;
class bNodeTreeZone;
} // namespace blender::bke
namespace blender::bke {
struct RuntimeNodeEnumItems;
} // namespace blender::bke
using NodeDeclarationHandle = blender::nodes::NodeDeclaration;
using SocketDeclarationHandle = blender::nodes::SocketDeclaration;
using bNodeTreeRuntimeHandle = blender::bke::bNodeTreeRuntime;
using bNodeRuntimeHandle = blender::bke::bNodeRuntime;
using bNodeSocketRuntimeHandle = blender::bke::bNodeSocketRuntime;
using RuntimeNodeEnumItemsHandle = blender::bke::RuntimeNodeEnumItems;
#else
typedef struct NodeDeclarationHandle NodeDeclarationHandle;
typedef struct SocketDeclarationHandle SocketDeclarationHandle;
typedef struct bNodeTreeRuntimeHandle bNodeTreeRuntimeHandle;
typedef struct bNodeRuntimeHandle bNodeRuntimeHandle;
typedef struct bNodeSocketRuntimeHandle bNodeSocketRuntimeHandle;
typedef struct RuntimeNodeEnumItemsHandle RuntimeNodeEnumItemsHandle;
#endif
struct AnimData;
@ -69,6 +74,7 @@ struct bNodeLink;
struct bNodePreview;
struct bNodeType;
struct bNode;
struct NodeEnumDefinition;
#define NODE_MAXSTR 64
@ -256,6 +262,7 @@ typedef enum eNodeSocketDatatype {
SOCK_TEXTURE = 12,
SOCK_MATERIAL = 13,
SOCK_ROTATION = 14,
SOCK_MENU = 15,
} eNodeSocketDatatype;
/** Socket shape. */
@ -919,6 +926,19 @@ typedef struct bNodeSocketValueMaterial {
struct Material *value;
} bNodeSocketValueMaterial;
typedef struct bNodeSocketValueMenu {
/* Default input enum identifier. */
int value;
/* #NodeSocketValueMenuRuntimeFlag */
int runtime_flag;
/* Immutable runtime enum definition. */
const RuntimeNodeEnumItemsHandle *enum_items;
#ifdef __cplusplus
bool has_conflict() const;
#endif
} bNodeSocketValueMenu;
typedef struct GeometryNodeAssetTraits {
int flag;
} GeometryNodeAssetTraits;
@ -1613,10 +1633,50 @@ typedef struct NodeGeometryMeshLine {
} NodeGeometryMeshLine;
typedef struct NodeSwitch {
/** #NodeSwitch. */
/** #eNodeSocketDatatype. */
uint8_t input_type;
} NodeSwitch;
typedef struct NodeEnumItem {
char *name;
char *description;
/* Immutable unique identifier. */
int32_t identifier;
char _pad[4];
} NodeEnumItem;
typedef struct NodeEnumDefinition {
/* User-defined enum items owned and managed by this node. */
NodeEnumItem *items_array;
int16_t items_num;
int16_t active_index;
uint32_t next_identifier;
#ifdef __cplusplus
blender::Span<NodeEnumItem> items() const;
blender::MutableSpan<NodeEnumItem> items_for_write();
NodeEnumItem *add_item(blender::StringRef name);
bool remove_item(NodeEnumItem &item);
void clear();
bool move_item(uint16_t from_index, uint16_t to_index);
const NodeEnumItem *active_item() const;
NodeEnumItem *active_item();
void active_item_set(NodeEnumItem *item);
void set_item_name(NodeEnumItem &item, blender::StringRef name);
#endif
} NodeEnumDefinition;
typedef struct NodeMenuSwitch {
NodeEnumDefinition enum_definition;
/** #eNodeSocketDatatype. */
uint8_t data_type;
char _pad[7];
} NodeMenuSwitch;
typedef struct NodeGeometryCurveSplineType {
/** #GeometryNodeSplineType. */
uint8_t spline_type;

View File

@ -17,6 +17,9 @@ struct bNodeType;
struct PointerRNA;
struct PropertyRNA;
struct bContext;
namespace blender::bke {
struct RuntimeNodeEnumItems;
}
/* Types */
#define DEF_ENUM(id) extern const EnumPropertyItem id[];
@ -121,3 +124,6 @@ const EnumPropertyItem *RNA_mask_local_itemf(bContext *C,
/* Non confirming, utility function. */
const EnumPropertyItem *RNA_enum_node_tree_types_itemf_impl(bContext *C, bool *r_free);
const EnumPropertyItem *RNA_node_enum_definition_itemf(
const blender::bke::RuntimeNodeEnumItems &enum_items, bool *r_free);

View File

@ -34,6 +34,7 @@ const EnumPropertyItem rna_enum_node_socket_type_items[] = {
{SOCK_COLLECTION, "COLLECTION", 0, "Collection", ""},
{SOCK_TEXTURE, "TEXTURE", 0, "Texture", ""},
{SOCK_MATERIAL, "MATERIAL", 0, "Material", ""},
{SOCK_MENU, "MENU", 0, "Menu", ""},
{0, nullptr, 0, nullptr, nullptr},
};
@ -42,6 +43,7 @@ const EnumPropertyItem rna_enum_node_socket_type_items[] = {
# include "DNA_material_types.h"
# include "BKE_node.h"
# include "BKE_node_enum.hh"
# include "BKE_node_runtime.hh"
# include "BKE_node_tree_update.hh"
@ -414,6 +416,56 @@ bool rna_NodeSocketMaterial_default_value_poll(PointerRNA * /*ptr*/, PointerRNA
return ma->gp_style == nullptr;
}
const EnumPropertyItem *RNA_node_enum_definition_itemf(
const blender::bke::RuntimeNodeEnumItems &enum_items, bool *r_free)
{
EnumPropertyItem tmp = {0};
EnumPropertyItem *result = nullptr;
int totitem = 0;
for (const blender::bke::RuntimeNodeEnumItem &item : enum_items.items) {
tmp.value = item.identifier;
/* Item name is unique and used as the RNA identitifier as well.
* The integer value is persistent and unique and should be used
* when storing the enum value. */
tmp.identifier = item.name.c_str();
/* TODO support icons in enum definition. */
tmp.icon = ICON_NONE;
tmp.name = item.name.c_str();
tmp.description = item.description.c_str();
RNA_enum_item_add(&result, &totitem, &tmp);
}
if (totitem == 0) {
*r_free = false;
return rna_enum_dummy_NULL_items;
}
RNA_enum_item_end(&result, &totitem);
*r_free = true;
return result;
}
const EnumPropertyItem *RNA_node_socket_menu_itemf(bContext * /*C*/,
PointerRNA *ptr,
PropertyRNA * /*prop*/,
bool *r_free)
{
const bNodeSocket *socket = static_cast<bNodeSocket *>(ptr->data);
if (!socket) {
*r_free = false;
return rna_enum_dummy_NULL_items;
}
const bNodeSocketValueMenu *data = static_cast<bNodeSocketValueMenu *>(socket->default_value);
if (!data->enum_items) {
*r_free = false;
return rna_enum_dummy_NULL_items;
}
return RNA_node_enum_definition_itemf(*data->enum_items, r_free);
}
#else
static void rna_def_node_socket(BlenderRNA *brna)
@ -1129,6 +1181,52 @@ static void rna_def_node_socket_interface_string(BlenderRNA *brna, const char *i
rna_def_node_tree_interface_socket_builtin(srna);
}
static void rna_def_node_socket_menu(BlenderRNA *brna, const char *identifier)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, identifier, "NodeSocketStandard");
RNA_def_struct_ui_text(srna, "Menu Node Socket", "Menu socket of a node");
RNA_def_struct_sdna(srna, "bNodeSocket");
RNA_def_struct_sdna_from(srna, "bNodeSocketValueMenu", "default_value");
prop = RNA_def_property(srna, "default_value", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "value");
RNA_def_property_enum_items(prop, rna_enum_dummy_NULL_items);
RNA_def_property_enum_funcs(prop, nullptr, nullptr, "RNA_node_socket_menu_itemf");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketStandard_value_update");
RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
RNA_def_struct_sdna_from(srna, "bNodeSocket", nullptr);
}
static void rna_def_node_socket_interface_menu(BlenderRNA *brna, const char *identifier)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, identifier, "NodeTreeInterfaceSocket");
RNA_def_struct_ui_text(srna, "Menu Node Socket Interface", "Menu socket of a node");
RNA_def_struct_sdna(srna, "bNodeTreeInterfaceSocket");
RNA_def_struct_sdna_from(srna, "bNodeSocketValueMenu", "socket_data");
prop = RNA_def_property(srna, "default_value", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "value");
RNA_def_property_enum_items(prop, rna_enum_dummy_NULL_items);
RNA_def_property_enum_funcs(prop, nullptr, nullptr, "RNA_node_tree_interface_socket_menu_itemf");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceSocket_value_update");
RNA_def_struct_sdna_from(srna, "bNodeTreeInterfaceSocket", nullptr);
rna_def_node_tree_interface_socket_builtin(srna);
}
static void rna_def_node_socket_shader(BlenderRNA *brna, const char *identifier)
{
StructRNA *srna;
@ -1458,6 +1556,7 @@ static const bNodeSocketStaticTypeInfo node_socket_subtypes[] = {
{"NodeSocketCollection", "NodeTreeInterfaceSocketCollection", SOCK_COLLECTION, PROP_NONE},
{"NodeSocketTexture", "NodeTreeInterfaceSocketTexture", SOCK_TEXTURE, PROP_NONE},
{"NodeSocketMaterial", "NodeTreeInterfaceSocketMaterial", SOCK_MATERIAL, PROP_NONE},
{"NodeSocketMenu", "NodeTreeInterfaceSocketMenu", SOCK_MENU, PROP_NONE},
};
static void rna_def_node_socket_subtypes(BlenderRNA *brna)
@ -1508,6 +1607,9 @@ static void rna_def_node_socket_subtypes(BlenderRNA *brna)
case SOCK_MATERIAL:
rna_def_node_socket_material(brna, identifier);
break;
case SOCK_MENU:
rna_def_node_socket_menu(brna, identifier);
break;
case SOCK_CUSTOM:
break;
@ -1547,6 +1649,9 @@ void rna_def_node_socket_interface_subtypes(BlenderRNA *brna)
case SOCK_STRING:
rna_def_node_socket_interface_string(brna, identifier);
break;
case SOCK_MENU:
rna_def_node_socket_interface_menu(brna, identifier);
break;
case SOCK_SHADER:
rna_def_node_socket_interface_shader(brna, identifier);
break;

View File

@ -30,6 +30,7 @@ static const EnumPropertyItem node_tree_interface_socket_in_out_items[] = {
# include "BKE_attribute.hh"
# include "BKE_node.h"
# include "BKE_node_enum.hh"
# include "BKE_node_runtime.hh"
# include "BKE_node_tree_interface.hh"
# include "BKE_node_tree_update.hh"
@ -883,6 +884,24 @@ static int rna_NodeTreeInterface_items_lookup_string(PointerRNA *ptr,
return false;
}
const EnumPropertyItem *RNA_node_tree_interface_socket_menu_itemf(bContext * /*C*/,
PointerRNA *ptr,
PropertyRNA * /*prop*/,
bool *r_free)
{
const bNodeTreeInterfaceSocket *socket = static_cast<bNodeTreeInterfaceSocket *>(ptr->data);
if (!socket) {
*r_free = false;
return rna_enum_dummy_NULL_items;
}
const bNodeSocketValueMenu *data = static_cast<bNodeSocketValueMenu *>(socket->socket_data);
if (!data->enum_items) {
*r_free = false;
return rna_enum_dummy_NULL_items;
}
return RNA_node_enum_definition_itemf(*data->enum_items, r_free);
}
#else
static void rna_def_node_interface_item(BlenderRNA *brna)

View File

@ -78,6 +78,7 @@ const EnumPropertyItem rna_enum_node_socket_data_type_items[] = {
{SOCK_VECTOR, "VECTOR", 0, "Vector", ""},
{SOCK_ROTATION, "ROTATION", 0, "Rotation", ""},
{SOCK_STRING, "STRING", 0, "String", ""},
{SOCK_MENU, "MENU", 0, "Menu", ""},
{SOCK_RGBA, "RGBA", 0, "Color", ""},
{SOCK_OBJECT, "OBJECT", 0, "Object", ""},
{SOCK_IMAGE, "IMAGE", 0, "Image", ""},
@ -3916,6 +3917,133 @@ static const EnumPropertyItem *rna_NodeConvertColorSpace_color_space_itemf(bCont
return items;
}
static bNode *find_node_by_enum_definition(bNodeTree *ntree, const NodeEnumDefinition *enum_def)
{
ntree->ensure_topology_cache();
for (bNode *node : ntree->nodes_by_type("GeometryNodeMenuSwitch")) {
NodeMenuSwitch *storage = static_cast<NodeMenuSwitch *>(node->storage);
if (&storage->enum_definition == enum_def) {
return node;
}
}
return nullptr;
}
static bNode *find_node_by_enum_item(bNodeTree *ntree, const NodeEnumItem *item)
{
ntree->ensure_topology_cache();
for (bNode *node : ntree->nodes_by_type("GeometryNodeMenuSwitch")) {
NodeMenuSwitch *storage = static_cast<NodeMenuSwitch *>(node->storage);
if (storage->enum_definition.items().contains_ptr(item)) {
return node;
}
}
return nullptr;
}
static NodeEnumDefinition *find_enum_definition_by_item(bNodeTree *ntree, const NodeEnumItem *item)
{
ntree->ensure_topology_cache();
for (bNode *node : ntree->nodes_by_type("GeometryNodeMenuSwitch")) {
NodeMenuSwitch *storage = static_cast<NodeMenuSwitch *>(node->storage);
if (storage->enum_definition.items().contains_ptr(item)) {
return &storage->enum_definition;
}
}
return nullptr;
}
/* Tag the node owning the enum definition to ensure propagation of the enum. */
static void rna_NodeEnumDefinition_tag_changed(bNodeTree *ntree, NodeEnumDefinition *enum_def)
{
bNode *node = find_node_by_enum_definition(ntree, enum_def);
BLI_assert(node != nullptr);
BKE_ntree_update_tag_node_property(ntree, node);
}
static void rna_NodeEnumItem_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr)
{
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
const NodeEnumItem *item = static_cast<NodeEnumItem *>(ptr->data);
BLI_assert(find_node_by_enum_item(ntree, item) != nullptr);
ED_node_tree_propagate_change(nullptr, bmain, ntree);
}
static void rna_NodeEnumItem_name_set(PointerRNA *ptr, const char *value)
{
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
NodeEnumItem *item = static_cast<NodeEnumItem *>(ptr->data);
NodeEnumDefinition *enum_def = find_enum_definition_by_item(ntree, item);
enum_def->set_item_name(*item, value);
rna_NodeEnumDefinition_tag_changed(ntree, enum_def);
}
static NodeEnumItem *rna_NodeEnumDefinition_new(
ID *id, NodeEnumDefinition *enum_def, Main *bmain, ReportList *reports, const char *name)
{
NodeEnumItem *item = enum_def->add_item(name);
if (item == nullptr) {
BKE_report(reports, RPT_ERROR, "Unable to create enum item");
}
else {
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id);
rna_NodeEnumDefinition_tag_changed(ntree, enum_def);
ED_node_tree_propagate_change(nullptr, bmain, ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
return item;
}
static void rna_NodeEnumDefinition_remove(
ID *id, NodeEnumDefinition *enum_def, Main *bmain, ReportList *reports, NodeEnumItem *item)
{
if (!enum_def->remove_item(*item)) {
BKE_reportf(reports, RPT_ERROR, "Unable to remove item '%s' from enum definition", item->name);
}
else {
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id);
rna_NodeEnumDefinition_tag_changed(ntree, enum_def);
ED_node_tree_propagate_change(nullptr, bmain, ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
}
static void rna_NodeEnumDefinition_clear(ID *id, NodeEnumDefinition *enum_def, Main *bmain)
{
enum_def->clear();
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id);
rna_NodeEnumDefinition_tag_changed(ntree, enum_def);
ED_node_tree_propagate_change(nullptr, bmain, ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
static void rna_NodeEnumDefinition_move(
ID *id, NodeEnumDefinition *enum_def, Main *bmain, int from_index, int to_index)
{
enum_def->move_item(from_index, to_index);
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id);
rna_NodeEnumDefinition_tag_changed(ntree, enum_def);
ED_node_tree_propagate_change(nullptr, bmain, ntree);
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
}
static PointerRNA rna_NodeEnumDefinition_active_item_get(PointerRNA *ptr)
{
NodeEnumDefinition *enum_def = static_cast<NodeEnumDefinition *>(ptr->data);
NodeEnumItem *item = enum_def->active_item();
PointerRNA r_ptr = RNA_pointer_create(ptr->owner_id, &RNA_NodeEnumItem, item);
return r_ptr;
}
static void rna_NodeEnumDefinition_active_item_set(PointerRNA *ptr,
PointerRNA value,
ReportList * /*reports*/)
{
NodeEnumDefinition *enum_def = static_cast<NodeEnumDefinition *>(ptr->data);
NodeEnumItem *item = static_cast<NodeEnumItem *>(value.data);
enum_def->active_item_set(item);
}
#else
static const EnumPropertyItem prop_image_layer_items[] = {
@ -9520,6 +9648,113 @@ static void def_geo_string_to_curves(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
static void rna_def_node_enum_item(BlenderRNA *brna)
{
PropertyRNA *prop;
StructRNA *srna = RNA_def_struct(brna, "NodeEnumItem", nullptr);
RNA_def_struct_ui_text(srna, "Enum Item", "");
RNA_def_struct_sdna(srna, "NodeEnumItem");
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_NodeEnumItem_name_set");
RNA_def_property_ui_text(prop, "Name", "");
RNA_def_struct_name_property(srna, prop);
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeEnumItem_update");
prop = RNA_def_property(srna, "description", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, nullptr, "description");
RNA_def_property_ui_text(prop, "Description", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeEnumItem_update");
}
static void rna_def_node_enum_definition_items(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *parm;
FunctionRNA *func;
srna = RNA_def_struct(brna, "NodeEnumDefinitionItems", nullptr);
RNA_def_struct_sdna(srna, "NodeEnumDefinition");
RNA_def_struct_ui_text(
srna, "Enum Definition Items", "Collection of items that make up an enum");
func = RNA_def_function(srna, "new", "rna_NodeEnumDefinition_new");
RNA_def_function_ui_description(func, "Add an a new enum item");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
parm = RNA_def_string(func, "name", nullptr, MAX_NAME, "Name", "");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
/* return value */
parm = RNA_def_pointer(func, "item", "NodeEnumItem", "Item", "New item");
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "remove", "rna_NodeEnumDefinition_remove");
RNA_def_function_ui_description(func, "Remove an item from this enum");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
parm = RNA_def_pointer(func, "item", "NodeEnumItem", "Item", "The item to remove");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
func = RNA_def_function(srna, "clear", "rna_NodeEnumDefinition_clear");
RNA_def_function_ui_description(func, "Remove all items from this enum");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
func = RNA_def_function(srna, "move", "rna_NodeEnumDefinition_move");
RNA_def_function_ui_description(func, "Move an item to another position");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
parm = RNA_def_int(
func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the item 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 item", 0, 10000);
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
}
static void rna_def_node_enum_definition(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "NodeEnumDefinition", nullptr);
RNA_def_struct_sdna(srna, "NodeEnumDefinition");
RNA_def_struct_ui_text(srna, "Enum Definition", "Definition of an enumeration for nodes");
prop = RNA_def_property(srna, "enum_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "items_array", "items_num");
RNA_def_property_struct_type(prop, "NodeEnumItem");
RNA_def_property_ui_text(prop, "Items", "");
RNA_def_property_srna(prop, "NodeEnumDefinitionItems");
prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "active_index");
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, NC_NODE, nullptr);
prop = RNA_def_property(srna, "active_item", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "NodeEnumItem");
RNA_def_property_pointer_funcs(prop,
"rna_NodeEnumDefinition_active_item_get",
"rna_NodeEnumDefinition_active_item_set",
nullptr,
nullptr);
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Active Item", "Active item");
RNA_def_property_update(prop, NC_NODE, nullptr);
}
static void def_geo_menu_switch(StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeMenuSwitch", "storage");
prop = RNA_def_property(srna, "enum_definition", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, nullptr, "enum_definition");
RNA_def_property_struct_type(prop, "NodeEnumDefinition");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Enum Definition", "Definition of enum items");
}
static void rna_def_shader_node(BlenderRNA *brna)
{
StructRNA *srna;
@ -10663,6 +10898,9 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_geo_repeat_output_items(brna);
rna_def_geo_index_switch_items(brna);
rna_def_bake_items(brna);
rna_def_node_enum_item(brna);
rna_def_node_enum_definition_items(brna);
rna_def_node_enum_definition(brna);
rna_def_node_instance_hash(brna);
}

View File

@ -398,6 +398,9 @@ std::unique_ptr<LazyFunction> get_index_switch_node_lazy_function(
const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info);
std::unique_ptr<LazyFunction> get_bake_lazy_function(
const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info);
std::unique_ptr<LazyFunction> get_menu_switch_node_lazy_function(
const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info);
std::unique_ptr<LazyFunction> get_menu_switch_node_socket_usage_lazy_function(const bNode &node);
/**
* Outputs the default value of each output socket that has not been output yet. This needs the

View File

@ -179,6 +179,27 @@ class StringBuilder : public SocketDeclarationBuilder<String> {
StringBuilder &default_value(const std::string value);
};
class MenuBuilder;
class Menu : public SocketDeclaration {
public:
int32_t default_value;
friend MenuBuilder;
using Builder = MenuBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
};
class MenuBuilder : public SocketDeclarationBuilder<Menu> {
public:
MenuBuilder &default_value(int32_t value);
};
class IDSocketDeclaration : public SocketDeclaration {
public:
const char *idname;
@ -488,6 +509,23 @@ inline StringBuilder &StringBuilder::default_value(std::string value)
/** \} */
/* -------------------------------------------------------------------- */
/** \name #MenuBuilder Inline Methods
* \{ */
inline MenuBuilder &MenuBuilder::default_value(const int32_t value)
{
if (decl_in_) {
decl_in_->default_value = value;
}
if (decl_out_) {
decl_out_->default_value = value;
}
return *this;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #RotationBuilder Inline Methods
* \{ */

View File

@ -372,6 +372,7 @@ DefNode(GeometryNode, GEO_NODE_INSTANCES_TO_POINTS, 0, "INSTANCES_TO_POINTS",Ins
DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "Retrieve whether the nodes are being evaluated for the viewport rather than the final render")
DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "Merge separately generated geometries into a single one")
DefNode(GeometryNode, GEO_NODE_MATERIAL_SELECTION, 0, "MATERIAL_SELECTION", MaterialSelection, "Material Selection", "Provide a selection of faces that use the specified material")
DefNode(GeometryNode, GEO_NODE_MENU_SWITCH, def_geo_menu_switch, "MENU_SWITCH", MenuSwitch, "Menu Switch", "Select from multiple inputs by name")
DefNode(GeometryNode, GEO_NODE_MERGE_BY_DISTANCE, 0, "MERGE_BY_DISTANCE", MergeByDistance, "Merge by Distance", "Merge vertices or points within a given distance")
DefNode(GeometryNode, GEO_NODE_MESH_BOOLEAN, 0, "MESH_BOOLEAN", MeshBoolean, "Mesh Boolean", "Cut, subtract, or join multiple mesh inputs")
DefNode(GeometryNode, GEO_NODE_MESH_FACE_GROUP_BOUNDARIES, 0, "MESH_FACE_SET_BOUNDARIES", MeshFaceSetBoundaries, "Face Group Boundaries", "Find edges on the boundaries between groups of faces with the same ID value")

View File

@ -122,6 +122,7 @@ set(SRC
nodes/node_geo_material_replace.cc
nodes/node_geo_material_selection.cc
nodes/node_geo_merge_by_distance.cc
nodes/node_geo_menu_switch.cc
nodes/node_geo_mesh_face_group_boundaries.cc
nodes/node_geo_mesh_primitive_circle.cc
nodes/node_geo_mesh_primitive_cone.cc

View File

@ -120,7 +120,8 @@ static bool geometry_node_tree_socket_type_valid(bNodeTreeType * /*treetype*/,
SOCK_COLLECTION,
SOCK_TEXTURE,
SOCK_IMAGE,
SOCK_MATERIAL);
SOCK_MATERIAL,
SOCK_MENU);
}
void register_node_tree_type_geo()

View File

@ -304,7 +304,8 @@ static void node_rna(StructRNA *srna)
SOCK_OBJECT,
SOCK_COLLECTION,
SOCK_MATERIAL,
SOCK_IMAGE);
SOCK_IMAGE,
SOCK_MENU);
});
});
}

View File

@ -0,0 +1,435 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <algorithm>
#include "node_geometry_util.hh"
#include "DNA_node_types.h"
#include "BLI_array_utils.hh"
#include "BLI_string.h"
#include "FN_multi_function.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket.hh"
#include "NOD_socket_search_link.hh"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "WM_api.hh"
namespace blender::nodes::node_geo_menu_switch_cc {
NODE_STORAGE_FUNCS(NodeMenuSwitch)
static bool is_supported_socket_type(const eNodeSocketDatatype data_type)
{
return ELEM(data_type,
SOCK_FLOAT,
SOCK_INT,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_VECTOR,
SOCK_STRING,
SOCK_RGBA,
SOCK_GEOMETRY,
SOCK_OBJECT,
SOCK_COLLECTION,
SOCK_MATERIAL,
SOCK_IMAGE);
}
static void node_declare(blender::nodes::NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
if (node == nullptr) {
return;
}
const NodeMenuSwitch &storage = node_storage(*node);
const eNodeSocketDatatype data_type = eNodeSocketDatatype(storage.data_type);
const bool supports_fields = socket_type_supports_fields(data_type);
auto &menu = b.add_input<decl::Menu>("Menu");
if (supports_fields) {
menu.supports_field();
}
for (const NodeEnumItem &enum_item : storage.enum_definition.items()) {
const std::string identifier = "Item_" + std::to_string(enum_item.identifier);
auto &input = b.add_input(data_type, enum_item.name, std::move(identifier));
if (supports_fields) {
input.supports_field();
}
/* Labels are ugly in combination with data-block pickers and are usually disabled. */
input.hide_label(ELEM(data_type, SOCK_OBJECT, SOCK_IMAGE, SOCK_COLLECTION, SOCK_MATERIAL));
}
auto &output = b.add_output(data_type, "Output");
if (supports_fields) {
output.dependent_field().reference_pass_all();
}
else if (data_type == SOCK_GEOMETRY) {
output.propagate_all();
}
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_enum_definition_init(NodeEnumDefinition &enum_def)
{
enum_def.next_identifier = 0;
enum_def.items_array = nullptr;
enum_def.items_num = 0;
}
static void node_enum_definition_free(NodeEnumDefinition &enum_def)
{
for (NodeEnumItem &item : enum_def.items_for_write()) {
MEM_SAFE_FREE(item.name);
MEM_SAFE_FREE(item.description);
}
MEM_SAFE_FREE(enum_def.items_array);
}
static void node_enum_definition_copy(NodeEnumDefinition &dst_enum_def,
const NodeEnumDefinition &src_enum_def)
{
dst_enum_def.items_array = MEM_cnew_array<NodeEnumItem>(src_enum_def.items_num, __func__);
dst_enum_def.items_num = src_enum_def.items_num;
dst_enum_def.active_index = src_enum_def.active_index;
dst_enum_def.next_identifier = src_enum_def.next_identifier;
for (const int i : IndexRange(src_enum_def.items_num)) {
dst_enum_def.items_array[i].identifier = src_enum_def.items_array[i].identifier;
if (char *name = src_enum_def.items_array[i].name) {
dst_enum_def.items_array[i].name = BLI_strdup(name);
}
if (char *desc = src_enum_def.items_array[i].description) {
dst_enum_def.items_array[i].description = BLI_strdup(desc);
}
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeMenuSwitch *data = MEM_cnew<NodeMenuSwitch>(__func__);
node_enum_definition_init(data->enum_definition);
data->data_type = SOCK_GEOMETRY;
node->storage = data;
}
static void node_free_storage(bNode *node)
{
NodeMenuSwitch &storage = node_storage(*node);
node_enum_definition_free(storage.enum_definition);
MEM_freeN(node->storage);
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeMenuSwitch &src_storage = node_storage(*src_node);
NodeMenuSwitch *dst_storage = MEM_cnew<NodeMenuSwitch>(__func__);
node_enum_definition_copy(dst_storage->enum_definition, src_storage.enum_definition);
dst_storage->data_type = src_storage.data_type;
dst_node->storage = dst_storage;
}
static void node_update(bNodeTree * /*ntree*/, bNode * /*node*/) {}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
if (params.in_out() == SOCK_IN) {
const eNodeSocketDatatype data_type = eNodeSocketDatatype(params.other_socket().type);
if (data_type == SOCK_MENU) {
params.add_item(IFACE_("Menu"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeMenuSwitch");
params.update_and_connect_available_socket(node, "Menu");
});
}
}
else {
params.add_item(IFACE_("Output"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeMenuSwitch");
node_storage(node).data_type = params.socket.type;
params.update_and_connect_available_socket(node, "Output");
});
}
}
/**
* Multifunction which evaluates the switch input for each enum item and partially fills the output
* array with values from the input array where the identifier matches.
*/
class MenuSwitchFn : public mf::MultiFunction {
const NodeEnumDefinition &enum_def_;
const CPPType &type_;
mf::Signature signature_;
public:
MenuSwitchFn(const NodeEnumDefinition &enum_def, const CPPType &type)
: enum_def_(enum_def), type_(type)
{
mf::SignatureBuilder builder{"Menu Switch", signature_};
builder.single_input<int>("Menu");
for (const NodeEnumItem &enum_item : enum_def.items()) {
builder.single_input(enum_item.name, type);
}
builder.single_output("Output", type);
this->set_signature(&signature_);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const
{
const int value_inputs_start = 1;
const int inputs_num = enum_def_.items_num;
const VArray<int> values = params.readonly_single_input<int>(0, "Menu");
/* Use one extra mask at the end for invalid indices. */
const int invalid_index = inputs_num;
GMutableSpan output = params.uninitialized_single_output(
signature_.params.index_range().last(), "Output");
auto find_item_index = [&](const int value) -> int {
for (const int i : enum_def_.items().index_range()) {
const NodeEnumItem &item = enum_def_.items()[i];
if (item.identifier == value) {
return i;
}
}
return invalid_index;
};
if (const std::optional<int> value = values.get_if_single()) {
const int index = find_item_index(*value);
if (index < inputs_num) {
const GVArray inputs = params.readonly_single_input(value_inputs_start + index);
inputs.materialize_to_uninitialized(mask, output.data());
}
else {
type_.fill_construct_indices(type_.default_value(), output.data(), mask);
}
return;
}
IndexMaskMemory memory;
Array<IndexMask> masks(inputs_num + 1);
IndexMask::from_groups<int64_t>(
mask, memory, [&](const int64_t i) { return find_item_index(values[i]); }, masks);
for (const int i : IndexRange(inputs_num)) {
if (!masks[i].is_empty()) {
const GVArray inputs = params.readonly_single_input(value_inputs_start + i);
inputs.materialize_to_uninitialized(masks[i], output.data());
}
}
type_.fill_construct_indices(type_.default_value(), output.data(), masks[invalid_index]);
}
};
class LazyFunctionForMenuSwitchNode : public LazyFunction {
private:
const bNode &node_;
bool can_be_field_ = false;
const NodeEnumDefinition &enum_def_;
const CPPType *cpp_type_;
const CPPType *field_base_type_;
public:
LazyFunctionForMenuSwitchNode(const bNode &node,
GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
: node_(node), enum_def_(node_storage(node).enum_definition)
{
const NodeMenuSwitch &storage = node_storage(node);
const eNodeSocketDatatype data_type = eNodeSocketDatatype(storage.data_type);
can_be_field_ = socket_type_supports_fields(data_type);
const bNodeSocketType *socket_type = nodeSocketTypeFind(
nodeStaticSocketType(data_type, PROP_NONE));
BLI_assert(socket_type != nullptr);
cpp_type_ = socket_type->geometry_nodes_cpp_type;
field_base_type_ = socket_type->base_cpp_type;
MutableSpan<int> lf_index_by_bsocket = lf_graph_info.mapping.lf_index_by_bsocket;
debug_name_ = node.name;
lf_index_by_bsocket[node.input_socket(0).index_in_tree()] = inputs_.append_and_get_index_as(
"Switch", CPPType::get<SocketValueVariant>(), lf::ValueUsage::Used);
for (const int i : enum_def_.items().index_range()) {
const NodeEnumItem &enum_item = enum_def_.items()[i];
lf_index_by_bsocket[node.input_socket(i + 1).index_in_tree()] =
inputs_.append_and_get_index_as(enum_item.name, *cpp_type_, lf::ValueUsage::Maybe);
}
lf_index_by_bsocket[node.output_socket(0).index_in_tree()] = outputs_.append_and_get_index_as(
"Value", *cpp_type_);
}
void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
{
SocketValueVariant condition_variant = params.get_input<SocketValueVariant>(0);
if (condition_variant.is_context_dependent_field() && can_be_field_) {
this->execute_field(condition_variant.get<Field<int>>(), params);
}
else {
this->execute_single(condition_variant.get<int>(), params);
}
}
void execute_single(const int condition, lf::Params &params) const
{
for (const int i : IndexRange(enum_def_.items_num)) {
const NodeEnumItem &enum_item = enum_def_.items_array[i];
const int input_index = i + 1;
if (enum_item.identifier == condition) {
void *value_to_forward = params.try_get_input_data_ptr_or_request(input_index);
if (value_to_forward == nullptr) {
/* Try again when the value is available. */
return;
}
void *output_ptr = params.get_output_data_ptr(0);
cpp_type_->move_construct(value_to_forward, output_ptr);
params.output_set(0);
}
else {
params.set_input_unused(input_index);
}
}
/* No guarantee that the switch input matches any enum,
* set default outputs to ensure valid state. */
set_default_remaining_node_outputs(params, node_);
}
void execute_field(Field<int> condition, lf::Params &params) const
{
/* When the condition is a non-constant field, we need all inputs. */
const int values_num = this->enum_def_.items_num;
Array<SocketValueVariant *, 8> input_values(values_num);
for (const int i : IndexRange(values_num)) {
const int input_index = i + 1;
input_values[i] = params.try_get_input_data_ptr_or_request<SocketValueVariant>(input_index);
}
if (input_values.as_span().contains(nullptr)) {
/* Try again when inputs are available. */
return;
}
Vector<GField> item_fields(enum_def_.items_num + 1);
item_fields[0] = std::move(condition);
for (const int i : IndexRange(enum_def_.items_num)) {
item_fields[i + 1] = input_values[i]->extract<GField>();
}
std::unique_ptr<MultiFunction> multi_function = std::make_unique<MenuSwitchFn>(
enum_def_, *field_base_type_);
GField output_field{FieldOperation::Create(std::move(multi_function), std::move(item_fields))};
void *output_ptr = params.get_output_data_ptr(0);
new (output_ptr) SocketValueVariant(std::move(output_field));
params.output_set(0);
}
};
/**
* Outputs booleans that indicate which inputs of a menu switch node
* are used. Note that it's possible that multiple inputs are used
* when the condition is a field.
*/
class LazyFunctionForMenuSwitchSocketUsage : public lf::LazyFunction {
const NodeEnumDefinition &enum_def_;
public:
LazyFunctionForMenuSwitchSocketUsage(const bNode &node)
: enum_def_(node_storage(node).enum_definition)
{
debug_name_ = "Menu Switch Socket Usage";
inputs_.append_as("Condition", CPPType::get<SocketValueVariant>());
for (const int i : IndexRange(enum_def_.items_num)) {
const NodeEnumItem &enum_item = enum_def_.items()[i];
outputs_.append_as(enum_item.name, CPPType::get<bool>());
}
}
void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
{
const SocketValueVariant &condition_variant = params.get_input<SocketValueVariant>(0);
if (condition_variant.is_context_dependent_field()) {
for (const int i : IndexRange(enum_def_.items_num)) {
params.set_output(i, true);
}
}
else {
const int32_t value = condition_variant.get<int>();
for (const int i : IndexRange(enum_def_.items_num)) {
const NodeEnumItem &enum_item = enum_def_.items()[i];
params.set_output(i, value == enum_item.identifier);
}
}
}
};
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(
srna,
"data_type",
"Data Type",
"",
rna_enum_node_socket_data_type_items,
NOD_storage_enum_accessors(data_type),
SOCK_GEOMETRY,
[](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
*r_free = true;
return enum_items_filter(
rna_enum_node_socket_data_type_items, [](const EnumPropertyItem &item) -> bool {
return is_supported_socket_type(eNodeSocketDatatype(item.value));
});
});
}
static void register_node()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_MENU_SWITCH, "Menu Switch", NODE_CLASS_CONVERTER);
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.updatefunc = node_update;
node_type_storage(&ntype, "NodeMenuSwitch", node_free_storage, node_copy_storage);
ntype.gather_link_search_ops = node_gather_link_searches;
ntype.draw_buttons = node_layout;
nodeRegisterType(&ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(register_node)
} // namespace blender::nodes::node_geo_menu_switch_cc
namespace blender::nodes {
std::unique_ptr<LazyFunction> get_menu_switch_node_lazy_function(
const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
{
using namespace node_geo_menu_switch_cc;
BLI_assert(node.type == GEO_NODE_MENU_SWITCH);
return std::make_unique<LazyFunctionForMenuSwitchNode>(node, lf_graph_info);
}
std::unique_ptr<LazyFunction> get_menu_switch_node_socket_usage_lazy_function(const bNode &node)
{
using namespace node_geo_menu_switch_cc;
BLI_assert(node.type == GEO_NODE_MENU_SWITCH);
return std::make_unique<LazyFunctionForMenuSwitchSocketUsage>(node);
}
} // namespace blender::nodes

View File

@ -234,7 +234,8 @@ static void node_rna(StructRNA *srna)
SOCK_OBJECT,
SOCK_COLLECTION,
SOCK_MATERIAL,
SOCK_IMAGE);
SOCK_IMAGE,
SOCK_MENU);
});
});
}

View File

@ -21,12 +21,15 @@
#include "BKE_geometry_fields.hh"
#include "BKE_geometry_set.hh"
#include "BKE_idprop.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_socket_value.hh"
#include "BKE_type_conversions.hh"
#include "FN_lazy_function_execute.hh"
#include "UI_resources.hh"
namespace lf = blender::fn::lazy_function;
namespace geo_log = blender::nodes::geo_eval_log;
@ -157,12 +160,60 @@ bool socket_type_has_attribute_toggle(const eNodeSocketDatatype type)
bool input_has_attribute_toggle(const bNodeTree &node_tree, const int socket_index)
{
node_tree.ensure_interface_cache();
const bNodeSocketType *typeinfo = node_tree.interface_inputs()[socket_index]->socket_typeinfo();
if (ELEM(typeinfo->type, SOCK_MENU)) {
return false;
}
BLI_assert(node_tree.runtime->field_inferencing_interface);
const nodes::FieldInferencingInterface &field_interface =
*node_tree.runtime->field_inferencing_interface;
return field_interface.inputs[socket_index] != nodes::InputSocketFieldType::None;
}
static void id_property_int_update_enum_items(const bNodeSocketValueMenu *value,
IDPropertyUIDataInt *ui_data)
{
int idprop_items_num = 0;
IDPropertyUIDataEnumItem *idprop_items = nullptr;
if (value->enum_items) {
const Span<bke::RuntimeNodeEnumItem> items = value->enum_items->items;
idprop_items_num = items.size();
idprop_items = MEM_cnew_array<IDPropertyUIDataEnumItem>(items.size(), __func__);
for (const int i : items.index_range()) {
const bke::RuntimeNodeEnumItem &item = items[i];
IDPropertyUIDataEnumItem &idprop_item = idprop_items[i];
idprop_item.value = item.identifier;
/* TODO: The name may not be unique!
* We require a unique identifier string for IDProperty and RNA enums,
* so node enums should probably have this too. */
idprop_item.identifier = BLI_strdup_null(item.name.c_str());
idprop_item.name = BLI_strdup_null(item.name.c_str());
idprop_item.description = BLI_strdup_null(item.description.c_str());
idprop_item.icon = ICON_NONE;
}
}
/* Fallback: if no items are defined, use a dummy item so the id property is not shown as a plain
* int value. */
if (idprop_items_num == 0) {
idprop_items_num = 1;
idprop_items = MEM_cnew_array<IDPropertyUIDataEnumItem>(1, __func__);
idprop_items->value = 0;
idprop_items->identifier = BLI_strdup("DUMMY");
idprop_items->name = BLI_strdup("");
idprop_items->description = BLI_strdup("");
idprop_items->icon = ICON_NONE;
}
/* Node enum definitions should already be valid. */
BLI_assert(IDP_EnumItemsValidate(idprop_items, idprop_items_num, nullptr));
ui_data->enum_items = idprop_items;
ui_data->enum_items_num = idprop_items_num;
}
std::unique_ptr<IDProperty, bke::idprop::IDPropertyDeleter> id_property_create_from_socket(
const bNodeTreeInterfaceSocket &socket)
{
@ -259,6 +310,14 @@ std::unique_ptr<IDProperty, bke::idprop::IDPropertyDeleter> id_property_create_f
ui_data->default_value = BLI_strdup(value->value);
return property;
}
case SOCK_MENU: {
const bNodeSocketValueMenu *value = static_cast<const bNodeSocketValueMenu *>(
socket.socket_data);
auto property = bke::idprop::create(identifier, value->value);
IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(property.get());
id_property_int_update_enum_items(value, ui_data);
return property;
}
case SOCK_OBJECT: {
const bNodeSocketValueObject *value = static_cast<const bNodeSocketValueObject *>(
socket.socket_data);
@ -319,6 +378,8 @@ bool id_property_type_matches_socket(const bNodeTreeInterfaceSocket &socket,
return property.type == IDP_BOOLEAN;
case SOCK_STRING:
return property.type == IDP_STRING;
case SOCK_MENU:
return property.type == IDP_INT;
case SOCK_OBJECT:
case SOCK_COLLECTION:
case SOCK_TEXTURE:
@ -415,6 +476,11 @@ static void init_socket_cpp_value_from_property(const IDProperty &property,
new (r_value) bke::SocketValueVariant(std::move(value));
break;
}
case SOCK_MENU: {
int value = IDP_Int(&property);
new (r_value) bke::SocketValueVariant(std::move(value));
break;
}
case SOCK_OBJECT: {
ID *id = IDP_Id(&property);
Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr;

View File

@ -3080,6 +3080,10 @@ struct GeometryNodesLazyFunctionBuilder {
this->build_bake_node(bnode, graph_params);
break;
}
case GEO_NODE_MENU_SWITCH: {
this->build_menu_switch_node(bnode, graph_params);
break;
}
default: {
if (node_type->geometry_node_execute) {
this->build_geometry_node(bnode, graph_params);
@ -3677,6 +3681,77 @@ struct GeometryNodesLazyFunctionBuilder {
}
}
void build_menu_switch_node(const bNode &bnode, BuildGraphParams &graph_params)
{
std::unique_ptr<LazyFunction> lazy_function = get_menu_switch_node_lazy_function(
bnode, *lf_graph_info_);
lf::FunctionNode &lf_node = graph_params.lf_graph.add_function(*lazy_function);
scope_.add(std::move(lazy_function));
int input_index = 0;
for (const bNodeSocket *bsocket : bnode.input_sockets()) {
if (bsocket->is_available()) {
lf::InputSocket &lf_socket = lf_node.input(input_index);
graph_params.lf_inputs_by_bsocket.add(bsocket, &lf_socket);
mapping_->bsockets_by_lf_socket_map.add(&lf_socket, bsocket);
input_index++;
}
}
for (const bNodeSocket *bsocket : bnode.output_sockets()) {
if (bsocket->is_available()) {
lf::OutputSocket &lf_socket = lf_node.output(0);
graph_params.lf_output_by_bsocket.add(bsocket, &lf_socket);
mapping_->bsockets_by_lf_socket_map.add(&lf_socket, bsocket);
break;
}
}
this->build_menu_switch_node_socket_usage(bnode, graph_params);
}
void build_menu_switch_node_socket_usage(const bNode &bnode, BuildGraphParams &graph_params)
{
const NodeMenuSwitch &storage = *static_cast<NodeMenuSwitch *>(bnode.storage);
const NodeEnumDefinition &enum_def = storage.enum_definition;
const bNodeSocket *switch_input_bsocket = bnode.input_sockets()[0];
Vector<const bNodeSocket *> input_bsockets(enum_def.items_num);
for (const int i : IndexRange(enum_def.items_num)) {
input_bsockets[i] = bnode.input_sockets()[i + 1];
}
const bNodeSocket *output_bsocket = bnode.output_sockets()[0];
lf::OutputSocket *output_is_used_socket = graph_params.usage_by_bsocket.lookup_default(
output_bsocket, nullptr);
if (output_is_used_socket == nullptr) {
return;
}
graph_params.usage_by_bsocket.add(switch_input_bsocket, output_is_used_socket);
if (switch_input_bsocket->is_directly_linked()) {
/* The condition input is dynamic, so the usage of the other inputs is as well. */
std::unique_ptr<LazyFunction> lazy_function =
get_menu_switch_node_socket_usage_lazy_function(bnode);
lf::FunctionNode &lf_node = graph_params.lf_graph.add_function(*lazy_function);
scope_.add(std::move(lazy_function));
graph_params.lf_inputs_by_bsocket.add(switch_input_bsocket, &lf_node.input(0));
for (const int i : IndexRange(enum_def.items_num)) {
graph_params.usage_by_bsocket.add(input_bsockets[i], &lf_node.output(i));
}
}
else {
const int condition =
switch_input_bsocket->default_value_typed<bNodeSocketValueMenu>()->value;
for (const int i : IndexRange(enum_def.items_num)) {
const NodeEnumItem &enum_item = enum_def.items()[i];
if (enum_item.identifier == condition) {
graph_params.usage_by_bsocket.add(input_bsockets[i], output_is_used_socket);
break;
}
}
}
}
void build_undefined_node(const bNode &bnode, BuildGraphParams &graph_params)
{
auto &lazy_function = scope_.construct<LazyFunctionForUndefinedNode>(

View File

@ -7,6 +7,7 @@
#include "BKE_compute_contexts.hh"
#include "BKE_curves.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_socket_value.hh"
#include "BKE_viewer_path.hh"
@ -176,6 +177,16 @@ void GeoTreeLogger::log_value(const bNode &node, const bNodeSocket &socket, cons
store_logged_value(this->allocator->construct<GenericValueLog>(GMutablePointer{type, buffer}));
};
auto log_menu_value = [&](Span<bke::RuntimeNodeEnumItem> enum_items, const int identifier) {
for (const bke::RuntimeNodeEnumItem &item : enum_items) {
if (item.identifier == identifier) {
log_generic_value(CPPType::get<std::string>(), &item.name);
return;
}
}
log_generic_value(CPPType::get<int>(), &identifier);
};
if (type.is<bke::GeometrySet>()) {
const bke::GeometrySet &geometry = *value.get<bke::GeometrySet>();
store_logged_value(this->allocator->construct<GeometryInfoLog>(geometry));
@ -189,7 +200,20 @@ void GeoTreeLogger::log_value(const bNode &node, const bNodeSocket &socket, cons
else {
value_variant.convert_to_single();
const GPointer value = value_variant.get_single_ptr();
log_generic_value(*value.type(), value.get());
if (socket.type == SOCK_MENU) {
const bNodeSocketValueMenu &default_value =
*socket.default_value_typed<bNodeSocketValueMenu>();
if (default_value.enum_items) {
const int identifier = *value.get<int>();
log_menu_value(default_value.enum_items->items, identifier);
}
else {
log_generic_value(*value.type(), value.get());
}
}
else {
log_generic_value(*value.type(), value.get());
}
}
}
else {

View File

@ -266,6 +266,13 @@ static SocketDeclarationPtr declaration_for_interface_socket(
dst = std::move(decl);
break;
}
case SOCK_MENU: {
const auto &value = node_interface::get_socket_data_as<bNodeSocketValueMenu>(io_socket);
std::unique_ptr<decl::Menu> decl = std::make_unique<decl::Menu>();
decl->default_value = value.value;
dst = std::move(decl);
break;
}
case SOCK_OBJECT: {
auto value = std::make_unique<decl::Object>();
value->default_value_fn = get_default_id_getter(ntree.tree_interface, io_socket);

View File

@ -428,6 +428,8 @@ std::unique_ptr<SocketDeclaration> make_declaration_for_socket_type(
return std::make_unique<decl::Collection>();
case SOCK_MATERIAL:
return std::make_unique<decl::Material>();
case SOCK_MENU:
return std::make_unique<decl::Menu>();
default:
return {};
}
@ -461,6 +463,8 @@ BaseSocketDeclarationBuilder &NodeDeclarationBuilder::add_input(
return this->add_input<decl::Collection>(name, identifier);
case SOCK_MATERIAL:
return this->add_input<decl::Material>(name, identifier);
case SOCK_MENU:
return this->add_input<decl::Menu>(name, identifier);
default:
BLI_assert_unreachable();
return this->add_input<decl::Float>("", "");
@ -502,6 +506,8 @@ BaseSocketDeclarationBuilder &NodeDeclarationBuilder::add_output(
return this->add_output<decl::Collection>(name, identifier);
case SOCK_MATERIAL:
return this->add_output<decl::Material>(name, identifier);
case SOCK_MENU:
return this->add_output<decl::Menu>(name, identifier);
default:
BLI_assert_unreachable();
return this->add_output<decl::Float>("", "");

View File

@ -548,8 +548,14 @@ void update_node_declaration_and_sockets(bNodeTree &ntree, bNode &node)
bool socket_type_supports_fields(const eNodeSocketDatatype socket_type)
{
return ELEM(
socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT, SOCK_ROTATION);
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_INT,
SOCK_ROTATION,
SOCK_MENU);
}
} // namespace blender::nodes
@ -646,6 +652,13 @@ void node_socket_init_default_value_data(eNodeSocketDatatype datatype, int subty
*data = dval;
break;
}
case SOCK_MENU: {
bNodeSocketValueMenu *dval = MEM_cnew<bNodeSocketValueMenu>("node socket value menu");
dval->value = -1;
*data = dval;
break;
}
case SOCK_OBJECT: {
bNodeSocketValueObject *dval = MEM_cnew<bNodeSocketValueObject>("node socket value object");
dval->value = nullptr;
@ -741,6 +754,12 @@ void node_socket_copy_default_value_data(eNodeSocketDatatype datatype, void *to,
*toval = *fromval;
break;
}
case SOCK_MENU: {
bNodeSocketValueMenu *toval = (bNodeSocketValueMenu *)to;
bNodeSocketValueMenu *fromval = (bNodeSocketValueMenu *)from;
*toval = *fromval;
break;
}
case SOCK_OBJECT: {
bNodeSocketValueObject *toval = (bNodeSocketValueObject *)to;
bNodeSocketValueObject *fromval = (bNodeSocketValueObject *)from;
@ -1045,6 +1064,23 @@ static bNodeSocketType *make_socket_type_string()
return socktype;
}
static bNodeSocketType *make_socket_type_menu()
{
bNodeSocketType *socktype = make_standard_socket_type(SOCK_MENU, PROP_NONE);
socktype->base_cpp_type = &blender::CPPType::get<int>();
socktype->get_base_cpp_value = [](const void *socket_value, void *r_value) {
*(int *)r_value = ((bNodeSocketValueMenu *)socket_value)->value;
};
socktype->geometry_nodes_cpp_type = &blender::CPPType::get<SocketValueVariant>();
socktype->get_geometry_nodes_cpp_value = [](const void *socket_value, void *r_value) {
const int value = ((bNodeSocketValueMenu *)socket_value)->value;
new (r_value) SocketValueVariant(value);
};
static SocketValueVariant default_value{0};
socktype->geometry_nodes_default_cpp_value = &default_value;
return socktype;
}
static bNodeSocketType *make_socket_type_object()
{
bNodeSocketType *socktype = make_standard_socket_type(SOCK_OBJECT, PROP_NONE);
@ -1150,6 +1186,8 @@ void register_standard_node_socket_types()
nodeRegisterSocketType(make_socket_type_string());
nodeRegisterSocketType(make_socket_type_menu());
nodeRegisterSocketType(make_standard_socket_type(SOCK_SHADER, PROP_NONE));
nodeRegisterSocketType(make_socket_type_object());

View File

@ -481,6 +481,53 @@ bNodeSocket &String::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Menu
* \{ */
bNodeSocket &Menu::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *nodeAddStaticSocket(&ntree,
&node,
this->in_out,
SOCK_MENU,
PROP_NONE,
this->identifier.c_str(),
this->name.c_str());
((bNodeSocketValueMenu *)socket.default_value)->value = this->default_value;
this->set_common_flags(socket);
return socket;
}
bool Menu::matches(const bNodeSocket &socket) const
{
if (!this->matches_common_data(socket)) {
return false;
}
if (socket.type != SOCK_MENU) {
return false;
}
return true;
}
bool Menu::can_connect(const bNodeSocket &socket) const
{
return sockets_can_connect(*this, socket) && socket.type == SOCK_MENU;
}
bNodeSocket &Menu::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const
{
if (socket.type != SOCK_MENU) {
BLI_assert(socket.in_out == this->in_out);
return this->build(ntree, node);
}
this->set_common_flags(socket);
return socket;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #IDSocketDeclaration
* \{ */