Geometry Nodes: Menu Switch Node #113445
@ -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,
|
||||
)
|
||||
|
@ -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")
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
49
source/blender/blenkernel/BKE_node_enum.hh
Normal file
@ -0,0 +1,49 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "BLI_implicit_sharing.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/* Flags for #bNodeSocketValueMenu. */
|
||||
enum NodeSocketValueMenuRuntimeFlag {
|
||||
/* Socket has conflicting menu connections and cannot resolve items. */
|
||||
NODE_MENU_ITEMS_CONFLICT = (1 << 0),
|
||||
};
|
||||
|
||||
LukasTonne marked this conversation as resolved
Outdated
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \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 {
|
||||
LukasTonne marked this conversation as resolved
Outdated
Hans Goudey
commented
No need for No need for `blender::` here
|
||||
Vector<RuntimeNodeEnumItem> items;
|
||||
|
||||
void delete_self() override
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::bke
|
@ -158,6 +158,7 @@ static const bNodeSocketStaticTypeInfo node_socket_subtypes[] = {
|
||||
{"NodeSocketCollection", "NodeTreeInterfaceSocketCollection", SOCK_COLLECTION, PROP_NONE},
|
||||
{"NodeSocketTexture", "NodeTreeInterfaceSocketTexture", SOCK_TEXTURE, PROP_NONE},
|
||||
{"NodeSocketMaterial", "NodeTreeInterfaceSocketMaterial", SOCK_MATERIAL, PROP_NONE},
|
||||
{"NodeSocketMenu", "NodeTreeInterfaceSocketMenu", SOCK_MENU, PROP_NONE},
|
||||
};
|
||||
|
||||
template<typename Fn> bool socket_data_to_static_type(const eNodeSocketDatatype type, const Fn &fn)
|
||||
@ -199,6 +200,9 @@ template<typename Fn> bool socket_data_to_static_type(const eNodeSocketDatatype
|
||||
case SOCK_MATERIAL:
|
||||
fn.template operator()<bNodeSocketValueMaterial>();
|
||||
return true;
|
||||
case SOCK_MENU:
|
||||
fn.template operator()<bNodeSocketValueMenu>();
|
||||
return true;
|
||||
|
||||
case SOCK_CUSTOM:
|
||||
case SOCK_SHADER:
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
@ -67,6 +67,7 @@
|
||||
#include "BKE_lib_query.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_enum.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_tree_anonymous_attributes.hh"
|
||||
#include "BKE_node_tree_interface.hh"
|
||||
@ -357,6 +358,7 @@ static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket
|
||||
case SOCK_CUSTOM:
|
||||
case SOCK_SHADER:
|
||||
case SOCK_GEOMETRY:
|
||||
case SOCK_MENU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -701,6 +703,10 @@ static void write_node_socket_default_value(BlendWriter *writer, const bNodeSock
|
||||
case SOCK_ROTATION:
|
||||
BLO_write_struct(writer, bNodeSocketValueRotation, sock->default_value);
|
||||
break;
|
||||
case SOCK_MENU: {
|
||||
BLO_write_struct(writer, bNodeSocketValueMenu, sock->default_value);
|
||||
break;
|
||||
}
|
||||
case SOCK_CUSTOM:
|
||||
/* Custom node sockets where default_value is defined uses custom properties for storage. */
|
||||
break;
|
||||
@ -853,6 +859,17 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
|
||||
if (node->type == GEO_NODE_BAKE) {
|
||||
blender::nodes::BakeItemsAccessor::blend_write(writer, *node);
|
||||
}
|
||||
if (node->type == GEO_NODE_MENU_SWITCH) {
|
||||
const NodeMenuSwitch &storage = *static_cast<const NodeMenuSwitch *>(node->storage);
|
||||
BLO_write_struct_array(writer,
|
||||
NodeEnumItem,
|
||||
storage.enum_definition.items_num,
|
||||
storage.enum_definition.items_array);
|
||||
for (const NodeEnumItem &item : storage.enum_definition.items()) {
|
||||
BLO_write_string(writer, item.name);
|
||||
BLO_write_string(writer, item.description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
|
||||
@ -921,6 +938,7 @@ static bool is_node_socket_supported(const bNodeSocket *sock)
|
||||
case SOCK_TEXTURE:
|
||||
case SOCK_MATERIAL:
|
||||
case SOCK_ROTATION:
|
||||
case SOCK_MENU:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -937,6 +955,18 @@ static void direct_link_node_socket(BlendDataReader *reader, bNodeSocket *sock)
|
||||
BLO_read_data_address(reader, &sock->default_value);
|
||||
BLO_read_data_address(reader, &sock->default_attribute_name);
|
||||
sock->runtime = MEM_new<bNodeSocketRuntime>(__func__);
|
||||
|
||||
switch (eNodeSocketDatatype(sock->type)) {
|
||||
case SOCK_MENU: {
|
||||
bNodeSocketValueMenu &default_value = *sock->default_value_typed<bNodeSocketValueMenu>();
|
||||
/* Clear runtime data. */
|
||||
default_value.enum_items = nullptr;
|
||||
default_value.runtime_flag = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void direct_link_node_socket_list(BlendDataReader *reader, ListBase *socket_list)
|
||||
@ -1099,6 +1129,15 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree)
|
||||
blender::nodes::BakeItemsAccessor::blend_read_data(reader, *node);
|
||||
break;
|
||||
}
|
||||
case GEO_NODE_MENU_SWITCH: {
|
||||
NodeMenuSwitch &storage = *static_cast<NodeMenuSwitch *>(node->storage);
|
||||
BLO_read_data_address(reader, &storage.enum_definition.items_array);
|
||||
for (const NodeEnumItem &item : storage.enum_definition.items()) {
|
||||
BLO_read_data_address(reader, &item.name);
|
||||
BLO_read_data_address(reader, &item.description);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
@ -1826,6 +1865,7 @@ static void socket_id_user_increment(bNodeSocket *sock)
|
||||
case SOCK_ROTATION:
|
||||
case SOCK_INT:
|
||||
case SOCK_STRING:
|
||||
case SOCK_MENU:
|
||||
LukasTonne marked this conversation as resolved
Outdated
Jacques Lucke
commented
Outdated comment, same below. Outdated comment, same below.
|
||||
case SOCK_CUSTOM:
|
||||
case SOCK_SHADER:
|
||||
case SOCK_GEOMETRY:
|
||||
@ -1872,6 +1912,7 @@ static bool socket_id_user_decrement(bNodeSocket *sock)
|
||||
case SOCK_ROTATION:
|
||||
case SOCK_INT:
|
||||
case SOCK_STRING:
|
||||
case SOCK_MENU:
|
||||
case SOCK_CUSTOM:
|
||||
case SOCK_SHADER:
|
||||
case SOCK_GEOMETRY:
|
||||
@ -1931,6 +1972,7 @@ void nodeModifySocketType(bNodeTree *ntree,
|
||||
case SOCK_COLLECTION:
|
||||
case SOCK_TEXTURE:
|
||||
case SOCK_MATERIAL:
|
||||
case SOCK_MENU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2065,6 +2107,8 @@ const char *nodeStaticSocketType(const int type, const int subtype)
|
||||
return "NodeSocketTexture";
|
||||
case SOCK_MATERIAL:
|
||||
return "NodeSocketMaterial";
|
||||
case SOCK_MENU:
|
||||
return "NodeSocketMenu";
|
||||
case SOCK_CUSTOM:
|
||||
break;
|
||||
}
|
||||
@ -2146,6 +2190,8 @@ const char *nodeStaticSocketInterfaceTypeNew(const int type, const int subtype)
|
||||
return "NodeTreeInterfaceSocketTexture";
|
||||
case SOCK_MATERIAL:
|
||||
return "NodeTreeInterfaceSocketMaterial";
|
||||
case SOCK_MENU:
|
||||
return "NodeTreeInterfaceSocketMenu";
|
||||
case SOCK_CUSTOM:
|
||||
break;
|
||||
}
|
||||
@ -2183,6 +2229,8 @@ const char *nodeStaticSocketLabel(const int type, const int /*subtype*/)
|
||||
return "Texture";
|
||||
case SOCK_MATERIAL:
|
||||
return "Material";
|
||||
case SOCK_MENU:
|
||||
return "Menu";
|
||||
case SOCK_CUSTOM:
|
||||
break;
|
||||
}
|
||||
@ -2222,6 +2270,13 @@ static void node_socket_free(bNodeSocket *sock, const bool do_id_user)
|
||||
if (do_id_user) {
|
||||
socket_id_user_decrement(sock);
|
||||
}
|
||||
if (sock->type == SOCK_MENU) {
|
||||
auto &default_value_menu = *sock->default_value_typed<bNodeSocketValueMenu>();
|
||||
if (default_value_menu.enum_items) {
|
||||
/* Release shared data pointer. */
|
||||
default_value_menu.enum_items->remove_user_and_delete_if_last();
|
||||
}
|
||||
}
|
||||
MEM_freeN(sock->default_value);
|
||||
}
|
||||
if (sock->default_attribute_name) {
|
||||
@ -2551,6 +2606,14 @@ static void node_socket_copy(bNodeSocket *sock_dst, const bNodeSocket *sock_src,
|
||||
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
|
||||
socket_id_user_increment(sock_dst);
|
||||
}
|
||||
|
||||
if (sock_src->type == SOCK_MENU) {
|
||||
auto &default_value_menu = *sock_dst->default_value_typed<bNodeSocketValueMenu>();
|
||||
if (default_value_menu.enum_items) {
|
||||
/* Copy of shared data pointer. */
|
||||
default_value_menu.enum_items->add_user();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sock_dst->default_attribute_name = static_cast<char *>(
|
||||
@ -2691,6 +2754,8 @@ static void *socket_value_storage(bNodeSocket &socket)
|
||||
return &socket.default_value_typed<bNodeSocketValueMaterial>()->value;
|
||||
case SOCK_ROTATION:
|
||||
return &socket.default_value_typed<bNodeSocketValueRotation>()->value_euler;
|
||||
case SOCK_MENU:
|
||||
return &socket.default_value_typed<bNodeSocketValueMenu>()->value;
|
||||
case SOCK_STRING:
|
||||
/* We don't want do this now! */
|
||||
return nullptr;
|
||||
|
149
source/blender/blenkernel/intern/node_enum_definition.cc
Normal file
@ -0,0 +1,149 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
#include "DNA_array_utils.hh"
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.h"
|
||||
#include "BKE_node_enum.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
using blender::bke::NodeSocketValueMenuRuntimeFlag;
|
||||
|
||||
bool bNodeSocketValueMenu::has_conflict() const
|
||||
{
|
||||
return this->runtime_flag & NodeSocketValueMenuRuntimeFlag::NODE_MENU_ITEMS_CONFLICT;
|
||||
}
|
||||
|
||||
blender::Span<NodeEnumItem> NodeEnumDefinition::items() const
|
||||
{
|
||||
return {this->items_array, this->items_num};
|
||||
}
|
||||
|
||||
blender::MutableSpan<NodeEnumItem> NodeEnumDefinition::items_for_write()
|
||||
{
|
||||
return {this->items_array, this->items_num};
|
||||
}
|
||||
|
||||
NodeEnumItem *NodeEnumDefinition::add_item(blender::StringRef name)
|
||||
{
|
||||
const int insert_index = this->items_num;
|
||||
NodeEnumItem *old_items = this->items_array;
|
||||
|
||||
this->items_array = MEM_cnew_array<NodeEnumItem>(this->items_num + 1, __func__);
|
||||
std::copy_n(old_items, insert_index, this->items_array);
|
||||
NodeEnumItem &new_item = this->items_array[insert_index];
|
||||
std::copy_n(old_items + insert_index + 1,
|
||||
this->items_num - insert_index,
|
||||
this->items_array + insert_index + 1);
|
||||
|
||||
new_item.identifier = this->next_identifier++;
|
||||
this->set_item_name(new_item, name);
|
||||
|
||||
this->items_num++;
|
||||
MEM_SAFE_FREE(old_items);
|
||||
|
||||
return &new_item;
|
||||
}
|
||||
|
||||
static void free_enum_item(NodeEnumItem *item)
|
||||
{
|
||||
MEM_SAFE_FREE(item->name);
|
||||
MEM_SAFE_FREE(item->description);
|
||||
}
|
||||
LukasTonne marked this conversation as resolved
Outdated
Jacques Lucke
commented
Maybe you want to use the utility in Maybe you want to use the utility in` DNA_array_utils.hh`. Same below.
|
||||
|
||||
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;
|
||||
LukasTonne marked this conversation as resolved
Outdated
Jacques Lucke
commented
Looks like this does not free everything in the items. Can just use the utility in Looks like this does not free everything in the items. Can just use the utility in `DNA_array_utils.hh`.
|
||||
int active_index = this->active_index;
|
||||
blender::dna::array::clear(&this->items_array, &items_num, &active_index, free_enum_item);
|
||||
this->items_num = int16_t(items_num);
|
||||
this->active_index = int16_t(active_index);
|
||||
}
|
||||
|
||||
bool NodeEnumDefinition::move_item(uint16_t from_index, uint16_t to_index)
|
||||
{
|
||||
if (to_index < this->items_num) {
|
||||
int items_num = this->items_num;
|
||||
int active_index = this->active_index;
|
||||
blender::dna::array::move_index(this->items_array, items_num, from_index, to_index);
|
||||
this->items_num = int16_t(items_num);
|
||||
this->active_index = int16_t(active_index);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const NodeEnumItem *NodeEnumDefinition::active_item() const
|
||||
{
|
||||
if (blender::IndexRange(this->items_num).contains(this->active_index)) {
|
||||
return &this->items()[this->active_index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeEnumItem *NodeEnumDefinition::active_item()
|
||||
{
|
||||
if (blender::IndexRange(this->items_num).contains(this->active_index)) {
|
||||
return &this->items_for_write()[this->active_index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NodeEnumDefinition::active_item_set(NodeEnumItem *item)
|
||||
{
|
||||
this->active_index = this->items().contains_ptr(item) ? item - this->items_array : -1;
|
||||
}
|
||||
|
||||
void NodeEnumDefinition::set_item_name(NodeEnumItem &item, blender::StringRef name)
|
||||
{
|
||||
char unique_name[MAX_NAME + 4];
|
||||
STRNCPY(unique_name, name.data());
|
||||
|
||||
struct Args {
|
||||
NodeEnumDefinition *enum_def;
|
||||
const NodeEnumItem *item;
|
||||
} args = {this, &item};
|
||||
|
||||
const char *default_name = items().is_empty() ? "Name" : items().last().name;
|
||||
BLI_uniquename_cb(
|
||||
[](void *arg, const char *name) {
|
||||
const Args &args = *static_cast<Args *>(arg);
|
||||
for (const NodeEnumItem &item : args.enum_def->items()) {
|
||||
if (&item != args.item) {
|
||||
if (STREQ(item.name, name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
&args,
|
||||
default_name,
|
||||
'.',
|
||||
unique_name,
|
||||
ARRAY_SIZE(unique_name));
|
||||
|
||||
MEM_SAFE_FREE(item.name);
|
||||
item.name = BLI_strdup(unique_name);
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_lib_query.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_enum.hh"
|
||||
#include "BKE_node_tree_interface.hh"
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
@ -171,6 +172,12 @@ template<> void socket_data_init_impl(bNodeSocketValueMaterial &data)
|
||||
{
|
||||
data.value = nullptr;
|
||||
}
|
||||
template<> void socket_data_init_impl(bNodeSocketValueMenu &data)
|
||||
{
|
||||
data.value = -1;
|
||||
data.enum_items = nullptr;
|
||||
data.runtime_flag = 0;
|
||||
}
|
||||
|
||||
static void *make_socket_data(const StringRef socket_type)
|
||||
{
|
||||
@ -191,6 +198,13 @@ static void *make_socket_data(const StringRef socket_type)
|
||||
* \{ */
|
||||
|
||||
template<typename T> void socket_data_free_impl(T & /*data*/, const bool /*do_id_user*/) {}
|
||||
template<> void socket_data_free_impl(bNodeSocketValueMenu &dst, const bool /*do_id_user*/)
|
||||
{
|
||||
if (dst.enum_items) {
|
||||
/* Release shared data pointer. */
|
||||
dst.enum_items->remove_user_and_delete_if_last();
|
||||
}
|
||||
}
|
||||
|
||||
static void socket_data_free(bNodeTreeInterfaceSocket &socket, const bool do_id_user)
|
||||
{
|
||||
@ -210,6 +224,14 @@ static void socket_data_free(bNodeTreeInterfaceSocket &socket, const bool do_id_
|
||||
* \{ */
|
||||
|
||||
template<typename T> void socket_data_copy_impl(T & /*dst*/, const T & /*src*/) {}
|
||||
template<>
|
||||
void socket_data_copy_impl(bNodeSocketValueMenu &dst, const bNodeSocketValueMenu & /*src*/)
|
||||
{
|
||||
/* Copy of shared data pointer. */
|
||||
if (dst.enum_items) {
|
||||
dst.enum_items->add_user();
|
||||
}
|
||||
}
|
||||
|
||||
static void socket_data_copy(bNodeTreeInterfaceSocket &dst,
|
||||
const bNodeTreeInterfaceSocket &src,
|
||||
@ -304,6 +326,10 @@ inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueMaterial
|
||||
{
|
||||
BLO_write_struct(writer, bNodeSocketValueMaterial, &data);
|
||||
}
|
||||
inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueMenu &data)
|
||||
{
|
||||
BLO_write_struct(writer, bNodeSocketValueMenu, &data);
|
||||
}
|
||||
|
||||
static void socket_data_write(BlendWriter *writer, bNodeTreeInterfaceSocket &socket)
|
||||
{
|
||||
@ -323,6 +349,13 @@ template<typename T> void socket_data_read_data_impl(BlendDataReader *reader, T
|
||||
{
|
||||
BLO_read_data_address(reader, data);
|
||||
}
|
||||
template<> void socket_data_read_data_impl(BlendDataReader *reader, bNodeSocketValueMenu **data)
|
||||
{
|
||||
BLO_read_data_address(reader, data);
|
||||
/* Clear runtime data. */
|
||||
(*data)->enum_items = nullptr;
|
||||
(*data)->runtime_flag = 0;
|
||||
}
|
||||
|
||||
static void socket_data_read_data(BlendDataReader *reader, bNodeTreeInterfaceSocket &socket)
|
||||
{
|
||||
|
@ -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) {
|
||||
LukasTonne marked this conversation as resolved
Outdated
Jacques Lucke
commented
Since this is geometry nodes only for now, it can also be moved one line down. Since this is geometry nodes only for now, it can also be moved one line down.
|
||||
if (this->propagate_enum_definitions(ntree)) {
|
||||
result.interface_changed = true;
|
||||
}
|
||||
if (node_field_inferencing::update_field_inferencing(ntree)) {
|
||||
result.interface_changed = true;
|
||||
LukasTonne marked this conversation as resolved
Outdated
Jacques Lucke
commented
I think it's important that this can detect whether the enum definitions on the interface changed (by just comparing the old and new state). This is also done below in a few cases: This is important because it allows more selectively updating parent node groups and the modifier. Setting I think it's important that this can detect whether the enum definitions on the interface changed (by just comparing the old and new state). This is also done below in a few cases: `result.interface_changed = true;`.
This is important because it allows more selectively updating parent node groups and the modifier. Setting `interface_changed` to true will also tag the group nodes that invoke the current group as changed (see `add_node_tag(pair.first, pair.second, NTREE_CHANGED_NODE_PROPERTY);`).
Lukas Tönne
commented
I think i need to add another update flag I think i need to add another update flag `NTREE_CHANGED_ENUM` to keep track of which nodes are actually affected by enum changes. RNA would also set this more specifically, instead of the generic `NTREE_CHANGED_NODE_PROPERTY`. Otherwise it wouldn't be possible to tell genuine enum pointer conflicts apart from pointers that simply haven't been updated yet and get replaced. Other parts of the updater seem to do the same, e.g. `BKE_ntree_update_tag_node_internal_link` is called both from the API as well as the `update_internal_links_in_node` propagation function.
Lukas Tönne
commented
I didn't need to add another update flag after all. I just make sure the enum items of the menu switch are only replaced when the node changes, and then compare the new node tree interface pointers at the end. I didn't need to add another update flag after all. I just make sure the enum items of the menu switch are only replaced when the node changes, and then compare the new node tree interface pointers at the end.
|
||||
}
|
||||
@ -799,6 +803,209 @@ class NodeTreeMainUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
bool propagate_enum_definitions(bNodeTree &ntree)
|
||||
{
|
||||
ntree.ensure_interface_cache();
|
||||
|
||||
LukasTonne marked this conversation as resolved
Outdated
Jacques Lucke
commented
Minor note, since we are in a Minor note, since we are in a `class` here, it's possible to order the functions so that the higher level functions are at the top and they call the functions below. That's done in the rest of the update code here.
|
||||
/* Propagation from right to left to determine which enum
|
||||
* definition to use for menu sockets. */
|
||||
for (bNode *node : ntree.toposort_right_to_left()) {
|
||||
const bool node_updated = this->should_update_individual_node(ntree, *node);
|
||||
|
||||
if (node->typeinfo->type == GEO_NODE_MENU_SWITCH) {
|
||||
/* Generate new enum items when the node has changed, otherwise keep existing items. */
|
||||
if (node_updated) {
|
||||
const NodeMenuSwitch &storage = *static_cast<NodeMenuSwitch *>(node->storage);
|
||||
const RuntimeNodeEnumItems *enum_items = this->create_runtime_enum_items(
|
||||
storage.enum_definition);
|
||||
|
||||
bNodeSocket &input = *node->input_sockets()[0];
|
||||
BLI_assert(input.is_available() && input.type == SOCK_MENU);
|
||||
this->set_enum_ptr(*input.default_value_typed<bNodeSocketValueMenu>(), enum_items);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
/* Clear current enum references. */
|
||||
for (bNodeSocket *socket : node->input_sockets()) {
|
||||
if (socket->is_available() && socket->type == SOCK_MENU) {
|
||||
clear_enum_reference(*socket);
|
||||
}
|
||||
}
|
||||
for (bNodeSocket *socket : node->output_sockets()) {
|
||||
if (socket->is_available() && socket->type == SOCK_MENU) {
|
||||
clear_enum_reference(*socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Propagate enum references from output links. */
|
||||
for (bNodeSocket *output : node->output_sockets()) {
|
||||
if (output->is_available() && output->type == SOCK_MENU) {
|
||||
for (const bNodeSocket *input : output->directly_linked_sockets()) {
|
||||
this->update_socket_enum_definition(
|
||||
*output->default_value_typed<bNodeSocketValueMenu>(),
|
||||
*input->default_value_typed<bNodeSocketValueMenu>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node->is_group()) {
|
||||
/* Node groups expose internal enum definitions. */
|
||||
if (node->id == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const bNodeTree *group_tree = reinterpret_cast<bNodeTree *>(node->id);
|
||||
group_tree->ensure_interface_cache();
|
||||
|
||||
for (const int socket_i : group_tree->interface_inputs().index_range()) {
|
||||
bNodeSocket &input = *node->input_sockets()[socket_i];
|
||||
const bNodeTreeInterfaceSocket &iosocket = *group_tree->interface_inputs()[socket_i];
|
||||
BLI_assert(STREQ(input.identifier, iosocket.identifier));
|
||||
LukasTonne marked this conversation as resolved
Outdated
Jacques Lucke
commented
Please use Please use `this->` for method calls in this file.
|
||||
if (input.is_available() && input.type == SOCK_MENU) {
|
||||
BLI_assert(STREQ(iosocket.socket_type, "NodeSocketMenu"));
|
||||
this->update_socket_enum_definition(
|
||||
*input.default_value_typed<bNodeSocketValueMenu>(),
|
||||
*static_cast<bNodeSocketValueMenu *>(iosocket.socket_data));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (node->type == GEO_NODE_MENU_SWITCH) {
|
||||
/* First input is always the node's own menu, propagate only to the enum case inputs. */
|
||||
const bNodeSocket *output = node->output_sockets().first();
|
||||
for (bNodeSocket *input : node->input_sockets().drop_front(1)) {
|
||||
if (input->is_available() && input->type == SOCK_MENU) {
|
||||
this->update_socket_enum_definition(
|
||||
*input->default_value_typed<bNodeSocketValueMenu>(),
|
||||
*output->default_value_typed<bNodeSocketValueMenu>());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Propagate over internal relations. */
|
||||
/* XXX Placeholder implementation just propagates all outputs
|
||||
* to all inputs for built-in nodes This could perhaps use
|
||||
* input/output relations to handle propagation generically? */
|
||||
for (bNodeSocket *input : node->input_sockets()) {
|
||||
if (input->is_available() && input->type == SOCK_MENU) {
|
||||
for (const bNodeSocket *output : node->output_sockets()) {
|
||||
if (output->is_available() && output->type == SOCK_MENU) {
|
||||
this->update_socket_enum_definition(
|
||||
*input->default_value_typed<bNodeSocketValueMenu>(),
|
||||
*output->default_value_typed<bNodeSocketValueMenu>());
|
||||
LukasTonne marked this conversation as resolved
Outdated
Iliya Katushenock
commented
Shouldn't type check for output socket be here too? Shouldn't type check for output socket be here too?
Lukas Tönne
commented
Yes, good catch. Yes, good catch.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Build list of new enum items for the node tree interface. */
|
||||
Vector<bNodeSocketValueMenu> interface_enum_items(ntree.interface_inputs().size(), {0});
|
||||
for (const bNode *group_input_node : ntree.group_input_nodes()) {
|
||||
for (const int socket_i : ntree.interface_inputs().index_range()) {
|
||||
const bNodeSocket &output = *group_input_node->output_sockets()[socket_i];
|
||||
|
||||
if (output.is_available() && output.type == SOCK_MENU) {
|
||||
this->update_socket_enum_definition(interface_enum_items[socket_i],
|
||||
*output.default_value_typed<bNodeSocketValueMenu>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Move enum items to the interface and detect if anything changed. */
|
||||
bool changed = false;
|
||||
for (const int socket_i : ntree.interface_inputs().index_range()) {
|
||||
bNodeTreeInterfaceSocket &iosocket = *ntree.interface_inputs()[socket_i];
|
||||
if (STREQ(iosocket.socket_type, "NodeSocketMenu")) {
|
||||
bNodeSocketValueMenu &dst = *static_cast<bNodeSocketValueMenu *>(iosocket.socket_data);
|
||||
const bNodeSocketValueMenu &src = interface_enum_items[socket_i];
|
||||
if (dst.enum_items != src.enum_items || dst.has_conflict() != src.has_conflict()) {
|
||||
changed = true;
|
||||
if (dst.enum_items) {
|
||||
dst.enum_items->remove_user_and_delete_if_last();
|
||||
}
|
||||
/* Items are moved, no need to change user count. */
|
||||
dst.enum_items = src.enum_items;
|
||||
SET_FLAG_FROM_TEST(dst.runtime_flag, src.has_conflict(), NODE_MENU_ITEMS_CONFLICT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a runtime copy of the DNA enum items.
|
||||
* The runtime items list is shared by sockets.
|
||||
*/
|
||||
const RuntimeNodeEnumItems *create_runtime_enum_items(const NodeEnumDefinition &enum_def)
|
||||
{
|
||||
RuntimeNodeEnumItems *enum_items = new RuntimeNodeEnumItems();
|
||||
enum_items->items.reinitialize(enum_def.items_num);
|
||||
for (const int i : enum_def.items().index_range()) {
|
||||
const NodeEnumItem &src = enum_def.items()[i];
|
||||
RuntimeNodeEnumItem &dst = enum_items->items[i];
|
||||
|
||||
dst.identifier = src.identifier;
|
||||
dst.name = src.name ? src.name : "";
|
||||
dst.description = src.description ? src.description : "";
|
||||
}
|
||||
return enum_items;
|
||||
}
|
||||
|
||||
void clear_enum_reference(bNodeSocket &socket)
|
||||
{
|
||||
BLI_assert(socket.is_available() && socket.type == SOCK_MENU);
|
||||
bNodeSocketValueMenu &default_value = *socket.default_value_typed<bNodeSocketValueMenu>();
|
||||
this->reset_enum_ptr(default_value);
|
||||
default_value.runtime_flag &= ~NODE_MENU_ITEMS_CONFLICT;
|
||||
}
|
||||
|
||||
void update_socket_enum_definition(bNodeSocketValueMenu &dst, const bNodeSocketValueMenu &src)
|
||||
{
|
||||
if (dst.has_conflict()) {
|
||||
/* Target enum already has a conflict. */
|
||||
BLI_assert(dst.enum_items == nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (src.has_conflict()) {
|
||||
/* Target conflict if any source enum has a conflict. */
|
||||
this->reset_enum_ptr(dst);
|
||||
dst.runtime_flag |= NODE_MENU_ITEMS_CONFLICT;
|
||||
}
|
||||
else if (!dst.enum_items) {
|
||||
/* First connection, set the reference. */
|
||||
this->set_enum_ptr(dst, src.enum_items);
|
||||
}
|
||||
else if (src.enum_items && dst.enum_items != src.enum_items) {
|
||||
/* Error if enum ref does not match other connections. */
|
||||
this->reset_enum_ptr(dst);
|
||||
dst.runtime_flag |= NODE_MENU_ITEMS_CONFLICT;
|
||||
}
|
||||
}
|
||||
|
||||
void reset_enum_ptr(bNodeSocketValueMenu &dst)
|
||||
{
|
||||
if (dst.enum_items) {
|
||||
dst.enum_items->remove_user_and_delete_if_last();
|
||||
dst.enum_items = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void set_enum_ptr(bNodeSocketValueMenu &dst, const RuntimeNodeEnumItems *enum_items)
|
||||
{
|
||||
if (dst.enum_items) {
|
||||
dst.enum_items->remove_user_and_delete_if_last();
|
||||
dst.enum_items = nullptr;
|
||||
}
|
||||
if (enum_items) {
|
||||
enum_items->add_user();
|
||||
dst.enum_items = enum_items;
|
||||
}
|
||||
}
|
||||
|
||||
void update_link_validation(bNodeTree &ntree)
|
||||
{
|
||||
const Span<const bNode *> toposort = ntree.toposort_left_to_right();
|
||||
@ -810,12 +1017,24 @@ class NodeTreeMainUpdater {
|
||||
toposort_indices[node.index()] = i;
|
||||
}
|
||||
|
||||
/* Tests if enum references are undefined. */
|
||||
const auto is_invalid_enum_ref = [](const bNodeSocket &socket) -> bool {
|
||||
if (socket.type == SOCK_MENU) {
|
||||
return socket.default_value_typed<bNodeSocketValueMenu>()->enum_items == nullptr;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
|
||||
link->flag |= NODE_LINK_VALID;
|
||||
if (!link->fromsock->is_available() || !link->tosock->is_available()) {
|
||||
link->flag &= ~NODE_LINK_VALID;
|
||||
continue;
|
||||
}
|
||||
if (is_invalid_enum_ref(*link->fromsock) || is_invalid_enum_ref(*link->tosock)) {
|
||||
link->flag &= ~NODE_LINK_VALID;
|
||||
continue;
|
||||
}
|
||||
const bNode &from_node = *link->fromnode;
|
||||
const bNode &to_node = *link->tonode;
|
||||
if (toposort_indices[from_node.index()] > toposort_indices[to_node.index()]) {
|
||||
@ -837,8 +1056,8 @@ class NodeTreeMainUpdater {
|
||||
{
|
||||
tree.ensure_topology_cache();
|
||||
|
||||
/* Compute a hash that represents the node topology connected to the output. This always has to
|
||||
* be updated even if it is not used to detect changes right now. Otherwise
|
||||
/* Compute a hash that represents the node topology connected to the output. This always has
|
||||
* to be updated even if it is not used to detect changes right now. Otherwise
|
||||
* #btree.runtime.output_topology_hash will go out of date. */
|
||||
const Vector<const bNodeSocket *> tree_output_sockets = this->find_output_sockets(tree);
|
||||
const uint32_t old_topology_hash = tree.runtime->output_topology_hash;
|
||||
@ -852,8 +1071,8 @@ class NodeTreeMainUpdater {
|
||||
* be used without causing updates all the time currently. In the future we could try to
|
||||
* handle other drivers better as well.
|
||||
* Note that this optimization only works in practice when the depsgraph didn't also get a
|
||||
* copy-on-write tag for the node tree (which happens when changing node properties). It does
|
||||
* work in a few situations like adding reroutes and duplicating nodes though. */
|
||||
* copy-on-write tag for the node tree (which happens when changing node properties). It
|
||||
* does work in a few situations like adding reroutes and duplicating nodes though. */
|
||||
LISTBASE_FOREACH (const FCurve *, fcurve, &adt->drivers) {
|
||||
const ChannelDriver *driver = fcurve->driver;
|
||||
const StringRef expression = driver->expression;
|
||||
@ -876,7 +1095,8 @@ class NodeTreeMainUpdater {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* The topology hash can only be used when only topology-changing operations have been done. */
|
||||
/* The topology hash can only be used when only topology-changing operations have been done.
|
||||
*/
|
||||
if (tree.runtime->changed_flag ==
|
||||
(tree.runtime->changed_flag & (NTREE_CHANGED_LINK | NTREE_CHANGED_REMOVED_NODE)))
|
||||
{
|
||||
@ -929,15 +1149,15 @@ class NodeTreeMainUpdater {
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a hash that changes when the node tree topology connected to an output node changes.
|
||||
* Adding reroutes does not have an effect on the hash.
|
||||
* Computes a hash that changes when the node tree topology connected to an output node
|
||||
* changes. Adding reroutes does not have an effect on the hash.
|
||||
*/
|
||||
uint32_t get_combined_socket_topology_hash(const bNodeTree &tree,
|
||||
Span<const bNodeSocket *> sockets)
|
||||
{
|
||||
if (tree.has_available_link_cycle()) {
|
||||
/* Return dummy value when the link has any cycles. The algorithm below could be improved to
|
||||
* handle cycles more gracefully. */
|
||||
/* Return dummy value when the link has any cycles. The algorithm below could be improved
|
||||
* to handle cycles more gracefully. */
|
||||
return 0;
|
||||
}
|
||||
Array<uint32_t> hashes = this->get_socket_topology_hashes(tree, sockets);
|
||||
@ -1121,8 +1341,8 @@ class NodeTreeMainUpdater {
|
||||
}
|
||||
}
|
||||
}
|
||||
/* The Normal node has a special case, because the value stored in the first output socket
|
||||
* is used as input in the node. */
|
||||
/* The Normal node has a special case, because the value stored in the first output
|
||||
* socket is used as input in the node. */
|
||||
if (node.type == SH_NODE_NORMAL && socket.index() == 1) {
|
||||
BLI_assert(STREQ(socket.name, "Dot"));
|
||||
const bNodeSocket &normal_output = node.output_socket(0);
|
||||
|
@ -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 */
|
||||
LukasTonne marked this conversation as resolved
Outdated
Jacques Lucke
commented
Wrong comment. Wrong comment.
|
||||
{0.40, 0.40, 0.40, 1.0}, /* SOCK_MENU */
|
||||
};
|
||||
|
||||
/* Callback for colors that does not depend on the socket pointer argument to get the type. */
|
||||
@ -1233,6 +1235,7 @@ static const SocketColorFn std_node_socket_color_funcs[] = {
|
||||
std_node_socket_color_fn<SOCK_TEXTURE>,
|
||||
std_node_socket_color_fn<SOCK_MATERIAL>,
|
||||
std_node_socket_color_fn<SOCK_ROTATION>,
|
||||
std_node_socket_color_fn<SOCK_MENU>,
|
||||
};
|
||||
|
||||
/* draw function for file output node sockets,
|
||||
@ -1371,6 +1374,27 @@ static void std_node_socket_draw(
|
||||
|
||||
break;
|
||||
}
|
||||
case SOCK_MENU: {
|
||||
const bNodeSocketValueMenu *default_value =
|
||||
sock->default_value_typed<bNodeSocketValueMenu>();
|
||||
if (default_value->enum_items) {
|
||||
if (default_value->enum_items->items.is_empty()) {
|
||||
uiLayout *row = uiLayoutSplit(layout, 0.4f, false);
|
||||
uiItemL(row, text, ICON_NONE);
|
||||
uiItemL(row, "No Items", ICON_NONE);
|
||||
}
|
||||
else {
|
||||
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
|
||||
}
|
||||
}
|
||||
else if (default_value->has_conflict()) {
|
||||
uiItemL(layout, IFACE_("Menu Error"), ICON_ERROR);
|
||||
}
|
||||
else {
|
||||
uiItemL(layout, IFACE_("Menu Undefined"), ICON_QUESTION);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_OBJECT: {
|
||||
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, ICON_NONE);
|
||||
break;
|
||||
@ -1498,6 +1522,10 @@ static void std_node_socket_interface_draw(ID *id,
|
||||
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
|
||||
break;
|
||||
}
|
||||
case SOCK_MENU: {
|
||||
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
|
||||
break;
|
||||
}
|
||||
case SOCK_SHADER:
|
||||
case SOCK_GEOMETRY:
|
||||
break;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
This file should go in the
blender::bke
namespaceI tried that, problem is we can't reference types in a namespace from DNA ...
You can, it's done a fair amount actually. See
MeshRuntimeHandle
for an example.Ah forgot about that trick.