Blender V5.0
node_geo_index_switch.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <fmt/format.h>
6
8
9#include "UI_interface_c.hh"
11#include "UI_resources.hh"
12
14#include "NOD_rna_define.hh"
15#include "NOD_socket.hh"
19
20#include "RNA_enum_types.hh"
21#include "RNA_prototypes.hh"
22
23#include "BLO_read_write.hh"
24
27
29
31
32static void draw_item_socket(CustomSocketDrawParams &params, const int index)
33{
34 if (!params.menu_switch_source_by_index_switch) {
35 params.draw_standard(params.layout);
36 return;
37 }
38 const bNode *menu_switch_node = params.menu_switch_source_by_index_switch->lookup_default(
39 &params.node, nullptr);
40 if (!menu_switch_node) {
41 params.draw_standard(params.layout);
42 return;
43 }
44 const auto &menu_switch_storage = *static_cast<const NodeMenuSwitch *>(
45 menu_switch_node->storage);
46 BLI_assert(menu_switch_storage.data_type == SOCK_INT);
47 const NodeEnumItem *found_item = nullptr;
48 for (const int i : IndexRange(menu_switch_storage.enum_definition.items_num)) {
49 const NodeEnumItem &item = menu_switch_storage.enum_definition.items_array[i];
50 const bNodeSocket &menu_switch_input_socket = menu_switch_node->input_socket(1 + i);
51 if (menu_switch_input_socket.is_directly_linked()) {
52 params.draw_standard(params.layout);
53 return;
54 }
55 const auto &menu_switch_input_socket_value = *static_cast<const bNodeSocketValueInt *>(
56 menu_switch_input_socket.default_value);
57 if (menu_switch_input_socket_value.value == index) {
58 if (found_item) {
59 /* Found multiple items, so there is not a unique label for this index. */
60 params.draw_standard(params.layout);
61 return;
62 }
63 found_item = &item;
64 }
65 }
66 if (!found_item) {
67 params.draw_standard(params.layout);
68 return;
69 }
70 const std::string label = fmt::format("{}: {}", index, found_item->name);
71 params.draw_standard(params.layout, label);
72}
73
75{
76 const bNode *node = b.node_or_null();
77 if (!node) {
78 return;
79 }
80 const NodeIndexSwitch &storage = node_storage(*node);
81 const eNodeSocketDatatype data_type = eNodeSocketDatatype(storage.data_type);
82 const bool supports_fields = socket_type_supports_fields(data_type);
83
84 const StructureType structure_type = socket_type_always_single(data_type) ?
85 StructureType::Single :
86 StructureType::Dynamic;
87
88 const Span<IndexSwitchItem> items = storage.items_span();
89 auto &index = b.add_input<decl::Int>("Index").min(0).max(std::max<int>(0, items.size() - 1));
90 if (supports_fields) {
91 index.supports_field().structure_type(structure_type);
92 }
93
94 for (const int i : items.index_range()) {
95 const std::string identifier = IndexSwitchItemsAccessor::socket_identifier_for_item(items[i]);
96 auto &input = b.add_input(data_type, std::to_string(i), std::move(identifier));
97 input.custom_draw(
98 [index = i](CustomSocketDrawParams &params) { draw_item_socket(params, index); });
99 if (supports_fields) {
100 input.supports_field();
101 }
102 /* Labels are ugly in combination with data-block pickers and are usually disabled. */
103 input.optional_label(ELEM(data_type, SOCK_OBJECT, SOCK_IMAGE, SOCK_COLLECTION, SOCK_MATERIAL));
104 input.structure_type(structure_type);
105 }
106
107 auto &output = b.add_output(data_type, "Output");
108 if (supports_fields) {
109 output.dependent_field().reference_pass_all();
110 }
112 output.propagate_all();
113 }
115 output.reference_pass_all();
116 }
117 output.structure_type(structure_type);
118
119 b.add_input<decl::Extend>("", "__extend__").custom_draw([](CustomSocketDrawParams &params) {
120 uiLayout &layout = params.layout;
122 PointerRNA op_ptr = layout.op("node.index_switch_item_add", "", ICON_ADD);
123 RNA_int_set(&op_ptr, "node_identifier", params.node.identifier);
124 });
125}
126
127static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
128{
129 layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
130}
131
133{
134 bNode &node = *static_cast<bNode *>(ptr->data);
135 NodeIndexSwitch &storage = node_storage(node);
136 if (uiLayout *panel = layout->panel(C, "index_switch_items", false, IFACE_("Items"))) {
137 panel->op("node.index_switch_item_add", IFACE_("Add Item"), ICON_ADD);
138 uiLayout *col = &panel->column(false);
139 for (const int i : IndexRange(storage.items_num)) {
140 uiLayout *row = &col->row(false);
141 row->label(node.input_socket(i + 1).name, ICON_NONE);
142 PointerRNA op_ptr = row->op("node.index_switch_item_remove", "", ICON_REMOVE);
143 RNA_int_set(&op_ptr, "index", i);
144 }
145 }
146}
147
149{
151 ot, "Add Item", __func__, "Add an item to the index switch");
152}
153
155{
157 ot, "Remove Item", __func__, "Remove an item from the index switch");
158}
159
165
166static void node_init(bNodeTree * /*tree*/, bNode *node)
167{
169 data->data_type = SOCK_GEOMETRY;
170 data->next_identifier = 0;
171
172 BLI_assert(data->items == nullptr);
173 const int default_items_num = 2;
174 data->items = MEM_calloc_arrayN<IndexSwitchItem>(default_items_num, __func__);
175 for (const int i : IndexRange(default_items_num)) {
176 data->items[i].identifier = data->next_identifier++;
177 }
178 data->items_num = default_items_num;
179
180 node->storage = data;
181}
182
184{
185 if (params.in_out() == SOCK_OUT) {
186 params.add_item(IFACE_("Output"), [](LinkSearchOpParams &params) {
187 bNode &node = params.add_node("GeometryNodeIndexSwitch");
188 node_storage(node).data_type = params.socket.type;
189 params.update_and_connect_available_socket(node, "Output");
190 });
191 }
192 else {
193 const eNodeSocketDatatype other_type = eNodeSocketDatatype(params.other_socket().type);
194 if (params.node_tree().typeinfo->validate_link(other_type, SOCK_INT)) {
195 params.add_item(IFACE_("Index"), [](LinkSearchOpParams &params) {
196 bNode &node = params.add_node("GeometryNodeIndexSwitch");
197 params.update_and_connect_available_socket(node, "Index");
198 });
199 }
200 }
201}
202
203constexpr int value_inputs_start = 1;
204
205class IndexSwitchFunction : public mf::MultiFunction {
206 mf::Signature signature_;
207 Array<std::string> debug_names_;
208
209 public:
210 IndexSwitchFunction(const CPPType &type, const int items_num)
211 {
212 mf::SignatureBuilder builder{"Index Switch", signature_};
213 builder.single_input<int>("Index");
214 debug_names_.reinitialize(items_num);
215 for (const int i : IndexRange(items_num)) {
216 debug_names_[i] = std::to_string(i);
217 builder.single_input(debug_names_[i].c_str(), type);
218 }
219 builder.single_output("Output", type);
220 this->set_signature(&signature_);
221 }
222
223 void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
224 {
225 const int inputs_num = signature_.params.size() - 2;
226 const VArray<int> indices = params.readonly_single_input<int>(0, "Index");
227
228 GMutableSpan output = params.uninitialized_single_output(
229 signature_.params.index_range().last(), "Output");
230 const CPPType &type = output.type();
231
232 if (const std::optional<int> i = indices.get_if_single()) {
233 if (IndexRange(inputs_num).contains(*i)) {
234 const GVArray inputs = params.readonly_single_input(value_inputs_start + *i);
235 inputs.materialize_to_uninitialized(mask, output.data());
236 }
237 else {
238 type.fill_construct_indices(type.default_value(), output.data(), mask);
239 }
240 return;
241 }
242
243 /* Use one extra mask at the end for invalid indices. */
244 const int invalid_index = inputs_num;
245 IndexMaskMemory memory;
246 Array<IndexMask> masks(inputs_num + 1);
248 mask,
249 memory,
250 [&](const int64_t i) {
251 const int index = indices[i];
252 return IndexRange(inputs_num).contains(index) ? index : invalid_index;
253 },
254 masks);
255
256 for (const int i : IndexRange(inputs_num)) {
257 if (!masks[i].is_empty()) {
258 const GVArray inputs = params.readonly_single_input(value_inputs_start + i);
259 inputs.materialize_to_uninitialized(masks[i], output.data());
260 }
261 }
262
263 type.fill_construct_indices(type.default_value(), output.data(), masks[invalid_index]);
264 }
265
267 {
268 ExecutionHints hints;
269 hints.allocates_array = true;
270 return hints;
271 }
272};
273
275 private:
276 const bNode &node_;
277 bool can_be_field_ = false;
278 const CPPType *field_base_type_;
279
280 public:
283 : node_(node)
284 {
285 const NodeIndexSwitch &storage = node_storage(node);
286 const eNodeSocketDatatype data_type = eNodeSocketDatatype(storage.data_type);
287 const bNodeSocket &index_socket = node.input_socket(0);
288 const bNodeSocket &output_socket = node.output_socket(0);
289 const CPPType &cpp_type = CPPType::get<SocketValueVariant>();
290
291 debug_name_ = node.name;
292 can_be_field_ = socket_type_supports_fields(data_type);
293 field_base_type_ = output_socket.typeinfo->base_cpp_type;
294
295 MutableSpan<int> lf_index_by_bsocket = lf_graph_info.mapping.lf_index_by_bsocket;
296
297 lf_index_by_bsocket[index_socket.index_in_tree()] = inputs_.append_and_get_index_as(
299 lf_index_by_bsocket[output_socket.index_in_tree()] = outputs_.append_and_get_index_as(
300 "Value", cpp_type);
301
302 for (const int i : storage.items_span().index_range()) {
303 const bNodeSocket &input = node.input_socket(value_inputs_start + i);
304 lf_index_by_bsocket[input.index_in_tree()] = inputs_.append_and_get_index_as(
305 input.identifier, cpp_type, lf::ValueUsage::Maybe);
306 }
307 }
308
309 void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
310 {
311 SocketValueVariant index_variant = params.get_input<SocketValueVariant>(0);
312 if (index_variant.is_context_dependent_field() && can_be_field_) {
313 this->execute_field(index_variant.get<Field<int>>(), params);
314 }
315 else {
316 this->execute_single(index_variant.get<int>(), params);
317 }
318 }
319
320 int values_num() const
321 {
322 return inputs_.size() - value_inputs_start;
323 }
324
325 void execute_single(const int index, lf::Params &params) const
326 {
327 const int values_num = this->values_num();
328 for (const int i : IndexRange(values_num)) {
329 if (i != index) {
330 params.set_input_unused(value_inputs_start + i);
331 }
332 }
333
334 /* Check for an invalid index. */
335 if (!IndexRange(values_num).contains(index)) {
337 return;
338 }
339
340 /* Request input and try again if unavailable. */
341 void *value_to_forward = params.try_get_input_data_ptr_or_request(index + value_inputs_start);
342 if (value_to_forward == nullptr) {
343 return;
344 }
345
346 const CPPType &type = *outputs_[0].type;
347 void *output_ptr = params.get_output_data_ptr(0);
348 type.move_construct(value_to_forward, output_ptr);
349 params.output_set(0);
350 }
351
353 {
354 const int values_num = this->values_num();
356 for (const int i : IndexRange(values_num)) {
357 input_values[i] = params.try_get_input_data_ptr_or_request<SocketValueVariant>(
359 }
360 if (input_values.as_span().contains(nullptr)) {
361 /* Try again when inputs are available. */
362 return;
363 }
364
365 Vector<GField> input_fields({std::move(index)});
366 for (const int i : IndexRange(values_num)) {
367 input_fields.append(input_values[i]->extract<GField>());
368 }
369
370 std::unique_ptr<mf::MultiFunction> switch_fn = std::make_unique<IndexSwitchFunction>(
371 *field_base_type_, values_num);
372 GField output_field(FieldOperation::from(std::move(switch_fn), std::move(input_fields)));
373
374 void *output_ptr = params.get_output_data_ptr(0);
375 SocketValueVariant::ConstructIn(output_ptr, std::move(output_field));
376 params.output_set(0);
377 }
378};
379
380static void node_rna(StructRNA *srna)
381{
383 srna,
384 "data_type",
385 "Data Type",
386 "",
390 [](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
391 *r_free = true;
393 [](const EnumPropertyItem &item) -> bool {
394 return ELEM(item.value,
396 SOCK_INT,
402 SOCK_RGBA,
408 SOCK_MENU,
411 });
412 });
413}
414
420
421static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
422{
423 const NodeIndexSwitch &src_storage = node_storage(*src_node);
424 auto *dst_storage = MEM_dupallocN<NodeIndexSwitch>(__func__, src_storage);
425 dst_node->storage = dst_storage;
426
428}
429
435
436static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
437{
439}
440
441static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
442{
444}
445
447 const bNode &node,
448 const bNodeSocket & /*output_socket*/)
449{
450 const NodeIndexSwitch &src_storage = node_storage(node);
451 if (src_storage.items_num == 0) {
452 return nullptr;
453 }
454 /* Default to the 0 input. */
455 return &node.input_socket(1);
456}
457
458static void register_node()
459{
460 static blender::bke::bNodeType ntype;
461
462 geo_node_type_base(&ntype, "GeometryNodeIndexSwitch", GEO_NODE_INDEX_SWITCH);
463 ntype.ui_name = "Index Switch";
464 ntype.ui_description = "Choose between an arbitrary number of values with an index";
465 ntype.enum_name_legacy = "INDEX_SWITCH";
467 ntype.declare = node_declare;
468 ntype.initfunc = node_init;
480
481 node_rna(ntype.rna_ext.srna);
482}
484
485} // namespace blender::nodes::node_geo_index_switch_cc
486
487namespace blender::nodes {
488
489std::unique_ptr<LazyFunction> get_index_switch_node_lazy_function(
490 const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
491{
492 using namespace node_geo_index_switch_cc;
494 return std::make_unique<LazyFunctionForIndexSwitchNode>(node, lf_graph_info);
495}
496
497StructRNA *IndexSwitchItemsAccessor::item_srna = &RNA_IndexSwitchItem;
498
500{
501}
502
506
507} // namespace blender::nodes
508
509blender::Span<IndexSwitchItem> NodeIndexSwitch::items_span() const
510{
511 return blender::Span<IndexSwitchItem>(items, items_num);
512}
513
514blender::MutableSpan<IndexSwitchItem> NodeIndexSwitch::items_span()
515{
516 return blender::MutableSpan<IndexSwitchItem>(items, items_num);
517}
#define NODE_CLASS_CONVERTER
Definition BKE_node.hh:453
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1240
#define GEO_NODE_INDEX_SWITCH
#define BLI_assert(a)
Definition BLI_assert.h:46
#define ELEM(...)
#define IFACE_(msgid)
@ SOCK_OUT
eNodeSocketDatatype
@ SOCK_INT
@ SOCK_VECTOR
@ SOCK_CLOSURE
@ SOCK_BOOLEAN
@ SOCK_MATERIAL
@ SOCK_MATRIX
@ SOCK_FLOAT
@ SOCK_IMAGE
@ SOCK_COLLECTION
@ SOCK_BUNDLE
@ SOCK_GEOMETRY
@ SOCK_ROTATION
@ SOCK_OBJECT
@ SOCK_STRING
@ SOCK_RGBA
@ SOCK_MENU
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_storage_enum_accessors(member)
#define C
Definition RandGen.cpp:29
#define UI_ITEM_NONE
__forceinline float extract(const int4 &b)
Definition binning.cpp:27
BMesh const char void * data
long long int int64_t
Span< T > as_span() const
Definition BLI_array.hh:243
static const CPPType & get()
void fill_construct_indices(const void *value, void *dst, const IndexMask &mask) const
const void * default_value() const
void move_construct(void *src, void *dst) const
static void from_groups(const IndexMask &universe, IndexMaskMemory &memory, Fn &&get_group_index, MutableSpan< IndexMask > r_masks)
constexpr bool contains(int64_t value) const
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
void set_signature(const Signature *signature)
static std::shared_ptr< FieldOperation > from(std::shared_ptr< const mf::MultiFunction > function, Vector< GField > inputs={})
Definition FN_field.hh:242
static SocketValueVariant & ConstructIn(void *ptr, T &&value)
void call(const IndexMask &mask, mf::Params params, mf::Context) const override
LazyFunctionForIndexSwitchNode(const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
void execute_impl(lf::Params &params, const lf::Context &) const override
static ushort indices[]
uint col
#define input
#define output
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
bool can_contain_reference(eNodeSocketDatatype socket_type)
bool can_contain_referenced_data(eNodeSocketDatatype socket_type)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:5414
static void node_copy_storage(bNodeTree *, bNode *dst_node, const bNode *src_node)
static void node_init(bNodeTree *, bNode *node)
static void node_blend_read(bNodeTree &, bNode &node, BlendDataReader &reader)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
static void NODE_OT_index_switch_item_add(wmOperatorType *ot)
static void NODE_OT_index_switch_item_remove(wmOperatorType *ot)
static bool node_insert_link(bke::NodeInsertLinkParams &params)
static void node_declare(NodeDeclarationBuilder &b)
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static void node_blend_write(const bNodeTree &, const bNode &node, BlendWriter &writer)
static void draw_item_socket(CustomSocketDrawParams &params, const int index)
static const bNodeSocket * node_internally_linked_input(const bNodeTree &, const bNode &node, const bNodeSocket &)
void remove_item_by_index(wmOperatorType *ot, const char *name, const char *idname, const char *description)
void add_item(wmOperatorType *ot, const char *name, const char *idname, const char *description)
void blend_write(BlendWriter *writer, const bNode &node)
void blend_read_data(BlendDataReader *reader, bNode &node)
void copy_array(const bNode &src_node, bNode &dst_node)
bool try_add_item_via_any_extend_socket(bNodeTree &ntree, bNode &extend_node, bNode &storage_node, bNodeLink &link, const std::optional< StringRef > socket_identifier=std::nullopt, typename Accessor::ItemT **r_new_item=nullptr)
std::unique_ptr< LazyFunction > get_index_switch_node_lazy_function(const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
PropertyRNA * RNA_def_node_enum(StructRNA *srna, const char *identifier, const char *ui_name, const char *ui_description, const EnumPropertyItem *static_items, const EnumRNAAccessors accessors, std::optional< int > default_value, const EnumPropertyItemFunc item_func, const bool allow_animation)
bool socket_type_supports_fields(const eNodeSocketDatatype socket_type)
void set_default_remaining_node_outputs(lf::Params &params, const bNode &node)
bool socket_type_always_single(const eNodeSocketDatatype socket_type)
const EnumPropertyItem * enum_items_filter(const EnumPropertyItem *original_item_array, FunctionRef< bool(const EnumPropertyItem &item)> fn)
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
static blender::bke::bNodeSocketTemplate inputs[]
void RNA_int_set(PointerRNA *ptr, const char *name, int value)
const EnumPropertyItem rna_enum_node_socket_data_type_items[]
#define min(a, b)
Definition sort.cc:36
StructRNA * srna
bNodeSocketTypeHandle * typeinfo
void * default_value
char name[64]
int16_t type_legacy
void * storage
Defines a node type.
Definition BKE_node.hh:238
NodeInternallyLinkedInputFunction internally_linked_input
Definition BKE_node.hh:384
NodeBlendWriteFunction blend_write_storage_content
Definition BKE_node.hh:390
std::string ui_description
Definition BKE_node.hh:244
NodeBlendDataReadFunction blend_data_read_storage_content
Definition BKE_node.hh:391
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
void(* draw_buttons_ex)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:261
const char * enum_name_legacy
Definition BKE_node.hh:247
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
bool(* insert_link)(NodeInsertLinkParams &params)
Definition BKE_node.hh:333
NodeGatherSocketLinkOperationsFunction gather_link_search_ops
Definition BKE_node.hh:378
NodeDeclareFunction declare
Definition BKE_node.hh:362
void(* register_operators)()
Definition BKE_node.hh:417
bool ignore_inferred_input_socket_visibility
Definition BKE_node.hh:422
static std::string socket_identifier_for_item(const IndexSwitchItem &item)
static void blend_read_data_item(BlendDataReader *reader, ItemT &item)
static void blend_write_item(BlendWriter *writer, const ItemT &item)
PanelLayout panel(const bContext *C, blender::StringRef idname, bool default_closed)
void label(blender::StringRef name, int icon)
void emboss_set(blender::ui::EmbossType emboss)
PointerRNA op(wmOperatorType *ot, std::optional< blender::StringRef > name, int icon, blender::wm::OpCallContext context, eUI_Item_Flag flag)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4238
wmOperatorType * ot
Definition wm_files.cc:4237
void WM_operatortype_append(void(*opfunc)(wmOperatorType *))