Geometry Nodes: Index switch node
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:
Hans Goudey 2023-11-22 16:11:32 +01:00 committed by Hans Goudey
parent dfc00e8a18
commit 8d5aa6eed4
16 changed files with 746 additions and 25 deletions

View File

@ -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,
)

View File

@ -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)

View File

@ -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,

View File

@ -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
/** \} */

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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()) {

View File

@ -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")

View File

@ -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

View File

@ -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

View 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 &params)
{
if (params.in_out() == SOCK_OUT) {
params.add_item(IFACE_("Output"), [](LinkSearchOpParams &params) {
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 &params) {
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 &params, 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 &params) 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 &params) 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);
}

View File

@ -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()) {

View File

@ -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 &params, 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>(

View File

@ -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