Blender V4.3
node_geo_simulation.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 "BLI_math_matrix.hh"
6#include "BLI_string.h"
7#include "BLI_string_utf8.h"
8#include "BLI_string_utils.hh"
9#include "BLI_task.hh"
10
12#include "BKE_attribute_math.hh"
16#include "BKE_context.hh"
17#include "BKE_curves.hh"
18#include "BKE_instances.hh"
19#include "BKE_modifier.hh"
23#include "BKE_object.hh"
24#include "BKE_scene.hh"
25#include "BKE_screen.hh"
26
28
29#include "UI_interface.hh"
30
31#include "NOD_common.h"
32#include "NOD_geo_bake.hh"
33#include "NOD_geo_simulation.hh"
34#include "NOD_geometry.hh"
36#include "NOD_socket.hh"
38
39#include "DNA_curves_types.h"
40#include "DNA_mesh_types.h"
41#include "DNA_modifier_types.h"
43#include "DNA_space_types.h"
44
45#include "ED_node.hh"
46
47#include "RNA_access.hh"
48#include "RNA_define.hh"
49#include "RNA_prototypes.hh"
50
51#include "MOD_nodes.hh"
52
53#include "BLT_translation.hh"
54
55#include "GEO_mix_geometries.hh"
56
57#include "WM_api.hh"
58
59#include "BLO_read_write.hh"
60
61#include "node_geometry_util.hh"
62
64
66{
67 const char *socket_idname = bke::node_static_socket_type(socket_type, 0);
68 const bke::bNodeSocketType *typeinfo = bke::node_socket_type_find(socket_idname);
69 BLI_assert(typeinfo);
71 return *typeinfo->geometry_nodes_cpp_type;
72}
73
78
80 const Span<NodeSimulationItem> node_simulation_items)
81{
83 const int items_num = node_simulation_items.size();
84 config.domains.resize(items_num);
85 config.names.resize(items_num);
86 config.types.resize(items_num);
87 config.geometries_by_attribute.resize(items_num);
88
89 int last_geometry_index = -1;
90 for (const int item_i : node_simulation_items.index_range()) {
91 const NodeSimulationItem &item = node_simulation_items[item_i];
92 config.types[item_i] = eNodeSocketDatatype(item.socket_type);
93 config.names[item_i] = item.name;
94 config.domains[item_i] = AttrDomain(item.attribute_domain);
95 if (item.socket_type == SOCK_GEOMETRY) {
96 last_geometry_index = item_i;
97 }
98 else if (last_geometry_index != -1) {
99 config.geometries_by_attribute[item_i].append(last_geometry_index);
100 }
101 }
102 return config;
103}
104
105static std::shared_ptr<AttributeFieldInput> make_attribute_field(
106 const Object &self_object,
107 const ComputeContext &compute_context,
108 const bNode &node,
109 const NodeSimulationItem &item,
110 const CPPType &type)
111{
112 std::string attribute_name = bke::hash_to_anonymous_attribute_name(
113 self_object.id.name, compute_context.hash(), node.identifier, item.identifier);
114 std::string socket_inspection_name = make_anonymous_attribute_socket_inspection_string(
115 node.label_or_name(), item.name);
116 return std::make_shared<AttributeFieldInput>(
117 std::move(attribute_name), type, std::move(socket_inspection_name));
118}
119
120static void move_simulation_state_to_values(const Span<NodeSimulationItem> node_simulation_items,
121 bke::bake::BakeState zone_state,
122 const Object &self_object,
123 const ComputeContext &compute_context,
124 const bNode &node,
125 bke::bake::BakeDataBlockMap *data_block_map,
126 Span<void *> r_output_values)
127{
128 const bke::bake::BakeSocketConfig config = make_bake_socket_config(node_simulation_items);
130 for (const NodeSimulationItem &item : node_simulation_items) {
131 std::unique_ptr<bke::bake::BakeItem> *bake_item = zone_state.items_by_id.lookup_ptr(
132 item.identifier);
133 bake_items.append(bake_item ? bake_item->get() : nullptr);
134 }
135
137 bake_items,
138 config,
139 data_block_map,
140 [&](const int i, const CPPType &type) {
142 self_object, compute_context, node, node_simulation_items[i], type);
143 },
144 r_output_values);
145}
146
147static void copy_simulation_state_to_values(const Span<NodeSimulationItem> node_simulation_items,
148 const bke::bake::BakeStateRef &zone_state,
149 const Object &self_object,
150 const ComputeContext &compute_context,
151 const bNode &node,
152 bke::bake::BakeDataBlockMap *data_block_map,
153 Span<void *> r_output_values)
154{
155 const bke::bake::BakeSocketConfig config = make_bake_socket_config(node_simulation_items);
157 for (const NodeSimulationItem &item : node_simulation_items) {
158 const bke::bake::BakeItem *const *bake_item = zone_state.items_by_id.lookup_ptr(
159 item.identifier);
160 bake_items.append(bake_item ? *bake_item : nullptr);
161 }
162
164 bake_items,
165 config,
166 data_block_map,
167 [&](const int i, const CPPType &type) {
169 self_object, compute_context, node, node_simulation_items[i], type);
170 },
171 r_output_values);
172}
173
175 const Span<NodeSimulationItem> node_simulation_items,
176 const Span<void *> input_values,
177 bke::bake::BakeDataBlockMap *data_block_map)
178{
179 const bke::bake::BakeSocketConfig config = make_bake_socket_config(node_simulation_items);
180
182 bke::bake::move_socket_values_to_bake_items(input_values, config, data_block_map);
183
184 bke::bake::BakeState bake_state;
185 for (const int i : node_simulation_items.index_range()) {
186 const NodeSimulationItem &item = node_simulation_items[i];
187 std::unique_ptr<bke::bake::BakeItem> &bake_item = bake_items[i];
188 if (bake_item) {
189 bake_state.items_by_id.add_new(item.identifier, std::move(bake_item));
190 }
191 }
192 return bake_state;
193}
194
195static void draw_simulation_state_item(uiList * /*ui_list*/,
196 const bContext *C,
197 uiLayout *layout,
198 PointerRNA * /*idataptr*/,
199 PointerRNA *itemptr,
200 int /*icon*/,
201 PointerRNA * /*active_dataptr*/,
202 const char * /*active_propname*/,
203 int /*index*/,
204 int /*flt_flag*/)
205{
206 uiLayout *row = uiLayoutRow(layout, true);
207 float4 color;
208 RNA_float_get_array(itemptr, "color", color);
209 uiTemplateNodeSocket(row, const_cast<bContext *>(C), color);
211 uiItemR(row, itemptr, "name", UI_ITEM_NONE, "", ICON_NONE);
212}
213
214static void draw_simulation_state(const bContext *C,
215 uiLayout *layout,
216 bNodeTree &ntree,
217 bNode &output_node)
218{
219 static const uiListType *state_items_list = []() {
220 uiListType *list = MEM_cnew<uiListType>(__func__);
221 STRNCPY(list->idname, "DATA_UL_simulation_zone_state");
222 list->draw_item = draw_simulation_state_item;
223 WM_uilisttype_add(list);
224 return list;
225 }();
226
227 PointerRNA output_node_ptr = RNA_pointer_create(&ntree.id, &RNA_Node, &output_node);
228
229 if (uiLayout *panel = uiLayoutPanel(
230 C, layout, "simulation_state_items", false, TIP_("Simulation State")))
231 {
232 uiLayout *row = uiLayoutRow(panel, false);
233 uiTemplateList(row,
234 C,
235 state_items_list->idname,
236 "",
237 &output_node_ptr,
238 "state_items",
239 &output_node_ptr,
240 "active_index",
241 nullptr,
242 3,
243 5,
245 0,
247
248 {
249 uiLayout *ops_col = uiLayoutColumn(row, false);
250 {
251 uiLayout *add_remove_col = uiLayoutColumn(ops_col, true);
252 uiItemO(add_remove_col, "", ICON_ADD, "node.simulation_zone_item_add");
253 uiItemO(add_remove_col, "", ICON_REMOVE, "node.simulation_zone_item_remove");
254 }
255 {
256 uiLayout *up_down_col = uiLayoutColumn(ops_col, true);
258 up_down_col, "node.simulation_zone_item_move", "", ICON_TRIA_UP, "direction", 0);
260 up_down_col, "node.simulation_zone_item_move", "", ICON_TRIA_DOWN, "direction", 1);
261 }
262 }
263
264 auto &storage = *static_cast<NodeGeometrySimulationOutput *>(output_node.storage);
265 if (storage.active_index >= 0 && storage.active_index < storage.items_num) {
266 NodeSimulationItem &active_item = storage.items[storage.active_index];
268 output_node_ptr.owner_id, SimulationItemsAccessor::item_srna, &active_item);
269 uiLayoutSetPropSep(panel, true);
270 uiLayoutSetPropDecorate(panel, false);
271 uiItemR(panel, &item_ptr, "socket_type", UI_ITEM_NONE, nullptr, ICON_NONE);
273 uiItemR(panel, &item_ptr, "attribute_domain", UI_ITEM_NONE, nullptr, ICON_NONE);
274 }
275 }
276 }
277}
278
280{
282 ot, "Remove Simulation Zone Item", __func__, "Remove active simulation zone item");
283}
284
286{
288 ot, "Add Simulation Zone Item", __func__, "Add simulation zone item");
289}
290
292{
294 ot, "Move Simulation Zone Item", __func__, "Move active simulation zone item");
295}
296
298static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_node_ptr)
299{
300 bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(current_node_ptr->owner_id);
301 bNode *current_node = static_cast<bNode *>(current_node_ptr->data);
302
303 const bke::bNodeTreeZones *zones = ntree.zones();
304 if (!zones) {
305 return;
306 }
307 const bke::bNodeTreeZone *zone = zones->get_zone_by_node(current_node->identifier);
308 if (!zone) {
309 return;
310 }
311 if (!zone->output_node) {
312 return;
313 }
314 bNode &output_node = const_cast<bNode &>(*zone->output_node);
315
316 BakeDrawContext ctx;
317 if (!get_bake_draw_context(C, output_node, ctx)) {
318 return;
319 }
320
321 draw_simulation_state(C, layout, ntree, output_node);
322
323 uiLayoutSetPropSep(layout, true);
324 uiLayoutSetPropDecorate(layout, false);
325
327
328 {
329 uiLayout *col = uiLayoutColumn(layout, false);
330 draw_bake_button_row(ctx, col, true);
331 if (const std::optional<std::string> bake_state_str = get_bake_state_string(ctx)) {
332 uiLayout *row = uiLayoutRow(col, true);
333 uiItemL(row, bake_state_str->c_str(), ICON_NONE);
334 }
335 }
336 draw_common_bake_settings(C, ctx, layout);
337 draw_data_blocks(C, layout, ctx.bake_rna);
338}
339
340namespace sim_input_node {
341
343
345 const bNode &node_;
346 int32_t output_node_id_;
347 Span<NodeSimulationItem> simulation_items_;
348
349 public:
351 const bNode &node,
352 GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
353 : node_(node)
354 {
355 debug_name_ = "Simulation Input";
356 output_node_id_ = node_storage(node).output_node_id;
357 const bNode &output_node = *node_tree.node_by_id(output_node_id_);
358 const NodeGeometrySimulationOutput &storage = *static_cast<NodeGeometrySimulationOutput *>(
359 output_node.storage);
360 simulation_items_ = {storage.items, storage.items_num};
361
362 MutableSpan<int> lf_index_by_bsocket = own_lf_graph_info.mapping.lf_index_by_bsocket;
363 lf_index_by_bsocket[node.output_socket(0).index_in_tree()] = outputs_.append_and_get_index_as(
364 "Delta Time", CPPType::get<SocketValueVariant>());
365
366 for (const int i : simulation_items_.index_range()) {
367 const NodeSimulationItem &item = simulation_items_[i];
368 const bNodeSocket &input_bsocket = node.input_socket(i);
369 const bNodeSocket &output_bsocket = node.output_socket(i + 1);
370
371 const CPPType &type = get_simulation_item_cpp_type(item);
372
373 lf_index_by_bsocket[input_bsocket.index_in_tree()] = inputs_.append_and_get_index_as(
374 item.name, type, lf::ValueUsage::Maybe);
375 lf_index_by_bsocket[output_bsocket.index_in_tree()] = outputs_.append_and_get_index_as(
376 item.name, type);
377 }
378 }
379
380 void execute_impl(lf::Params &params, const lf::Context &context) const final
381 {
382 const GeoNodesLFUserData &user_data = *static_cast<const GeoNodesLFUserData *>(
383 context.user_data);
384 if (!user_data.call_data->simulation_params) {
386 return;
387 }
388 if (!user_data.call_data->self_object()) {
389 /* Self object is currently required for creating anonymous attribute names. */
391 return;
392 }
393 std::optional<FoundNestedNodeID> found_id = find_nested_node_id(user_data, output_node_id_);
394 if (!found_id) {
396 return;
397 }
398 if (found_id->is_in_loop) {
400 return;
401 }
402 SimulationZoneBehavior *zone_behavior = user_data.call_data->simulation_params->get(
403 found_id->id);
404 if (!zone_behavior) {
406 return;
407 }
408 sim_input::Behavior &input_behavior = zone_behavior->input;
409 float delta_time = 0.0f;
410 if (auto *info = std::get_if<sim_input::OutputCopy>(&input_behavior)) {
411 delta_time = info->delta_time;
413 params, user_data, zone_behavior->data_block_map, info->state);
414 }
415 else if (auto *info = std::get_if<sim_input::OutputMove>(&input_behavior)) {
416 delta_time = info->delta_time;
418 params, user_data, zone_behavior->data_block_map, std::move(info->state));
419 }
420 else if (std::get_if<sim_input::PassThrough>(&input_behavior)) {
421 delta_time = 0.0f;
422 this->pass_through(params, user_data, zone_behavior->data_block_map);
423 }
424 else {
426 }
427 if (!params.output_was_set(0)) {
428 params.set_output(0, SocketValueVariant(delta_time));
429 }
430 }
431
436
438 const GeoNodesLFUserData &user_data,
439 bke::bake::BakeDataBlockMap *data_block_map,
440 const bke::bake::BakeStateRef &zone_state) const
441 {
442 Array<void *> outputs(simulation_items_.size());
443 for (const int i : simulation_items_.index_range()) {
444 outputs[i] = params.get_output_data_ptr(i + 1);
445 }
446 copy_simulation_state_to_values(simulation_items_,
447 zone_state,
448 *user_data.call_data->self_object(),
449 *user_data.compute_context,
450 node_,
451 data_block_map,
452 outputs);
453 for (const int i : simulation_items_.index_range()) {
454 params.output_set(i + 1);
455 }
456 }
457
459 const GeoNodesLFUserData &user_data,
460 bke::bake::BakeDataBlockMap *data_block_map,
461 bke::bake::BakeState zone_state) const
462 {
463 Array<void *> outputs(simulation_items_.size());
464 for (const int i : simulation_items_.index_range()) {
465 outputs[i] = params.get_output_data_ptr(i + 1);
466 }
467 move_simulation_state_to_values(simulation_items_,
468 std::move(zone_state),
469 *user_data.call_data->self_object(),
470 *user_data.compute_context,
471 node_,
472 data_block_map,
473 outputs);
474 for (const int i : simulation_items_.index_range()) {
475 params.output_set(i + 1);
476 }
477 }
478
480 const GeoNodesLFUserData &user_data,
481 bke::bake::BakeDataBlockMap *data_block_map) const
482 {
483 Array<void *> input_values(inputs_.size());
484 for (const int i : inputs_.index_range()) {
485 input_values[i] = params.try_get_input_data_ptr_or_request(i);
486 }
487 if (input_values.as_span().contains(nullptr)) {
488 /* Wait for inputs to be computed. */
489 return;
490 }
491 /* Instead of outputting the initial values directly, convert them to a simulation state and
492 * then back. This ensures that some geometry processing happens on the data consistently (e.g.
493 * removing anonymous attributes). */
495 simulation_items_, input_values, data_block_map);
496 this->output_simulation_state_move(params, user_data, data_block_map, std::move(bake_state));
497 }
498};
499
501{
502 b.use_custom_socket_order();
503 b.allow_any_socket_order();
504 b.add_output<decl::Float>("Delta Time");
505
506 const bNode *node = b.node_or_null();
507 const bNodeTree *node_tree = b.tree_or_null();
508 if (ELEM(nullptr, node, node_tree)) {
509 return;
510 }
511
512 const bNode *output_node = node_tree->node_by_id(node_storage(*node).output_node_id);
513 if (!output_node) {
514 return;
515 }
516 const auto &output_storage = *static_cast<const NodeGeometrySimulationOutput *>(
517 output_node->storage);
518
519 for (const int i : IndexRange(output_storage.items_num)) {
520 const NodeSimulationItem &item = output_storage.items[i];
521 const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
522 if (socket_type == SOCK_GEOMETRY && i > 0) {
523 b.add_separator();
524 }
525 const StringRef name = item.name;
526 const std::string identifier = SimulationItemsAccessor::socket_identifier_for_item(item);
527 auto &input_decl = b.add_input(socket_type, name, identifier)
528 .socket_name_ptr(
529 &node_tree->id, SimulationItemsAccessor::item_srna, &item, "name");
530 auto &output_decl = b.add_output(socket_type, name, identifier).align_with_previous();
531 if (socket_type_supports_fields(socket_type)) {
532 input_decl.supports_field();
533 output_decl.dependent_field({input_decl.index()});
534 }
535 }
536 b.add_input<decl::Extend>("", "__extend__");
537 b.add_output<decl::Extend>("", "__extend__").align_with_previous();
538}
539
540static void node_init(bNodeTree * /*tree*/, bNode *node)
541{
542 NodeGeometrySimulationInput *data = MEM_cnew<NodeGeometrySimulationInput>(__func__);
543 /* Needs to be initialized for the node to work. */
544 data->output_node_id = 0;
545 node->storage = data;
546}
547
548static void node_label(const bNodeTree * /*ntree*/,
549 const bNode * /*node*/,
550 char *label,
551 const int label_maxncpy)
552{
553 BLI_strncpy_utf8(label, IFACE_("Simulation"), label_maxncpy);
554}
555
556static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
557{
558 bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id);
559 if (!output_node) {
560 return true;
561 }
563 *ntree, *node, *output_node, *link);
564}
565
566static void node_register()
567{
568 static blender::bke::bNodeType ntype;
569 geo_node_type_base(&ntype, GEO_NODE_SIMULATION_INPUT, "Simulation Input", NODE_CLASS_INTERFACE);
570 ntype.initfunc = node_init;
571 ntype.declare = node_declare;
572 ntype.labelfunc = node_label;
574 ntype.gather_link_search_ops = nullptr;
575 ntype.no_muting = true;
578 "NodeGeometrySimulationInput",
582}
584
585} // namespace sim_input_node
586
587namespace sim_output_node {
588
590
592 const bNode &node_;
593 Span<NodeSimulationItem> simulation_items_;
594 int skip_input_index_;
600 int skip_inputs_offset_;
605 int solve_inputs_offset_;
606
607 public:
609 GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
610 : node_(node)
611 {
612 debug_name_ = "Simulation Output";
613 const NodeGeometrySimulationOutput &storage = node_storage(node);
614 simulation_items_ = {storage.items, storage.items_num};
615
616 MutableSpan<int> lf_index_by_bsocket = own_lf_graph_info.mapping.lf_index_by_bsocket;
617
618 const bNodeSocket &skip_bsocket = node.input_socket(0);
619 skip_input_index_ = inputs_.append_and_get_index_as(
620 "Skip", *skip_bsocket.typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Maybe);
621 lf_index_by_bsocket[skip_bsocket.index_in_tree()] = skip_input_index_;
622
623 skip_inputs_offset_ = inputs_.size();
624
625 /* Add the skip inputs that are linked to the simulation input node. */
626 for (const int i : simulation_items_.index_range()) {
627 const NodeSimulationItem &item = simulation_items_[i];
628 const CPPType &type = get_simulation_item_cpp_type(item);
629 inputs_.append_as(item.name, type, lf::ValueUsage::Maybe);
630 }
631
632 solve_inputs_offset_ = inputs_.size();
633
634 /* Add the solve inputs that correspond to the simulation state inputs in the UI. */
635 for (const int i : simulation_items_.index_range()) {
636 const NodeSimulationItem &item = simulation_items_[i];
637 const bNodeSocket &input_bsocket = node.input_socket(i + 1);
638 const bNodeSocket &output_bsocket = node.output_socket(i);
639
640 const CPPType &type = get_simulation_item_cpp_type(item);
641
642 lf_index_by_bsocket[input_bsocket.index_in_tree()] = inputs_.append_and_get_index_as(
643 item.name, type, lf::ValueUsage::Maybe);
644 lf_index_by_bsocket[output_bsocket.index_in_tree()] = outputs_.append_and_get_index_as(
645 item.name, type);
646 }
647 }
648
649 void execute_impl(lf::Params &params, const lf::Context &context) const final
650 {
651 GeoNodesLFUserData &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
652 if (!user_data.call_data->self_object()) {
653 /* The self object is currently required for generating anonymous attribute names. */
655 return;
656 }
657 if (!user_data.call_data->simulation_params) {
659 return;
660 }
661 std::optional<FoundNestedNodeID> found_id = find_nested_node_id(user_data, node_.identifier);
662 if (!found_id) {
664 return;
665 }
666 if (found_id->is_in_loop) {
668 return;
669 }
670 SimulationZoneBehavior *zone_behavior = user_data.call_data->simulation_params->get(
671 found_id->id);
672 if (!zone_behavior) {
674 return;
675 }
676 sim_output::Behavior &output_behavior = zone_behavior->output;
677 if (auto *info = std::get_if<sim_output::ReadSingle>(&output_behavior)) {
678 this->output_cached_state(params, user_data, zone_behavior->data_block_map, info->state);
679 }
680 else if (auto *info = std::get_if<sim_output::ReadInterpolated>(&output_behavior)) {
682 zone_behavior->data_block_map,
683 *user_data.call_data->self_object(),
684 *user_data.compute_context,
685 info->prev_state,
686 info->next_state,
687 info->mix_factor);
688 }
689 else if (std::get_if<sim_output::PassThrough>(&output_behavior)) {
690 this->pass_through(params, user_data, zone_behavior->data_block_map);
691 }
692 else if (auto *info = std::get_if<sim_output::StoreNewState>(&output_behavior)) {
693 this->store_new_state(params, user_data, zone_behavior->data_block_map, *info);
694 }
695 else {
697 }
698 }
699
704
706 GeoNodesLFUserData &user_data,
707 bke::bake::BakeDataBlockMap *data_block_map,
708 const bke::bake::BakeStateRef &state) const
709 {
710 Array<void *> output_values(simulation_items_.size());
711 for (const int i : simulation_items_.index_range()) {
712 output_values[i] = params.get_output_data_ptr(i);
713 }
714 copy_simulation_state_to_values(simulation_items_,
715 state,
716 *user_data.call_data->self_object(),
717 *user_data.compute_context,
718 node_,
719 data_block_map,
720 output_values);
721 for (const int i : simulation_items_.index_range()) {
722 params.output_set(i);
723 }
724 }
725
727 bke::bake::BakeDataBlockMap *data_block_map,
728 const Object &self_object,
729 const ComputeContext &compute_context,
730 const bke::bake::BakeStateRef &prev_state,
732 const float mix_factor) const
733 {
734 Array<void *> output_values(simulation_items_.size());
735 for (const int i : simulation_items_.index_range()) {
736 output_values[i] = params.get_output_data_ptr(i);
737 }
738 copy_simulation_state_to_values(simulation_items_,
739 prev_state,
740 self_object,
741 compute_context,
742 node_,
743 data_block_map,
744 output_values);
745
746 Array<void *> next_values(simulation_items_.size());
747 LinearAllocator<> allocator;
748 for (const int i : simulation_items_.index_range()) {
749 const CPPType &type = *outputs_[i].type;
750 next_values[i] = allocator.allocate(type.size(), type.alignment());
751 }
752 copy_simulation_state_to_values(simulation_items_,
754 self_object,
755 compute_context,
756 node_,
757 data_block_map,
758 next_values);
759
760 for (const int i : simulation_items_.index_range()) {
761 mix_baked_data_item(eNodeSocketDatatype(simulation_items_[i].socket_type),
762 output_values[i],
763 next_values[i],
764 mix_factor);
765 }
766
767 for (const int i : simulation_items_.index_range()) {
768 const CPPType &type = *outputs_[i].type;
769 type.destruct(next_values[i]);
770 }
771
772 for (const int i : simulation_items_.index_range()) {
773 params.output_set(i);
774 }
775 }
776
778 GeoNodesLFUserData &user_data,
779 bke::bake::BakeDataBlockMap *data_block_map) const
780 {
781 std::optional<bke::bake::BakeState> bake_state = this->get_bake_state_from_inputs(
782 params, data_block_map, true);
783 if (!bake_state) {
784 /* Wait for inputs to be computed. */
785 return;
786 }
787
788 Array<void *> output_values(simulation_items_.size());
789 for (const int i : simulation_items_.index_range()) {
790 output_values[i] = params.get_output_data_ptr(i);
791 }
792 move_simulation_state_to_values(simulation_items_,
793 std::move(*bake_state),
794 *user_data.call_data->self_object(),
795 *user_data.compute_context,
796 node_,
797 data_block_map,
798 output_values);
799 for (const int i : simulation_items_.index_range()) {
800 params.output_set(i);
801 }
802 }
803
805 GeoNodesLFUserData &user_data,
806 bke::bake::BakeDataBlockMap *data_block_map,
807 const sim_output::StoreNewState &info) const
808 {
809 const SocketValueVariant *skip_variant =
810 params.try_get_input_data_ptr_or_request<SocketValueVariant>(skip_input_index_);
811 if (skip_variant == nullptr) {
812 /* Wait for skip input to be computed. */
813 return;
814 }
815 const bool skip = skip_variant->get<bool>();
816
817 /* Instead of outputting the values directly, convert them to a bake state and then back.
818 * This ensures that some geometry processing happens on the data consistently (e.g. removing
819 * anonymous attributes). */
820 std::optional<bke::bake::BakeState> bake_state = this->get_bake_state_from_inputs(
821 params, data_block_map, skip);
822 if (!bake_state) {
823 /* Wait for inputs to be computed. */
824 return;
825 }
826 this->output_cached_state(params, user_data, data_block_map, *bake_state);
827 info.store_fn(std::move(*bake_state));
828 }
829
830 std::optional<bke::bake::BakeState> get_bake_state_from_inputs(
831 lf::Params &params, bke::bake::BakeDataBlockMap *data_block_map, const bool skip) const
832 {
833 /* Choose which set of input parameters to use. The others are ignored. */
834 const int params_offset = skip ? skip_inputs_offset_ : solve_inputs_offset_;
835 Array<void *> input_values(simulation_items_.size());
836 for (const int i : simulation_items_.index_range()) {
837 input_values[i] = params.try_get_input_data_ptr_or_request(i + params_offset);
838 }
839 if (input_values.as_span().contains(nullptr)) {
840 /* Wait for inputs to be computed. */
841 return std::nullopt;
842 }
843
844 return move_values_to_simulation_state(simulation_items_, input_values, data_block_map);
845 }
846};
847
849{
850 b.use_custom_socket_order();
851 b.allow_any_socket_order();
852 b.add_input<decl::Bool>("Skip").hide_value().description(
853 "Forward the output of the simulation input node directly to the output node and ignore "
854 "the nodes in the simulation zone");
855
856 const bNodeTree *tree = b.tree_or_null();
857 const bNode *node = b.node_or_null();
858 if (node == nullptr) {
859 return;
860 }
861
862 const NodeGeometrySimulationOutput &storage = node_storage(*node);
863
864 for (const int i : IndexRange(storage.items_num)) {
865 const NodeSimulationItem &item = storage.items[i];
866 const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
867 if (socket_type == SOCK_GEOMETRY && i > 0) {
868 b.add_separator();
869 }
870 const StringRef name = item.name;
871 const std::string identifier = SimulationItemsAccessor::socket_identifier_for_item(item);
872 auto &input_decl = b.add_input(socket_type, name, identifier)
873 .socket_name_ptr(
874 &tree->id, SimulationItemsAccessor::item_srna, &item, "name");
875 auto &output_decl = b.add_output(socket_type, name, identifier).align_with_previous();
876 if (socket_type_supports_fields(socket_type)) {
877 input_decl.supports_field();
878 output_decl.dependent_field({input_decl.index()});
879 }
880 }
881 b.add_input<decl::Extend>("", "__extend__");
882 b.add_output<decl::Extend>("", "__extend__").align_with_previous();
883}
884
885static void node_init(bNodeTree * /*tree*/, bNode *node)
886{
887 NodeGeometrySimulationOutput *data = MEM_cnew<NodeGeometrySimulationOutput>(__func__);
888
889 data->next_identifier = 0;
890
891 data->items = MEM_cnew_array<NodeSimulationItem>(1, __func__);
892 data->items[0].name = BLI_strdup(DATA_("Geometry"));
893 data->items[0].socket_type = SOCK_GEOMETRY;
894 data->items[0].identifier = data->next_identifier++;
895 data->items_num = 1;
896
897 node->storage = data;
898}
899
900static void node_free_storage(bNode *node)
901{
903 MEM_freeN(node->storage);
904}
905
906static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
907{
908 const NodeGeometrySimulationOutput &src_storage = node_storage(*src_node);
909 auto *dst_storage = MEM_cnew<NodeGeometrySimulationOutput>(__func__, src_storage);
910 dst_node->storage = dst_storage;
911
913}
914
921
922static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
923{
925 *ntree, *node, *node, *link);
926}
927
929{
930 BakeDrawContext ctx;
931 if (!get_bake_draw_context(&params.C, params.node, ctx)) {
932 return;
933 }
934 if (ctx.is_baked) {
936 row.text = get_baked_string(ctx);
937 params.rows.append(std::move(row));
938 }
939}
940
941static void node_register()
942{
943 static blender::bke::bNodeType ntype;
944
946 &ntype, GEO_NODE_SIMULATION_OUTPUT, "Simulation Output", NODE_CLASS_INTERFACE);
947 ntype.initfunc = node_init;
948 ntype.declare = node_declare;
950 ntype.gather_link_search_ops = nullptr;
953 ntype.no_muting = true;
957 &ntype, "NodeGeometrySimulationOutput", node_free_storage, node_copy_storage);
959}
961
962} // namespace sim_output_node
963
964} // namespace blender::nodes::node_geo_simulation_cc
965
966namespace blender::nodes {
967
968std::unique_ptr<LazyFunction> get_simulation_input_lazy_function(
969 const bNodeTree &node_tree,
970 const bNode &node,
971 GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
972{
973 BLI_assert(node.type == GEO_NODE_SIMULATION_INPUT);
974 return std::make_unique<
976 node_tree, node, own_lf_graph_info);
977}
978
979std::unique_ptr<LazyFunction> get_simulation_output_lazy_function(
980 const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
981{
982 BLI_assert(node.type == GEO_NODE_SIMULATION_OUTPUT);
983 return std::make_unique<
985 node, own_lf_graph_info);
986}
987
989 void *prev,
990 const void *next,
991 const float factor)
992{
993 switch (socket_type) {
994 case SOCK_GEOMETRY: {
995 GeometrySet &prev_geo = *static_cast<GeometrySet *>(prev);
996 const GeometrySet &next_geo = *static_cast<const GeometrySet *>(next);
997 prev_geo = geometry::mix_geometries(std::move(prev_geo), next_geo, factor);
998 break;
999 }
1000 case SOCK_FLOAT:
1001 case SOCK_VECTOR:
1002 case SOCK_INT:
1003 case SOCK_BOOLEAN:
1004 case SOCK_ROTATION:
1005 case SOCK_RGBA:
1006 case SOCK_MATRIX: {
1007 const CPPType &type = *bke::socket_type_to_geo_nodes_base_cpp_type(socket_type);
1008 SocketValueVariant prev_value_variant = *static_cast<const SocketValueVariant *>(prev);
1009 SocketValueVariant next_value_variant = *static_cast<const SocketValueVariant *>(next);
1010 if (prev_value_variant.is_context_dependent_field() ||
1011 next_value_variant.is_context_dependent_field())
1012 {
1013 /* Fields are evaluated on geometries and are mixed there. */
1014 break;
1015 }
1016
1017 prev_value_variant.convert_to_single();
1018 next_value_variant.convert_to_single();
1019
1020 void *prev_value = prev_value_variant.get_single_ptr().get();
1021 const void *next_value = next_value_variant.get_single_ptr().get();
1022
1023 bke::attribute_math::convert_to_static_type(type, [&](auto dummy) {
1024 using T = decltype(dummy);
1025 *static_cast<T *>(prev_value) = bke::attribute_math::mix2(
1026 factor, *static_cast<T *>(prev_value), *static_cast<const T *>(next_value));
1027 });
1028 break;
1029 }
1030 default:
1031 break;
1032 }
1033}
1034
1035StructRNA *SimulationItemsAccessor::item_srna = &RNA_SimulationStateItem;
1037
1039{
1040 const auto &storage = *static_cast<const NodeGeometrySimulationOutput *>(node.storage);
1041 BLO_write_struct_array(writer, NodeSimulationItem, storage.items_num, storage.items);
1042 for (const NodeSimulationItem &item : Span(storage.items, storage.items_num)) {
1043 BLO_write_string(writer, item.name);
1044 }
1045}
1046
1048{
1049 auto &storage = *static_cast<NodeGeometrySimulationOutput *>(node.storage);
1050 BLO_read_struct_array(reader, NodeSimulationItem, storage.items_num, &storage.items);
1051 for (const NodeSimulationItem &item : Span(storage.items, storage.items_num)) {
1052 BLO_read_string(reader, &item.name);
1053 }
1054}
1055
1056} // namespace blender::nodes
1057
1058blender::Span<NodeSimulationItem> NodeGeometrySimulationOutput::items_span() const
1059{
1060 return blender::Span<NodeSimulationItem>(items, items_num);
1061}
1062
1063blender::MutableSpan<NodeSimulationItem> NodeGeometrySimulationOutput::items_span()
1064{
1065 return blender::MutableSpan<NodeSimulationItem>(items, items_num);
1066}
Low-level operations for curves.
#define NODE_CLASS_INTERFACE
Definition BKE_node.hh:416
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
#define GEO_NODE_SIMULATION_OUTPUT
Definition BKE_node.hh:1331
General operations, lookup, etc. for blender objects.
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.c:40
#define STRNCPY(dst, src)
Definition BLI_string.h:593
char * BLI_strncpy_utf8(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define ELEM(...)
void BLO_read_string(BlendDataReader *reader, char **ptr_p)
Definition readfile.cc:4992
void BLO_write_string(BlendWriter *writer, const char *data_ptr)
#define BLO_write_struct_array(writer, struct_name, array_size, data_ptr)
#define BLO_read_struct_array(reader, struct_name, array_size, ptr_p)
#define TIP_(msgid)
#define IFACE_(msgid)
#define DATA_(msgid)
#define ID_IS_EDITABLE(_id)
Definition DNA_ID.h:658
eNodeSocketDatatype
@ SOCK_INT
@ SOCK_VECTOR
@ SOCK_BOOLEAN
@ SOCK_MATRIX
@ SOCK_FLOAT
@ SOCK_GEOMETRY
@ SOCK_ROTATION
@ SOCK_RGBA
@ UILST_LAYOUT_DEFAULT
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ UI_EMBOSS_NONE
void uiLayoutSetEnabled(uiLayout *layout, bool enabled)
void uiItemL(uiLayout *layout, const char *name, int icon)
uiLayout * uiLayoutRow(uiLayout *layout, bool align)
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
void uiItemEnumO(uiLayout *layout, const char *opname, const char *name, int icon, const char *propname, int value)
void uiTemplateList(uiLayout *layout, const bContext *C, const char *listtype_name, const char *list_id, PointerRNA *dataptr, const char *propname, PointerRNA *active_dataptr, const char *active_propname, const char *item_dyntip_propname, int rows, int maxrows, int layout_type, int columns, enum uiTemplateListFlags flags)
#define UI_ITEM_NONE
PanelLayout uiLayoutPanel(const bContext *C, uiLayout *layout, const char *idname, bool default_closed)
void uiTemplateNodeSocket(uiLayout *layout, bContext *C, const float color[4])
void uiLayoutSetEmboss(uiLayout *layout, eUIEmbossType emboss)
void uiItemO(uiLayout *layout, const char *name, int icon, const char *opname)
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
uiLayout * uiLayoutColumn(uiLayout *layout, bool align)
@ UI_TEMPLATE_LIST_FLAG_NONE
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
Span< T > as_span() const
Definition BLI_array.hh:232
static const CPPType & get()
void destruct(void *ptr) const
const ComputeContextHash & hash() const
const void * get() const
void * allocate(const int64_t size, const int64_t alignment)
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
void append(const T &value)
void resize(const int64_t new_size)
const bNodeTreeZone * get_zone_by_node(const int32_t node_id) const
virtual SimulationZoneBehavior * get(const int zone_id) const =0
void output_simulation_state_copy(lf::Params &params, const GeoNodesLFUserData &user_data, bke::bake::BakeDataBlockMap *data_block_map, const bke::bake::BakeStateRef &zone_state) const
void output_simulation_state_move(lf::Params &params, const GeoNodesLFUserData &user_data, bke::bake::BakeDataBlockMap *data_block_map, bke::bake::BakeState zone_state) const
void execute_impl(lf::Params &params, const lf::Context &context) const final
LazyFunctionForSimulationInputNode(const bNodeTree &node_tree, const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
void pass_through(lf::Params &params, const GeoNodesLFUserData &user_data, bke::bake::BakeDataBlockMap *data_block_map) const
void pass_through(lf::Params &params, GeoNodesLFUserData &user_data, bke::bake::BakeDataBlockMap *data_block_map) const
void output_mixed_cached_state(lf::Params &params, bke::bake::BakeDataBlockMap *data_block_map, const Object &self_object, const ComputeContext &compute_context, const bke::bake::BakeStateRef &prev_state, const bke::bake::BakeStateRef &next_state, const float mix_factor) const
void store_new_state(lf::Params &params, GeoNodesLFUserData &user_data, bke::bake::BakeDataBlockMap *data_block_map, const sim_output::StoreNewState &info) const
void execute_impl(lf::Params &params, const lf::Context &context) const final
LazyFunctionForSimulationOutputNode(const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
void output_cached_state(lf::Params &params, GeoNodesLFUserData &user_data, bke::bake::BakeDataBlockMap *data_block_map, const bke::bake::BakeStateRef &state) const
std::optional< bke::bake::BakeState > get_bake_state_from_inputs(lf::Params &params, bke::bake::BakeDataBlockMap *data_block_map, const bool skip) const
local_group_size(16, 16) .push_constant(Type b
OperationNode * node
const char * label
KDTree_3d * tree
uint col
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
static ulong * next
static ulong state[N]
static void next_state()
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
T mix2(float factor, const T &a, const T &b)
Array< std::unique_ptr< BakeItem > > move_socket_values_to_bake_items(Span< void * > socket_values, const BakeSocketConfig &config, BakeDataBlockMap *data_block_map)
void copy_bake_items_to_socket_values(Span< const BakeItem * > bake_items, const BakeSocketConfig &config, BakeDataBlockMap *data_block_map, FunctionRef< std::shared_ptr< AttributeFieldInput >(int, const CPPType &)> make_attribute_field, Span< void * > r_socket_values)
void move_bake_items_to_socket_values(Span< BakeItem * > bake_items, const BakeSocketConfig &config, BakeDataBlockMap *data_block_map, FunctionRef< std::shared_ptr< AttributeFieldInput >(int socket_index, const CPPType &)> make_attribute_field, Span< void * > r_socket_values)
bNodeSocketType * node_socket_type_find(const char *idname)
Definition node.cc:1763
const char * node_static_socket_type(int type, int subtype)
Definition node.cc:2126
const CPPType * socket_type_to_geo_nodes_base_cpp_type(eNodeSocketDatatype type)
Definition node.cc:4438
void node_type_storage(bNodeType *ntype, const char *storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:4632
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
std::string hash_to_anonymous_attribute_name(Args &&...args)
bke::GeometrySet mix_geometries(bke::GeometrySet a, const bke::GeometrySet &b, float factor)
static void node_label(const bNodeTree *, const bNode *, char *label, const int label_maxncpy)
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
static void node_copy_storage(bNodeTree *, bNode *dst_node, const bNode *src_node)
static void node_extra_info(NodeExtraInfoParams &params)
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
static const CPPType & get_simulation_item_cpp_type(const eNodeSocketDatatype socket_type)
static void NODE_OT_simulation_zone_item_add(wmOperatorType *ot)
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_node_ptr)
static bke::bake::BakeState move_values_to_simulation_state(const Span< NodeSimulationItem > node_simulation_items, const Span< void * > input_values, bke::bake::BakeDataBlockMap *data_block_map)
static std::shared_ptr< AttributeFieldInput > make_attribute_field(const Object &self_object, const ComputeContext &compute_context, const bNode &node, const NodeSimulationItem &item, const CPPType &type)
static void move_simulation_state_to_values(const Span< NodeSimulationItem > node_simulation_items, bke::bake::BakeState zone_state, const Object &self_object, const ComputeContext &compute_context, const bNode &node, bke::bake::BakeDataBlockMap *data_block_map, Span< void * > r_output_values)
static void draw_simulation_state(const bContext *C, uiLayout *layout, bNodeTree &ntree, bNode &output_node)
static void NODE_OT_simulation_zone_item_move(wmOperatorType *ot)
static void NODE_OT_simulation_zone_item_remove(wmOperatorType *ot)
static void draw_simulation_state_item(uiList *, const bContext *C, uiLayout *layout, PointerRNA *, PointerRNA *itemptr, int, PointerRNA *, const char *, int, int)
static bke::bake::BakeSocketConfig make_bake_socket_config(const Span< NodeSimulationItem > node_simulation_items)
static void copy_simulation_state_to_values(const Span< NodeSimulationItem > node_simulation_items, const bke::bake::BakeStateRef &zone_state, const Object &self_object, const ComputeContext &compute_context, const bNode &node, bke::bake::BakeDataBlockMap *data_block_map, Span< void * > r_output_values)
std::variant< PassThrough, OutputCopy, OutputMove > Behavior
std::variant< PassThrough, StoreNewState, ReadSingle, ReadInterpolated, ReadError > Behavior
void move_active_item(wmOperatorType *ot, const char *name, const char *idname, const char *description)
void remove_active_item(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 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)
bool get_bake_draw_context(const bContext *C, const bNode &node, BakeDrawContext &r_ctx)
std::unique_ptr< LazyFunction > get_simulation_output_lazy_function(const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
void draw_data_blocks(const bContext *C, uiLayout *layout, PointerRNA &bake_rna)
std::unique_ptr< LazyFunction > get_simulation_input_lazy_function(const bNodeTree &node_tree, const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info)
void draw_common_bake_settings(bContext *C, BakeDrawContext &ctx, uiLayout *layout)
void draw_bake_button_row(const BakeDrawContext &ctx, uiLayout *layout, bool is_in_sidebar=false)
std::string make_anonymous_attribute_socket_inspection_string(const bNodeSocket &socket)
bool socket_type_supports_fields(const eNodeSocketDatatype socket_type)
void set_default_remaining_node_outputs(lf::Params &params, const bNode &node)
std::optional< FoundNestedNodeID > find_nested_node_id(const GeoNodesLFUserData &user_data, const int node_id)
std::optional< std::string > get_bake_state_string(const BakeDrawContext &ctx)
void mix_baked_data_item(eNodeSocketDatatype socket_type, void *prev, const void *next, const float factor)
std::string get_baked_string(const BakeDrawContext &ctx)
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:46
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:58
void RNA_float_get_array(PointerRNA *ptr, const char *name, float *values)
PointerRNA RNA_pointer_create(ID *id, StructRNA *type, void *data)
signed int int32_t
Definition stdint.h:77
char name[66]
Definition DNA_ID.h:425
ID * owner_id
Definition RNA_types.hh:40
void * data
Definition RNA_types.hh:42
bNodeSocketTypeHandle * typeinfo
void * storage
int32_t identifier
Defines a socket type.
Definition BKE_node.hh:151
const blender::CPPType * geometry_nodes_cpp_type
Definition BKE_node.hh:195
Defines a node type.
Definition BKE_node.hh:218
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:267
void(* labelfunc)(const bNodeTree *ntree, const bNode *node, char *label, int label_maxncpy)
Definition BKE_node.hh:249
void(* draw_buttons_ex)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:240
NodeExtraInfoFunction get_extra_info
Definition BKE_node.hh:366
NodeGatherSocketLinkOperationsFunction gather_link_search_ops
Definition BKE_node.hh:363
bool(* insert_link)(bNodeTree *ntree, bNode *node, bNodeLink *link)
Definition BKE_node.hh:309
NodeDeclareFunction declare
Definition BKE_node.hh:347
void(* register_operators)()
Definition BKE_node.hh:392
Vector< Vector< int, 1 > > geometries_by_attribute
Map< int, const BakeItem * > items_by_id
Map< int, std::unique_ptr< BakeItem > > items_by_id
static void blend_write(BlendWriter *writer, const bNode &node)
static std::string socket_identifier_for_item(const NodeSimulationItem &item)
static void blend_read_data(BlendDataReader *reader, bNode &node)
std::function< void(bke::bake::BakeState state)> store_fn
char idname[BKE_ST_MAXNAME]
wmOperatorType * ot
Definition wm_files.cc:4125
void WM_operatortype_append(void(*opfunc)(wmOperatorType *))
bool WM_uilisttype_add(uiListType *ult)