Geometry Nodes: Index switch node
All checks were successful
buildbot/vdev-code-daily-coordinator Build done.
All checks were successful
buildbot/vdev-code-daily-coordinator Build done.
Add an "Index Switch" node which is meant as a simpler version of the "Menu Switch" from #113445 that doesn't allow naming items or displaying them in a dropdown, but still allows choosing between an arbitrary number of items, unlike the regular "Switch" node. Even when the Menu Switch is included (which should be in the same release as this), it may still be helpful to have explicit mapping of indices, and a fair amount of the internals can be shared anyway. Pull Request: #115250
This commit is contained in:
parent
dfc00e8a18
commit
8d5aa6eed4
@ -4,6 +4,7 @@
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import IntProperty
|
||||
|
||||
from bpy.app.translations import pgettext_data as data_
|
||||
|
||||
@ -459,6 +460,55 @@ class RepeatZoneItemMoveOperator(RepeatZoneOperator, ZoneMoveItemOperator, Opera
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
|
||||
def _editable_tree_with_active_node_type(context, node_type):
|
||||
space = context.space_data
|
||||
# Needs active node editor and a tree.
|
||||
if not space or space.type != 'NODE_EDITOR' or not space.edit_tree or space.edit_tree.library:
|
||||
return False
|
||||
node = context.active_node
|
||||
if node is None or node.bl_idname != node_type:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class IndexSwitchItemAddOperator(Operator):
|
||||
"""Add an item to the index switch"""
|
||||
bl_idname = "node.index_switch_item_add"
|
||||
bl_label = "Add Item"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return _editable_tree_with_active_node_type(context, 'GeometryNodeIndexSwitch')
|
||||
|
||||
def execute(self, context):
|
||||
node = context.active_node
|
||||
node.index_switch_items.new()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class IndexSwitchItemRemoveOperator(Operator):
|
||||
"""Remove an item from the index switch"""
|
||||
bl_idname = "node.index_switch_item_remove"
|
||||
bl_label = "Remove Item"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
index: IntProperty(
|
||||
name="Index",
|
||||
description="Index of item to remove",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return _editable_tree_with_active_node_type(context, 'GeometryNodeIndexSwitch')
|
||||
|
||||
def execute(self, context):
|
||||
node = context.active_node
|
||||
items = node.index_switch_items
|
||||
items.remove(items[self.index])
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
classes = (
|
||||
NewGeometryNodesModifier,
|
||||
NewGeometryNodeTreeAssign,
|
||||
@ -470,4 +520,6 @@ classes = (
|
||||
RepeatZoneItemAddOperator,
|
||||
RepeatZoneItemRemoveOperator,
|
||||
RepeatZoneItemMoveOperator,
|
||||
IndexSwitchItemAddOperator,
|
||||
IndexSwitchItemRemoveOperator,
|
||||
)
|
||||
|
@ -546,6 +546,7 @@ class NODE_MT_category_GEO_UTILITIES(Menu):
|
||||
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")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeIndexSwitch")
|
||||
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
|
||||
|
||||
|
||||
|
@ -1145,6 +1145,34 @@ class NODE_PT_repeat_zone_items(Panel):
|
||||
layout.prop(output_node, "inspection_index")
|
||||
|
||||
|
||||
class NODE_PT_index_switch_node_items(Panel):
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Node"
|
||||
bl_label = "Index Switch"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
snode = context.space_data
|
||||
if snode is None:
|
||||
return False
|
||||
node = context.active_node
|
||||
print()
|
||||
if node is None or node.bl_idname != 'GeometryNodeIndexSwitch':
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
node = context.active_node
|
||||
layout.operator("node.index_switch_item_add", icon='ADD', text="Add Item")
|
||||
col = layout.column()
|
||||
for i, item in enumerate(node.index_switch_items):
|
||||
row = col.row()
|
||||
row.label(text=node.inputs[i + 1].name)
|
||||
row.operator("node.index_switch_item_remove", icon='REMOVE', text="").index = i
|
||||
|
||||
|
||||
# Grease Pencil properties
|
||||
class NODE_PT_annotation(AnnotationDataPanel, Panel):
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
@ -1213,6 +1241,7 @@ classes = (
|
||||
NODE_UL_simulation_zone_items,
|
||||
NODE_PT_simulation_zone_items,
|
||||
NODE_UL_repeat_zone_items,
|
||||
NODE_PT_index_switch_node_items,
|
||||
NODE_PT_repeat_zone_items,
|
||||
NODE_PT_active_node_properties,
|
||||
|
||||
|
@ -1316,6 +1316,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
|
||||
#define GEO_NODE_INPUT_EDGE_SMOOTH 2115
|
||||
#define GEO_NODE_SPLIT_TO_INSTANCES 2116
|
||||
#define GEO_NODE_INPUT_NAMED_LAYER_SELECTION 2117
|
||||
#define GEO_NODE_INDEX_SWITCH 2118
|
||||
|
||||
/** \} */
|
||||
|
||||
|
@ -788,6 +788,9 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
|
||||
if (node->type == GEO_NODE_REPEAT_OUTPUT) {
|
||||
blender::nodes::RepeatItemsAccessor::blend_write(writer, *node);
|
||||
}
|
||||
if (node->type == GEO_NODE_INDEX_SWITCH) {
|
||||
blender::nodes::IndexSwitchItemsAccessor::blend_write(writer, *node);
|
||||
}
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
|
||||
@ -1027,6 +1030,10 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree)
|
||||
blender::nodes::RepeatItemsAccessor::blend_read_data(reader, *node);
|
||||
break;
|
||||
}
|
||||
case GEO_NODE_INDEX_SWITCH: {
|
||||
blender::nodes::IndexSwitchItemsAccessor::blend_read_data(reader, *node);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
@ -1856,6 +1856,27 @@ typedef struct NodeGeometryRepeatOutput {
|
||||
#endif
|
||||
} NodeGeometryRepeatOutput;
|
||||
|
||||
typedef struct IndexSwitchItem {
|
||||
/** Generated unique identifier which stays the same even when the item order or names change. */
|
||||
int identifier;
|
||||
} IndexSwitchItem;
|
||||
|
||||
typedef struct NodeIndexSwitch {
|
||||
IndexSwitchItem *items;
|
||||
int items_num;
|
||||
|
||||
/* #eNodeSocketDataType. */
|
||||
int data_type;
|
||||
/** Identifier to give to the next item. */
|
||||
int next_identifier;
|
||||
|
||||
char _pad[4];
|
||||
#ifdef __cplusplus
|
||||
blender::Span<IndexSwitchItem> items_span() const;
|
||||
blender::MutableSpan<IndexSwitchItem> items_span();
|
||||
#endif
|
||||
} NodeIndexSwitch;
|
||||
|
||||
typedef struct NodeGeometryDistributePointsInVolume {
|
||||
/** #GeometryNodePointDistributeVolumeMode. */
|
||||
uint8_t mode;
|
||||
|
@ -618,6 +618,7 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = {
|
||||
# include "DNA_scene_types.h"
|
||||
# include "WM_api.hh"
|
||||
|
||||
using blender::nodes::IndexSwitchItemsAccessor;
|
||||
using blender::nodes::RepeatItemsAccessor;
|
||||
using blender::nodes::SimulationItemsAccessor;
|
||||
|
||||
@ -3298,7 +3299,16 @@ static void rna_Node_ItemArray_remove(ID *id,
|
||||
{
|
||||
blender::nodes::socket_items::SocketItemsRef ref = Accessor::get_items_from_node(*node);
|
||||
if (item_to_remove < *ref.items || item_to_remove >= *ref.items + *ref.items_num) {
|
||||
BKE_reportf(reports, RPT_ERROR, "Unable to locate item '%s' in node", item_to_remove->name);
|
||||
if constexpr (Accessor::has_name) {
|
||||
char **name_ptr = Accessor::get_name(*item_to_remove);
|
||||
if (name_ptr && *name_ptr) {
|
||||
BKE_reportf(reports, RPT_ERROR, "Unable to locate item '%s' in node", *name_ptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BKE_report(reports, RPT_ERROR, "Unable to locate item in node");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const int remove_index = item_to_remove - *ref.items;
|
||||
@ -3433,6 +3443,19 @@ typename Accessor::ItemT *rna_Node_ItemArray_new_with_socket_and_name(
|
||||
return new_item;
|
||||
}
|
||||
|
||||
static IndexSwitchItem *rna_NodeIndexSwitchItems_new(ID *id, bNode *node, Main *bmain)
|
||||
{
|
||||
IndexSwitchItem *new_item = blender::nodes::socket_items::add_item<IndexSwitchItemsAccessor>(
|
||||
*node);
|
||||
|
||||
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id);
|
||||
BKE_ntree_update_tag_node_property(ntree, node);
|
||||
ED_node_tree_propagate_change(nullptr, bmain, ntree);
|
||||
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
|
||||
|
||||
return new_item;
|
||||
}
|
||||
|
||||
/* ******** Node Socket Types ******** */
|
||||
|
||||
static PointerRNA rna_NodeOutputFile_slot_layer_get(CollectionPropertyIterator *iter)
|
||||
@ -9052,6 +9075,54 @@ static void def_geo_repeat_output(StructRNA *srna)
|
||||
RNA_def_property_update(prop, NC_NODE, "rna_Node_update");
|
||||
}
|
||||
|
||||
static void rna_def_index_switch_item(BlenderRNA *brna)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
StructRNA *srna = RNA_def_struct(brna, "IndexSwitchItem", nullptr);
|
||||
RNA_def_struct_ui_text(srna, "Index Switch Item", "");
|
||||
RNA_def_struct_sdna(srna, "IndexSwitchItem");
|
||||
|
||||
prop = RNA_def_property(srna, "identifier", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_ui_range(prop, 0, INT32_MAX, 1, -1);
|
||||
RNA_def_property_ui_text(prop, "Identifier", "Consistent identifier used for the item");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_update(prop, NC_NODE, "rna_Node_update");
|
||||
}
|
||||
|
||||
static void rna_def_geo_index_switch_items(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
srna = RNA_def_struct(brna, "NodeIndexSwitchItems", nullptr);
|
||||
RNA_def_struct_sdna(srna, "bNode");
|
||||
RNA_def_struct_ui_text(srna, "Items", "Collection of index_switch items");
|
||||
|
||||
func = RNA_def_function(srna, "new", "rna_NodeIndexSwitchItems_new");
|
||||
RNA_def_function_ui_description(func, "Add an item at the end");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
|
||||
/* Return value. */
|
||||
parm = RNA_def_pointer(func, "item", "IndexSwitchItem", "Item", "New item");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
rna_def_node_item_array_common_functions(srna, "IndexSwitchItem", "IndexSwitchItemsAccessor");
|
||||
}
|
||||
|
||||
static void def_geo_index_switch(StructRNA *srna)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
RNA_def_struct_sdna_from(srna, "NodeIndexSwitch", "storage");
|
||||
|
||||
prop = RNA_def_property(srna, "index_switch_items", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_collection_sdna(prop, nullptr, "items", "items_num");
|
||||
RNA_def_property_struct_type(prop, "IndexSwitchItem");
|
||||
RNA_def_property_ui_text(prop, "Items", "");
|
||||
RNA_def_property_srna(prop, "NodeIndexSwitchItems");
|
||||
}
|
||||
|
||||
static void def_geo_curve_handle_type_selection(StructRNA *srna)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
@ -10409,6 +10480,7 @@ void RNA_def_nodetree(BlenderRNA *brna)
|
||||
|
||||
rna_def_simulation_state_item(brna);
|
||||
rna_def_repeat_item(brna);
|
||||
rna_def_index_switch_item(brna);
|
||||
|
||||
# define DefNode(Category, ID, DefFunc, EnumName, StructName, UIName, UIDesc) \
|
||||
{ \
|
||||
@ -10461,6 +10533,7 @@ void RNA_def_nodetree(BlenderRNA *brna)
|
||||
rna_def_cmp_output_file_slot_layer(brna);
|
||||
rna_def_geo_simulation_output_items(brna);
|
||||
rna_def_geo_repeat_output_items(brna);
|
||||
rna_def_geo_index_switch_items(brna);
|
||||
|
||||
rna_def_node_instance_hash(brna);
|
||||
}
|
||||
|
@ -362,6 +362,7 @@ std::unique_ptr<LazyFunction> get_simulation_input_lazy_function(
|
||||
const bNode &node,
|
||||
GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info);
|
||||
std::unique_ptr<LazyFunction> get_switch_node_lazy_function(const bNode &node);
|
||||
std::unique_ptr<LazyFunction> get_index_switch_node_lazy_function(const bNode &node);
|
||||
|
||||
struct FoundNestedNodeID {
|
||||
int id;
|
||||
|
@ -69,7 +69,6 @@ inline void remove_item(T **items,
|
||||
|
||||
const int old_items_num = *items_num;
|
||||
const int new_items_num = old_items_num - 1;
|
||||
const int old_active_index = *active_index;
|
||||
|
||||
T *old_items = *items;
|
||||
T *new_items = MEM_cnew_array<T>(new_items_num, __func__);
|
||||
@ -81,12 +80,15 @@ inline void remove_item(T **items,
|
||||
destruct_item(&old_items[remove_index]);
|
||||
MEM_SAFE_FREE(old_items);
|
||||
|
||||
const int new_active_index = std::max(
|
||||
0, old_active_index == new_items_num ? new_items_num - 1 : old_active_index);
|
||||
|
||||
*items = new_items;
|
||||
*items_num = new_items_num;
|
||||
*active_index = new_active_index;
|
||||
|
||||
if (active_index) {
|
||||
const int old_active_index = active_index ? *active_index : 0;
|
||||
const int new_active_index = std::max(
|
||||
0, old_active_index == new_items_num ? new_items_num - 1 : old_active_index);
|
||||
*active_index = new_active_index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,7 +103,9 @@ inline void clear_items(T **items, int *items_num, int *active_index, void (*des
|
||||
}
|
||||
MEM_SAFE_FREE(*items);
|
||||
*items_num = 0;
|
||||
*active_index = 0;
|
||||
if (active_index) {
|
||||
*active_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -209,16 +213,11 @@ inline void set_item_name_and_make_unique(bNode &node,
|
||||
*item_name = BLI_strdup(unique_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new item at the end with the given socket type and name.
|
||||
*/
|
||||
template<typename Accessor>
|
||||
inline typename Accessor::ItemT *add_item_with_socket_and_name(
|
||||
bNode &node, const eNodeSocketDatatype socket_type, const char *name)
|
||||
namespace detail {
|
||||
|
||||
template<typename Accessor> inline typename Accessor::ItemT &add_item_to_array(bNode &node)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
BLI_assert(Accessor::supports_socket_type(socket_type));
|
||||
|
||||
SocketItemsRef array = Accessor::get_items_from_node(node);
|
||||
|
||||
ItemT *old_items = *array.items;
|
||||
@ -229,13 +228,40 @@ inline typename Accessor::ItemT *add_item_with_socket_and_name(
|
||||
std::copy_n(old_items, old_items_num, new_items);
|
||||
ItemT &new_item = new_items[old_items_num];
|
||||
|
||||
Accessor::init_with_socket_type_and_name(node, new_item, socket_type, name);
|
||||
|
||||
MEM_SAFE_FREE(old_items);
|
||||
*array.items = new_items;
|
||||
*array.items_num = new_items_num;
|
||||
*array.active_index = old_items_num;
|
||||
if (array.active_index) {
|
||||
*array.active_index = old_items_num;
|
||||
}
|
||||
|
||||
return new_item;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Add a new item at the end with the given socket type and name.
|
||||
*/
|
||||
template<typename Accessor>
|
||||
inline typename Accessor::ItemT *add_item_with_socket_and_name(
|
||||
bNode &node, const eNodeSocketDatatype socket_type, const char *name)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
BLI_assert(Accessor::supports_socket_type(socket_type));
|
||||
ItemT &new_item = detail::add_item_to_array<Accessor>(node);
|
||||
Accessor::init_with_socket_type_and_name(node, new_item, socket_type, name);
|
||||
return &new_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new item at the end.
|
||||
*/
|
||||
template<typename Accessor> inline typename Accessor::ItemT *add_item(bNode &node)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
ItemT &new_item = detail::add_item_to_array<Accessor>(node);
|
||||
Accessor::init(node, new_item);
|
||||
return &new_item;
|
||||
}
|
||||
|
||||
@ -262,15 +288,22 @@ template<typename Accessor>
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(src_socket->type);
|
||||
if (!Accessor::supports_socket_type(socket_type)) {
|
||||
return false;
|
||||
|
||||
const ItemT *item = nullptr;
|
||||
if constexpr (Accessor::has_name && Accessor::has_type) {
|
||||
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(src_socket->type);
|
||||
if (!Accessor::supports_socket_type(socket_type)) {
|
||||
return false;
|
||||
}
|
||||
item = add_item_with_socket_and_name<Accessor>(storage_node, socket_type, src_socket->name);
|
||||
}
|
||||
else {
|
||||
item = add_item<Accessor>(storage_node);
|
||||
}
|
||||
const ItemT *item = add_item_with_socket_and_name<Accessor>(
|
||||
storage_node, socket_type, src_socket->name);
|
||||
if (item == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
update_node_declaration_and_sockets(ntree, extend_node);
|
||||
const std::string item_identifier = Accessor::socket_identifier_for_item(*item);
|
||||
if (extend_socket.is_input()) {
|
||||
|
@ -334,6 +334,7 @@ DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE",
|
||||
DefNode(GeometryNode, GEO_NODE_IMAGE_INFO, 0, "IMAGE_INFO", ImageInfo, "Image Info", "Retrieve information about an image")
|
||||
DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture")
|
||||
DefNode(GeometryNode, GEO_NODE_INDEX_OF_NEAREST, 0, "INDEX_OF_NEAREST", IndexOfNearest, "Index of Nearest", "Find the nearest element in a group. Similar to the \"Sample Nearest\" node")
|
||||
DefNode(GeometryNode, GEO_NODE_INDEX_SWITCH, def_geo_index_switch, "INDEX_SWITCH", IndexSwitch, "Index Switch", "Choose between an arbitrary number of values with an index")
|
||||
DefNode(GeometryNode, GEO_NODE_IMAGE, def_geo_image, "IMAGE", InputImage, "Image", "Input image")
|
||||
DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_HANDLES, 0, "INPUT_CURVE_HANDLES", InputCurveHandlePositions, "Curve Handle Positions", "Retrieve the position of each Bézier control point's handles")
|
||||
DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_TILT, 0, "INPUT_CURVE_TILT", InputCurveTilt, "Curve Tilt", "Retrieve the angle at each control point used to twist the curve's normal around its tangent")
|
||||
|
@ -22,6 +22,8 @@ struct SimulationItemsAccessor {
|
||||
static StructRNA *item_srna;
|
||||
static int node_type;
|
||||
static constexpr const char *node_idname = "GeometryNodeSimulationOutput";
|
||||
static constexpr bool has_type = true;
|
||||
static constexpr bool has_name = true;
|
||||
|
||||
static socket_items::SocketItemsRef<NodeSimulationItem> get_items_from_node(bNode &node)
|
||||
{
|
||||
@ -84,6 +86,8 @@ struct RepeatItemsAccessor {
|
||||
static StructRNA *item_srna;
|
||||
static int node_type;
|
||||
static constexpr const char *node_idname = "GeometryNodeRepeatOutput";
|
||||
static constexpr bool has_type = true;
|
||||
static constexpr bool has_name = true;
|
||||
|
||||
static socket_items::SocketItemsRef<NodeRepeatItem> get_items_from_node(bNode &node)
|
||||
{
|
||||
@ -141,4 +145,39 @@ struct RepeatItemsAccessor {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) for index
|
||||
* switch items.
|
||||
*/
|
||||
struct IndexSwitchItemsAccessor {
|
||||
using ItemT = IndexSwitchItem;
|
||||
static StructRNA *item_srna;
|
||||
static int node_type;
|
||||
static constexpr const char *node_idname = "GeometryNodeIndexSwitch";
|
||||
static constexpr bool has_type = false;
|
||||
static constexpr bool has_name = false;
|
||||
|
||||
static socket_items::SocketItemsRef<IndexSwitchItem> get_items_from_node(bNode &node)
|
||||
{
|
||||
auto &storage = *static_cast<NodeIndexSwitch *>(node.storage);
|
||||
return {&storage.items, &storage.items_num, nullptr};
|
||||
}
|
||||
static void copy_item(const IndexSwitchItem &src, IndexSwitchItem &dst)
|
||||
{
|
||||
dst = src;
|
||||
}
|
||||
static void destruct_item(IndexSwitchItem * /*item*/) {}
|
||||
static void blend_write(BlendWriter *writer, const bNode &node);
|
||||
static void blend_read_data(BlendDataReader *reader, bNode &node);
|
||||
static void init(bNode &node, IndexSwitchItem &item)
|
||||
{
|
||||
auto &storage = *static_cast<NodeIndexSwitch *>(node.storage);
|
||||
item.identifier = storage.next_identifier++;
|
||||
}
|
||||
static std::string socket_identifier_for_item(const IndexSwitchItem &item)
|
||||
{
|
||||
return "Item_" + std::to_string(item.identifier);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
@ -82,6 +82,7 @@ set(SRC
|
||||
nodes/node_geo_image_info.cc
|
||||
nodes/node_geo_image_texture.cc
|
||||
nodes/node_geo_index_of_nearest.cc
|
||||
nodes/node_geo_index_switch.cc
|
||||
nodes/node_geo_input_curve_handles.cc
|
||||
nodes/node_geo_input_curve_tilt.cc
|
||||
nodes/node_geo_input_edge_smooth.cc
|
||||
|
364
source/blender/nodes/geometry/nodes/node_geo_index_switch.cc
Normal file
364
source/blender/nodes/geometry/nodes/node_geo_index_switch.cc
Normal file
@ -0,0 +1,364 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#include "BLI_array_utils.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 "NOD_zone_socket_items.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "BKE_node_socket_value_cpp_type.hh"
|
||||
|
||||
namespace blender::nodes::node_geo_index_switch_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeIndexSwitch)
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
const bNode *node = b.node_or_null();
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
const NodeIndexSwitch &storage = node_storage(*node);
|
||||
const eNodeSocketDatatype data_type = eNodeSocketDatatype(storage.data_type);
|
||||
const bool supports_fields = socket_type_supports_fields(data_type);
|
||||
|
||||
auto &index = b.add_input<decl::Int>("Index");
|
||||
if (supports_fields) {
|
||||
index.supports_field();
|
||||
}
|
||||
|
||||
const Span<IndexSwitchItem> items = storage.items_span();
|
||||
for (const int i : items.index_range()) {
|
||||
const std::string identifier = IndexSwitchItemsAccessor::socket_identifier_for_item(items[i]);
|
||||
auto &input = b.add_input(data_type, std::to_string(i), 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();
|
||||
}
|
||||
|
||||
b.add_input<decl::Extend>("", "__extend__");
|
||||
}
|
||||
|
||||
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeIndexSwitch *data = MEM_cnew<NodeIndexSwitch>(__func__);
|
||||
data->data_type = SOCK_GEOMETRY;
|
||||
data->next_identifier = 0;
|
||||
|
||||
BLI_assert(data->items == nullptr);
|
||||
data->items = MEM_cnew_array<IndexSwitchItem>(1, __func__);
|
||||
data->items[0].identifier = data->next_identifier++;
|
||||
data->items_num = 1;
|
||||
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
if (params.in_out() == SOCK_OUT) {
|
||||
params.add_item(IFACE_("Output"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("GeometryNodeIndexSwitch");
|
||||
node_storage(node).data_type = params.socket.type;
|
||||
params.update_and_connect_available_socket(node, "Output");
|
||||
});
|
||||
}
|
||||
else {
|
||||
const eNodeSocketDatatype other_type = eNodeSocketDatatype(params.other_socket().type);
|
||||
if (params.node_tree().typeinfo->validate_link(other_type, SOCK_INT)) {
|
||||
params.add_item(IFACE_("Index"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("GeometryNodeIndexSwitch");
|
||||
params.update_and_connect_available_socket(node, "Index");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int value_inputs_start = 1;
|
||||
|
||||
class IndexSwitchFunction : public mf::MultiFunction {
|
||||
mf::Signature signature_;
|
||||
Array<std::string> debug_names_;
|
||||
|
||||
public:
|
||||
IndexSwitchFunction(const CPPType &type, const int items_num)
|
||||
{
|
||||
mf::SignatureBuilder builder{"Index Switch", signature_};
|
||||
builder.single_input<int>("Index");
|
||||
debug_names_.reinitialize(items_num);
|
||||
for (const int i : IndexRange(items_num)) {
|
||||
debug_names_[i] = std::to_string(i);
|
||||
builder.single_input(debug_names_[i].c_str(), type);
|
||||
}
|
||||
builder.single_output("Output", type);
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const
|
||||
{
|
||||
const int inputs_num = signature_.params.size() - 2;
|
||||
const VArray<int> indices = params.readonly_single_input<int>(0, "Index");
|
||||
|
||||
GMutableSpan output = params.uninitialized_single_output(
|
||||
signature_.params.index_range().last(), "Output");
|
||||
const CPPType &type = output.type();
|
||||
|
||||
if (const std::optional<int> i = indices.get_if_single()) {
|
||||
if (IndexRange(inputs_num).contains(*i)) {
|
||||
const GVArray inputs = params.readonly_single_input(value_inputs_start + *i);
|
||||
inputs.materialize_to_uninitialized(mask, output.data());
|
||||
}
|
||||
else {
|
||||
type.fill_construct_indices(type.default_value(), output.data(), mask);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Use one extra mask at the end for invalid indices. */
|
||||
const int invalid_index = inputs_num;
|
||||
IndexMaskMemory memory;
|
||||
Array<IndexMask> masks(inputs_num + 1);
|
||||
IndexMask::from_groups<int64_t>(
|
||||
mask,
|
||||
memory,
|
||||
[&](const int64_t i) {
|
||||
const int index = indices[i];
|
||||
return IndexRange(inputs_num).contains(index) ? index : invalid_index;
|
||||
},
|
||||
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]);
|
||||
}
|
||||
|
||||
ExecutionHints get_execution_hints() const override
|
||||
{
|
||||
ExecutionHints hints;
|
||||
hints.allocates_array = true;
|
||||
return hints;
|
||||
}
|
||||
};
|
||||
|
||||
class LazyFunctionForIndexSwitchNode : public LazyFunction {
|
||||
private:
|
||||
bool can_be_field_ = false;
|
||||
|
||||
public:
|
||||
LazyFunctionForIndexSwitchNode(const bNode &node)
|
||||
{
|
||||
const NodeIndexSwitch &storage = node_storage(node);
|
||||
const eNodeSocketDatatype data_type = eNodeSocketDatatype(storage.data_type);
|
||||
can_be_field_ = socket_type_supports_fields(data_type);
|
||||
|
||||
const CPPType &cpp_type = *node.output_socket(0).typeinfo->geometry_nodes_cpp_type;
|
||||
|
||||
debug_name_ = node.name;
|
||||
inputs_.append_as("Index", CPPType::get<ValueOrField<int>>(), lf::ValueUsage::Used);
|
||||
for (const int i : storage.items_span().index_range()) {
|
||||
const bNodeSocket &input = node.input_socket(value_inputs_start + i);
|
||||
inputs_.append_as(input.identifier, cpp_type, lf::ValueUsage::Maybe);
|
||||
}
|
||||
outputs_.append_as("Value", cpp_type);
|
||||
}
|
||||
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override
|
||||
{
|
||||
const ValueOrField<int> index = params.get_input<ValueOrField<int>>(0);
|
||||
if (index.is_field() && can_be_field_) {
|
||||
Field<int> index_field = index.as_field();
|
||||
if (index_field.node().depends_on_input()) {
|
||||
this->execute_field(index.as_field(), params);
|
||||
}
|
||||
else {
|
||||
this->execute_single(fn::evaluate_constant_field(index_field), params);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this->execute_single(index.as_value(), params);
|
||||
}
|
||||
}
|
||||
|
||||
int values_num() const
|
||||
{
|
||||
return inputs_.size() - value_inputs_start;
|
||||
}
|
||||
|
||||
void execute_single(const int index, lf::Params ¶ms) const
|
||||
{
|
||||
const int values_num = this->values_num();
|
||||
for (const int i : IndexRange(values_num)) {
|
||||
if (i != index) {
|
||||
params.set_input_unused(value_inputs_start + i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for an invalid index. */
|
||||
if (!IndexRange(values_num).contains(index)) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Request input and try again if unavailable. */
|
||||
void *value_to_forward = params.try_get_input_data_ptr_or_request(index + value_inputs_start);
|
||||
if (value_to_forward == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const CPPType &type = *outputs_[0].type;
|
||||
void *output_ptr = params.get_output_data_ptr(0);
|
||||
type.move_construct(value_to_forward, output_ptr);
|
||||
params.output_set(0);
|
||||
}
|
||||
|
||||
void execute_field(Field<int> index, lf::Params ¶ms) const
|
||||
{
|
||||
const int values_num = this->values_num();
|
||||
Array<void *, 8> input_values(values_num);
|
||||
for (const int i : IndexRange(values_num)) {
|
||||
input_values[i] = params.try_get_input_data_ptr_or_request(value_inputs_start + i);
|
||||
}
|
||||
if (input_values.as_span().contains(nullptr)) {
|
||||
/* Try again when inputs are available. */
|
||||
return;
|
||||
}
|
||||
|
||||
const CPPType &type = *outputs_[0].type;
|
||||
const auto &value_or_field_type = *bke::ValueOrFieldCPPType::get_from_self(type);
|
||||
const CPPType &value_type = value_or_field_type.value;
|
||||
|
||||
Vector<GField> input_fields({std::move(index)});
|
||||
for (const int i : IndexRange(values_num)) {
|
||||
input_fields.append(value_or_field_type.as_field(input_values[i]));
|
||||
}
|
||||
|
||||
std::unique_ptr<mf::MultiFunction> switch_fn = std::make_unique<IndexSwitchFunction>(
|
||||
value_type, values_num);
|
||||
GField output_field(FieldOperation::Create(std::move(switch_fn), std::move(input_fields)));
|
||||
|
||||
void *output_ptr = params.get_output_data_ptr(0);
|
||||
value_or_field_type.construct_from_field(output_ptr, std::move(output_field));
|
||||
params.output_set(0);
|
||||
}
|
||||
};
|
||||
|
||||
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 ELEM(item.value,
|
||||
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_free_storage(bNode *node)
|
||||
{
|
||||
socket_items::destruct_array<IndexSwitchItemsAccessor>(*node);
|
||||
MEM_freeN(node->storage);
|
||||
}
|
||||
|
||||
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
|
||||
{
|
||||
const NodeIndexSwitch &src_storage = node_storage(*src_node);
|
||||
auto *dst_storage = MEM_new<NodeIndexSwitch>(__func__, src_storage);
|
||||
dst_node->storage = dst_storage;
|
||||
|
||||
socket_items::copy_array<IndexSwitchItemsAccessor>(*src_node, *dst_node);
|
||||
}
|
||||
|
||||
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
|
||||
{
|
||||
return socket_items::try_add_item_via_any_extend_socket<IndexSwitchItemsAccessor>(
|
||||
*ntree, *node, *node, *link);
|
||||
}
|
||||
|
||||
static void register_node()
|
||||
{
|
||||
static bNodeType ntype;
|
||||
|
||||
geo_node_type_base(&ntype, GEO_NODE_INDEX_SWITCH, "Index Switch", NODE_CLASS_CONVERTER);
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.insert_link = node_insert_link;
|
||||
node_type_storage(&ntype, "NodeIndexSwitch", 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_index_switch_cc
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
std::unique_ptr<LazyFunction> get_index_switch_node_lazy_function(const bNode &node)
|
||||
{
|
||||
using namespace node_geo_index_switch_cc;
|
||||
BLI_assert(node.type == GEO_NODE_INDEX_SWITCH);
|
||||
return std::make_unique<LazyFunctionForIndexSwitchNode>(node);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
blender::Span<IndexSwitchItem> NodeIndexSwitch::items_span() const
|
||||
{
|
||||
return blender::Span<IndexSwitchItem>(items, items_num);
|
||||
}
|
||||
|
||||
blender::MutableSpan<IndexSwitchItem> NodeIndexSwitch::items_span()
|
||||
{
|
||||
return blender::MutableSpan<IndexSwitchItem>(items, items_num);
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
#include "NOD_socket.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
@ -160,8 +161,7 @@ class LazyFunctionForSwitchNode : public LazyFunction {
|
||||
{
|
||||
const NodeSwitch &storage = node_storage(node);
|
||||
const eNodeSocketDatatype data_type = eNodeSocketDatatype(storage.input_type);
|
||||
can_be_field_ = ELEM(
|
||||
data_type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA, SOCK_ROTATION);
|
||||
can_be_field_ = socket_type_supports_fields(data_type);
|
||||
|
||||
const bNodeSocketType *socket_type = nullptr;
|
||||
for (const bNodeSocket *socket : node.output_sockets()) {
|
||||
|
@ -1127,6 +1127,38 @@ class LazyFunctionForSwitchSocketUsage : public lf::LazyFunction {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Outputs booleans that indicate which inputs of a switch node are used. Note that it's possible
|
||||
* that all inputs are used when the index input is a field.
|
||||
*/
|
||||
class LazyFunctionForIndexSwitchSocketUsage : public lf::LazyFunction {
|
||||
public:
|
||||
LazyFunctionForIndexSwitchSocketUsage(const bNode &bnode)
|
||||
{
|
||||
debug_name_ = "Index Switch Socket Usage";
|
||||
inputs_.append_as("Index", CPPType::get<ValueOrField<int>>());
|
||||
for (const bNodeSocket *socket : bnode.input_sockets().drop_front(1)) {
|
||||
outputs_.append_as(socket->identifier, CPPType::get<bool>());
|
||||
}
|
||||
}
|
||||
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override
|
||||
{
|
||||
const ValueOrField<int> &index = params.get_input<ValueOrField<int>>(0);
|
||||
if (index.is_field()) {
|
||||
for (const int i : outputs_.index_range()) {
|
||||
params.set_output(i, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const int value = index.as_value();
|
||||
for (const int i : outputs_.index_range()) {
|
||||
params.set_output(i, i == value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a field as input and extracts the set of anonymous attributes that it references.
|
||||
*/
|
||||
@ -3024,6 +3056,10 @@ struct GeometryNodesLazyFunctionBuilder {
|
||||
this->build_switch_node(bnode, graph_params);
|
||||
break;
|
||||
}
|
||||
case GEO_NODE_INDEX_SWITCH: {
|
||||
this->build_index_switch_node(bnode, graph_params);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (node_type->geometry_node_execute) {
|
||||
this->build_geometry_node(bnode, graph_params);
|
||||
@ -3566,6 +3602,53 @@ struct GeometryNodesLazyFunctionBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
void build_index_switch_node(const bNode &bnode, BuildGraphParams &graph_params)
|
||||
{
|
||||
std::unique_ptr<LazyFunction> lazy_function = get_index_switch_node_lazy_function(bnode);
|
||||
lf::FunctionNode &lf_node = graph_params.lf_graph.add_function(*lazy_function);
|
||||
scope_.add(std::move(lazy_function));
|
||||
|
||||
for (const int i : bnode.input_sockets().drop_back(1).index_range()) {
|
||||
graph_params.lf_inputs_by_bsocket.add(&bnode.input_socket(i), &lf_node.input(i));
|
||||
mapping_->bsockets_by_lf_socket_map.add(&lf_node.input(i), &bnode.input_socket(i));
|
||||
}
|
||||
|
||||
graph_params.lf_output_by_bsocket.add(&bnode.output_socket(0), &lf_node.output(0));
|
||||
mapping_->bsockets_by_lf_socket_map.add(&lf_node.output(0), &bnode.output_socket(0));
|
||||
|
||||
this->build_index_switch_node_socket_usage(bnode, graph_params);
|
||||
}
|
||||
|
||||
void build_index_switch_node_socket_usage(const bNode &bnode, BuildGraphParams &graph_params)
|
||||
{
|
||||
const bNodeSocket &index_socket = bnode.input_socket(0);
|
||||
const int items_num = bnode.input_sockets().size() - 1;
|
||||
|
||||
lf::OutputSocket *output_is_used = graph_params.usage_by_bsocket.lookup_default(
|
||||
&bnode.output_socket(0), nullptr);
|
||||
if (output_is_used == nullptr) {
|
||||
return;
|
||||
}
|
||||
graph_params.usage_by_bsocket.add(&index_socket, output_is_used);
|
||||
if (index_socket.is_directly_linked()) {
|
||||
/* The condition input is dynamic, so the usage of the other inputs is as well. */
|
||||
auto usage_fn = std::make_unique<LazyFunctionForIndexSwitchSocketUsage>(bnode);
|
||||
lf::Node &lf_node = graph_params.lf_graph.add_function(*usage_fn);
|
||||
scope_.add(std::move(usage_fn));
|
||||
|
||||
graph_params.lf_inputs_by_bsocket.add(&index_socket, &lf_node.input(0));
|
||||
for (const int i : IndexRange(items_num)) {
|
||||
graph_params.usage_by_bsocket.add(&bnode.input_socket(i + 1), &lf_node.output(i));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const int index = index_socket.default_value_typed<bNodeSocketValueInt>()->value;
|
||||
if (IndexRange(items_num).contains(index)) {
|
||||
graph_params.usage_by_bsocket.add(&bnode.input_socket(index + 1), output_is_used);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void build_undefined_node(const bNode &bnode, BuildGraphParams &graph_params)
|
||||
{
|
||||
auto &lazy_function = scope_.construct<LazyFunctionForUndefinedNode>(
|
||||
|
@ -56,4 +56,19 @@ void RepeatItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node)
|
||||
}
|
||||
}
|
||||
|
||||
StructRNA *IndexSwitchItemsAccessor ::item_srna = &RNA_IndexSwitchItem;
|
||||
int IndexSwitchItemsAccessor::node_type = GEO_NODE_INDEX_SWITCH;
|
||||
|
||||
void IndexSwitchItemsAccessor::blend_write(BlendWriter *writer, const bNode &node)
|
||||
{
|
||||
const auto &storage = *static_cast<const NodeIndexSwitch *>(node.storage);
|
||||
BLO_write_struct_array(writer, IndexSwitchItem, storage.items_num, storage.items);
|
||||
}
|
||||
|
||||
void IndexSwitchItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node)
|
||||
{
|
||||
auto &storage = *static_cast<NodeIndexSwitch *>(node.storage);
|
||||
BLO_read_data_address(reader, &storage.items);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
Loading…
x
Reference in New Issue
Block a user