diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index c9d22b55aa4..9a49cc8467e 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -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, ) diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 4aa1cdb33fc..15fae7534ee 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -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") diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index 25729b384ed..b69e5de6d5b 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -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), diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 3b72cf45ad6..1d80d45172c 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -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 /** \} */ diff --git a/source/blender/blenkernel/BKE_node_enum.hh b/source/blender/blenkernel/BKE_node_enum.hh new file mode 100644 index 00000000000..a1c3a5df97e --- /dev/null +++ b/source/blender/blenkernel/BKE_node_enum.hh @@ -0,0 +1,49 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include + +#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 items; + + void delete_self() override + { + delete this; + } +}; + +/** \} */ + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_node_tree_interface.hh b/source/blender/blenkernel/BKE_node_tree_interface.hh index f8434095d88..c699df7f31a 100644 --- a/source/blender/blenkernel/BKE_node_tree_interface.hh +++ b/source/blender/blenkernel/BKE_node_tree_interface.hh @@ -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 bool socket_data_to_static_type(const eNodeSocketDatatype type, const Fn &fn) @@ -199,6 +200,9 @@ template bool socket_data_to_static_type(const eNodeSocketDatatype case SOCK_MATERIAL: fn.template operator()(); return true; + case SOCK_MENU: + fn.template operator()(); + return true; case SOCK_CUSTOM: case SOCK_SHADER: diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 2086d571d02..cd9e6032ea7 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -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 diff --git a/source/blender/blenkernel/intern/idprop.cc b/source/blender/blenkernel/intern/idprop.cc index 213c4ee87ca..0e5804055c9 100644 --- a/source/blender/blenkernel/intern/idprop.cc +++ b/source/blender/blenkernel/intern/idprop.cc @@ -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 */ }; /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index c1b30c59dc4..6d722807fd9 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -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(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(__func__); + + switch (eNodeSocketDatatype(sock->type)) { + case SOCK_MENU: { + bNodeSocketValueMenu &default_value = *sock->default_value_typed(); + /* 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(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(); + 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(); + 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( @@ -2691,6 +2754,8 @@ static void *socket_value_storage(bNodeSocket &socket) return &socket.default_value_typed()->value; case SOCK_ROTATION: return &socket.default_value_typed()->value_euler; + case SOCK_MENU: + return &socket.default_value_typed()->value; case SOCK_STRING: /* We don't want do this now! */ return nullptr; diff --git a/source/blender/blenkernel/intern/node_enum_definition.cc b/source/blender/blenkernel/intern/node_enum_definition.cc new file mode 100644 index 00000000000..510d70c1d10 --- /dev/null +++ b/source/blender/blenkernel/intern/node_enum_definition.cc @@ -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 NodeEnumDefinition::items() const +{ + return {this->items_array, this->items_num}; +} + +blender::MutableSpan 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(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(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); +} diff --git a/source/blender/blenkernel/intern/node_tree_interface.cc b/source/blender/blenkernel/intern/node_tree_interface.cc index 05ff47839a2..eca2c2c2f22 100644 --- a/source/blender/blenkernel/intern/node_tree_interface.cc +++ b/source/blender/blenkernel/intern/node_tree_interface.cc @@ -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 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 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 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) { diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index bf10f4cd037..fd8ffc9ecd5 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -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(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(), 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(), + *input->default_value_typed()); + } + } + } + + if (node->is_group()) { + /* Node groups expose internal enum definitions. */ + if (node->id == nullptr) { + continue; + } + const bNodeTree *group_tree = reinterpret_cast(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(), + *static_cast(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(), + *output->default_value_typed()); + } + } + } + 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(), + *output->default_value_typed()); + } + } + } + } + } + } + + /* Build list of new enum items for the node tree interface. */ + Vector 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()); + } + } + } + + /* 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(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(); + 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 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()->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 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 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 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); diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index fd6cfa18c93..35ac7ece4b1 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -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, std_node_socket_color_fn, std_node_socket_color_fn, + std_node_socket_color_fn, }; /* 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(); + 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; diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index fba0258bd45..af83bec6b6b 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -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; diff --git a/source/blender/editors/space_node/node_templates.cc b/source/blender/editors/space_node/node_templates.cc index 2edea184a72..aa7174507d3 100644 --- a/source/blender/editors/space_node/node_templates.cc +++ b/source/blender/editors/space_node/node_templates.cc @@ -394,6 +394,9 @@ static Vector ui_node_link_items(NodeLinkArg *arg, else if (dynamic_cast(&socket_decl)) { item.socket_type = SOCK_STRING; } + else if (dynamic_cast(&socket_decl)) { + item.socket_type = SOCK_MENU; + } else if (dynamic_cast(&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; diff --git a/source/blender/makesdna/DNA_array_utils.hh b/source/blender/makesdna/DNA_array_utils.hh index eb89b021aa6..cf4e303195d 100644 --- a/source/blender/makesdna/DNA_array_utils.hh +++ b/source/blender/makesdna/DNA_array_utils.hh @@ -13,6 +13,7 @@ #include "MEM_guardedalloc.h" #include "BLI_index_range.hh" +#include "BLI_utildefines.h" namespace blender::dna::array { diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index e4f69e90bdf..1d354653adb 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -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 items() const; + blender::MutableSpan 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; diff --git a/source/blender/makesrna/RNA_enum_types.hh b/source/blender/makesrna/RNA_enum_types.hh index 4a2236b1425..ee0cd006441 100644 --- a/source/blender/makesrna/RNA_enum_types.hh +++ b/source/blender/makesrna/RNA_enum_types.hh @@ -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); diff --git a/source/blender/makesrna/intern/rna_node_socket.cc b/source/blender/makesrna/intern/rna_node_socket.cc index 4ed195d2154..15194b28e96 100644 --- a/source/blender/makesrna/intern/rna_node_socket.cc +++ b/source/blender/makesrna/intern/rna_node_socket.cc @@ -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(ptr->data); + if (!socket) { + *r_free = false; + return rna_enum_dummy_NULL_items; + } + const bNodeSocketValueMenu *data = static_cast(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; diff --git a/source/blender/makesrna/intern/rna_node_tree_interface.cc b/source/blender/makesrna/intern/rna_node_tree_interface.cc index 4ac8edfe4bc..716636a83f5 100644 --- a/source/blender/makesrna/intern/rna_node_tree_interface.cc +++ b/source/blender/makesrna/intern/rna_node_tree_interface.cc @@ -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(ptr->data); + if (!socket) { + *r_free = false; + return rna_enum_dummy_NULL_items; + } + const bNodeSocketValueMenu *data = static_cast(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) diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 6279d6f9235..38dba742d25 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -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(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(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(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(ptr->owner_id); + const NodeEnumItem *item = static_cast(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(ptr->owner_id); + NodeEnumItem *item = static_cast(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(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(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(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(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(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(ptr->data); + NodeEnumItem *item = static_cast(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); } diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index 98999e6c09e..2c259ee7250 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -398,6 +398,9 @@ std::unique_ptr get_index_switch_node_lazy_function( const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info); std::unique_ptr get_bake_lazy_function( const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info); +std::unique_ptr get_menu_switch_node_lazy_function( + const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info); +std::unique_ptr 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 diff --git a/source/blender/nodes/NOD_socket_declarations.hh b/source/blender/nodes/NOD_socket_declarations.hh index 2ceeded54b8..033584c4fe2 100644 --- a/source/blender/nodes/NOD_socket_declarations.hh +++ b/source/blender/nodes/NOD_socket_declarations.hh @@ -179,6 +179,27 @@ class StringBuilder : public SocketDeclarationBuilder { 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 { + 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 * \{ */ diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 28c934bae7c..cfe80875e4f 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -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") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 5c588ccdb23..ada7ebf8f0a 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -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 diff --git a/source/blender/nodes/geometry/node_geometry_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index 178ed2fcb4c..f2fbb363c55 100644 --- a/source/blender/nodes/geometry/node_geometry_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -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() diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc index 31e95e511cf..cec273a8708 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc @@ -304,7 +304,8 @@ static void node_rna(StructRNA *srna) SOCK_OBJECT, SOCK_COLLECTION, SOCK_MATERIAL, - SOCK_IMAGE); + SOCK_IMAGE, + SOCK_MENU); }); }); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc new file mode 100644 index 00000000000..02d0b29078a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc @@ -0,0 +1,435 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#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("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(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(__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(__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 ¶ms) +{ + 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 ¶ms) { + bNode &node = params.add_node("GeometryNodeMenuSwitch"); + params.update_and_connect_available_socket(node, "Menu"); + }); + } + } + else { + params.add_item(IFACE_("Output"), [](LinkSearchOpParams ¶ms) { + 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("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 values = params.readonly_single_input(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 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 masks(inputs_num + 1); + IndexMask::from_groups( + 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 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(), 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 ¶ms, const lf::Context & /*context*/) const override + { + SocketValueVariant condition_variant = params.get_input(0); + if (condition_variant.is_context_dependent_field() && can_be_field_) { + this->execute_field(condition_variant.get>(), params); + } + else { + this->execute_single(condition_variant.get(), params); + } + } + + void execute_single(const int condition, lf::Params ¶ms) 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 condition, lf::Params ¶ms) const + { + /* When the condition is a non-constant field, we need all inputs. */ + const int values_num = this->enum_def_.items_num; + Array 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(input_index); + } + if (input_values.as_span().contains(nullptr)) { + /* Try again when inputs are available. */ + return; + } + + Vector 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(); + } + std::unique_ptr multi_function = std::make_unique( + 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()); + 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()); + } + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + const SocketValueVariant &condition_variant = params.get_input(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(); + 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 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(node, lf_graph_info); +} + +std::unique_ptr 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(node); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_switch.cc index c9d23befaba..9fa89f0cc2b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_switch.cc @@ -234,7 +234,8 @@ static void node_rna(StructRNA *srna) SOCK_OBJECT, SOCK_COLLECTION, SOCK_MATERIAL, - SOCK_IMAGE); + SOCK_IMAGE, + SOCK_MENU); }); }); } diff --git a/source/blender/nodes/intern/geometry_nodes_execute.cc b/source/blender/nodes/intern/geometry_nodes_execute.cc index d53dd9150d8..a0a856a7f79 100644 --- a/source/blender/nodes/intern/geometry_nodes_execute.cc +++ b/source/blender/nodes/intern/geometry_nodes_execute.cc @@ -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 items = value->enum_items->items; + idprop_items_num = items.size(); + idprop_items = MEM_cnew_array(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(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 id_property_create_from_socket( const bNodeTreeInterfaceSocket &socket) { @@ -259,6 +310,14 @@ std::unique_ptr id_property_create_f ui_data->default_value = BLI_strdup(value->value); return property; } + case SOCK_MENU: { + const bNodeSocketValueMenu *value = static_cast( + 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( 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; diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index 24c930a8c1d..6ec0f7a07d2 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -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 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(bnode.storage); + const NodeEnumDefinition &enum_def = storage.enum_definition; + + const bNodeSocket *switch_input_bsocket = bnode.input_sockets()[0]; + Vector 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 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()->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( diff --git a/source/blender/nodes/intern/geometry_nodes_log.cc b/source/blender/nodes/intern/geometry_nodes_log.cc index 3fbd68faf6a..25fabb0a00c 100644 --- a/source/blender/nodes/intern/geometry_nodes_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -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(GMutablePointer{type, buffer})); }; + auto log_menu_value = [&](Span enum_items, const int identifier) { + for (const bke::RuntimeNodeEnumItem &item : enum_items) { + if (item.identifier == identifier) { + log_generic_value(CPPType::get(), &item.name); + return; + } + } + log_generic_value(CPPType::get(), &identifier); + }; + if (type.is()) { const bke::GeometrySet &geometry = *value.get(); store_logged_value(this->allocator->construct(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(); + if (default_value.enum_items) { + const int identifier = *value.get(); + 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 { diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index 172e5f854fb..8cb16a2d594 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -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(io_socket); + std::unique_ptr decl = std::make_unique(); + decl->default_value = value.value; + dst = std::move(decl); + break; + } case SOCK_OBJECT: { auto value = std::make_unique(); value->default_value_fn = get_default_id_getter(ntree.tree_interface, io_socket); diff --git a/source/blender/nodes/intern/node_declaration.cc b/source/blender/nodes/intern/node_declaration.cc index df5d1561484..0d8291cb0b8 100644 --- a/source/blender/nodes/intern/node_declaration.cc +++ b/source/blender/nodes/intern/node_declaration.cc @@ -428,6 +428,8 @@ std::unique_ptr make_declaration_for_socket_type( return std::make_unique(); case SOCK_MATERIAL: return std::make_unique(); + case SOCK_MENU: + return std::make_unique(); default: return {}; } @@ -461,6 +463,8 @@ BaseSocketDeclarationBuilder &NodeDeclarationBuilder::add_input( return this->add_input(name, identifier); case SOCK_MATERIAL: return this->add_input(name, identifier); + case SOCK_MENU: + return this->add_input(name, identifier); default: BLI_assert_unreachable(); return this->add_input("", ""); @@ -502,6 +506,8 @@ BaseSocketDeclarationBuilder &NodeDeclarationBuilder::add_output( return this->add_output(name, identifier); case SOCK_MATERIAL: return this->add_output(name, identifier); + case SOCK_MENU: + return this->add_output(name, identifier); default: BLI_assert_unreachable(); return this->add_output("", ""); diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index cad312e2859..9f25624c3fd 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -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("node socket value menu"); + dval->value = -1; + + *data = dval; + break; + } case SOCK_OBJECT: { bNodeSocketValueObject *dval = MEM_cnew("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(); + 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(); + 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()); diff --git a/source/blender/nodes/intern/node_socket_declarations.cc b/source/blender/nodes/intern/node_socket_declarations.cc index 10f2b8b474c..8c72b107971 100644 --- a/source/blender/nodes/intern/node_socket_declarations.cc +++ b/source/blender/nodes/intern/node_socket_declarations.cc @@ -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 * \{ */