Blender V4.3
node_geo_bake.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
7#include "NOD_geo_bake.hh"
9#include "NOD_rna_define.hh"
12
13#include "UI_interface.hh"
14#include "UI_resources.hh"
15
16#include "BLI_path_utils.hh"
17#include "BLI_string.h"
18
22#include "BKE_context.hh"
23#include "BKE_global.hh"
24#include "BKE_main.hh"
25#include "BKE_screen.hh"
26
27#include "ED_node.hh"
28
29#include "DNA_modifier_types.h"
30
31#include "RNA_access.hh"
32#include "RNA_prototypes.hh"
33
34#include "MOD_nodes.hh"
35
36#include "WM_api.hh"
37
38#include "BLO_read_write.hh"
39
40#include "node_geometry_util.hh"
41
43
44namespace bake = bke::bake;
45
47
49{
50 b.use_custom_socket_order();
51 b.allow_any_socket_order();
52
53 const bNodeTree *ntree = b.tree_or_null();
54 const bNode *node = b.node_or_null();
55 if (!node) {
56 return;
57 }
58 const NodeGeometryBake &storage = node_storage(*node);
59
60 for (const int i : IndexRange(storage.items_num)) {
61 const NodeGeometryBakeItem &item = storage.items[i];
62 const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
63 const StringRef name = item.name;
64 const std::string identifier = BakeItemsAccessor::socket_identifier_for_item(item);
65 auto &input_decl = b.add_input(socket_type, name, identifier)
66 .socket_name_ptr(
67 &ntree->id, BakeItemsAccessor::item_srna, &item, "name");
68 auto &output_decl = b.add_output(socket_type, name, identifier).align_with_previous();
69 if (socket_type_supports_fields(socket_type)) {
70 input_decl.supports_field();
72 output_decl.field_source();
73 }
74 else {
75 output_decl.dependent_field({input_decl.index()});
76 }
77 }
78 }
79 b.add_input<decl::Extend>("", "__extend__");
80 b.add_output<decl::Extend>("", "__extend__").align_with_previous();
81}
82
83static void node_init(bNodeTree * /*tree*/, bNode *node)
84{
85 NodeGeometryBake *data = MEM_cnew<NodeGeometryBake>(__func__);
86
87 data->items = MEM_cnew_array<NodeGeometryBakeItem>(1, __func__);
88 data->items_num = 1;
89
90 NodeGeometryBakeItem &item = data->items[0];
91 item.name = BLI_strdup(DATA_("Geometry"));
92 item.identifier = data->next_identifier++;
93 item.attribute_domain = int16_t(AttrDomain::Point);
95
96 node->storage = data;
97}
98
99static void node_free_storage(bNode *node)
100{
102 MEM_freeN(node->storage);
103}
104
105static void node_copy_storage(bNodeTree * /*tree*/, bNode *dst_node, const bNode *src_node)
106{
107 const NodeGeometryBake &src_storage = node_storage(*src_node);
108 auto *dst_storage = MEM_cnew<NodeGeometryBake>(__func__, src_storage);
109 dst_node->storage = dst_storage;
110
112}
113
114static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
115{
117 *ntree, *node, *node, *link);
118}
119
120static const CPPType &get_item_cpp_type(const eNodeSocketDatatype socket_type)
121{
122 const char *socket_idname = bke::node_static_socket_type(socket_type, 0);
123 const bke::bNodeSocketType *typeinfo = bke::node_socket_type_find(socket_idname);
124 BLI_assert(typeinfo);
126 return *typeinfo->geometry_nodes_cpp_type;
127}
128
129static void draw_bake_item(uiList * /*ui_list*/,
130 const bContext *C,
131 uiLayout *layout,
132 PointerRNA * /*idataptr*/,
133 PointerRNA *itemptr,
134 int /*icon*/,
135 PointerRNA * /*active_dataptr*/,
136 const char * /*active_propname*/,
137 int /*index*/,
138 int /*flt_flag*/)
139{
140 uiLayout *row = uiLayoutRow(layout, true);
142 RNA_float_get_array(itemptr, "color", color);
143 uiTemplateNodeSocket(row, const_cast<bContext *>(C), color);
145 uiItemR(row, itemptr, "name", UI_ITEM_NONE, "", ICON_NONE);
146}
147
148static void draw_bake_items(const bContext *C, uiLayout *layout, PointerRNA node_ptr)
149{
150 static const uiListType *bake_items_list = []() {
151 uiListType *list = MEM_cnew<uiListType>(__func__);
152 STRNCPY(list->idname, "DATA_UL_bake_node_items");
153 list->draw_item = draw_bake_item;
154 WM_uilisttype_add(list);
155 return list;
156 }();
157
158 bNode &node = *static_cast<bNode *>(node_ptr.data);
159
160 if (uiLayout *panel = uiLayoutPanel(C, layout, "bake_items", false, TIP_("Bake Items"))) {
161 uiLayout *row = uiLayoutRow(panel, false);
162 uiTemplateList(row,
163 C,
164 bake_items_list->idname,
165 "",
166 &node_ptr,
167 "bake_items",
168 &node_ptr,
169 "active_index",
170 nullptr,
171 3,
172 5,
174 0,
176
177 {
178 uiLayout *ops_col = uiLayoutColumn(row, false);
179 {
180 uiLayout *add_remove_col = uiLayoutColumn(ops_col, true);
181 uiItemO(add_remove_col, "", ICON_ADD, "node.bake_node_item_add");
182 uiItemO(add_remove_col, "", ICON_REMOVE, "node.bake_node_item_remove");
183 }
184 {
185 uiLayout *up_down_col = uiLayoutColumn(ops_col, true);
186 uiItemEnumO(up_down_col, "node.bake_node_item_move", "", ICON_TRIA_UP, "direction", 0);
187 uiItemEnumO(up_down_col, "node.bake_node_item_move", "", ICON_TRIA_DOWN, "direction", 1);
188 }
189 }
190
191 NodeGeometryBake &storage = node_storage(node);
192 if (storage.active_index >= 0 && storage.active_index < storage.items_num) {
193 NodeGeometryBakeItem &active_item = storage.items[storage.active_index];
195 node_ptr.owner_id, BakeItemsAccessor::item_srna, &active_item);
196 uiLayoutSetPropSep(panel, true);
197 uiLayoutSetPropDecorate(panel, false);
198 uiItemR(panel, &item_ptr, "socket_type", UI_ITEM_NONE, nullptr, ICON_NONE);
200 uiItemR(panel, &item_ptr, "attribute_domain", UI_ITEM_NONE, nullptr, ICON_NONE);
201 uiItemR(panel, &item_ptr, "is_attribute", UI_ITEM_NONE, nullptr, ICON_NONE);
202 }
203 }
204 }
205}
206
208{
210 ot, "Remove Bake Item", __func__, "Remove active bake item");
211}
212
214{
215 socket_items::ops::add_item<BakeItemsAccessor>(ot, "Add Bake Item", __func__, "Add bake item");
216}
217
219{
221 ot, "Move Bake Item", __func__, "Move active bake item");
222}
223
230
232{
234 const int items_num = bake_items.size();
235 config.domains.resize(items_num);
236 config.names.resize(items_num);
237 config.types.resize(items_num);
238 config.geometries_by_attribute.resize(items_num);
239
240 int last_geometry_index = -1;
241 for (const int item_i : bake_items.index_range()) {
242 const NodeGeometryBakeItem &item = bake_items[item_i];
243 config.types[item_i] = eNodeSocketDatatype(item.socket_type);
244 config.names[item_i] = item.name;
245 config.domains[item_i] = AttrDomain(item.attribute_domain);
246 if (item.socket_type == SOCK_GEOMETRY) {
247 last_geometry_index = item_i;
248 }
249 else if (last_geometry_index != -1) {
250 config.geometries_by_attribute[item_i].append(last_geometry_index);
251 }
252 }
253 return config;
254}
255
261 private:
262 std::mutex mutex_;
264
265 public:
267 {
268 std::lock_guard lock{mutex_};
269 return map_.lookup_default(key, nullptr);
270 }
271
272 void try_add(ID &id) override
273 {
274 std::lock_guard lock{mutex_};
275 map_.add(bake::BakeDataBlockID(id), &id);
276 }
277};
278
280 const bNode &node_;
281 Span<NodeGeometryBakeItem> bake_items_;
282 bake::BakeSocketConfig bake_socket_config_;
283
284 public:
286 : node_(node)
287 {
288 debug_name_ = "Bake";
289 const NodeGeometryBake &storage = node_storage(node);
290 bake_items_ = {storage.items, storage.items_num};
291
292 MutableSpan<int> lf_index_by_bsocket = lf_graph_info.mapping.lf_index_by_bsocket;
293
294 for (const int i : bake_items_.index_range()) {
295 const NodeGeometryBakeItem &item = bake_items_[i];
296 const bNodeSocket &input_bsocket = node.input_socket(i);
297 const bNodeSocket &output_bsocket = node.output_socket(i);
299 lf_index_by_bsocket[input_bsocket.index_in_tree()] = inputs_.append_and_get_index_as(
300 item.name, type, lf::ValueUsage::Maybe);
301 lf_index_by_bsocket[output_bsocket.index_in_tree()] = outputs_.append_and_get_index_as(
302 item.name, type);
303 }
304
305 bake_socket_config_ = make_bake_socket_config(bake_items_);
306 }
307
308 void execute_impl(lf::Params &params, const lf::Context &context) const final
309 {
310 GeoNodesLFUserData &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
311 GeoNodesLFLocalUserData &local_user_data = *static_cast<GeoNodesLFLocalUserData *>(
312 context.local_user_data);
313 if (!user_data.call_data->self_object()) {
314 /* The self object is currently required for generating anonymous attribute names. */
316 return;
317 }
318 if (!user_data.call_data->bake_params) {
320 return;
321 }
322 std::optional<FoundNestedNodeID> found_id = find_nested_node_id(user_data, node_.identifier);
323 if (!found_id) {
325 return;
326 }
327 if (found_id->is_in_loop) {
328 DummyDataBlockMap data_block_map;
329 this->pass_through(params, user_data, &data_block_map);
330 return;
331 }
332 BakeNodeBehavior *behavior = user_data.call_data->bake_params->get(found_id->id);
333 if (!behavior) {
335 return;
336 }
337 if (auto *info = std::get_if<sim_output::ReadSingle>(&behavior->behavior)) {
338 this->output_cached_state(params, user_data, behavior->data_block_map, info->state);
339 }
340 else if (auto *info = std::get_if<sim_output::ReadInterpolated>(&behavior->behavior)) {
342 behavior->data_block_map,
343 *user_data.call_data->self_object(),
344 *user_data.compute_context,
345 info->prev_state,
346 info->next_state,
347 info->mix_factor);
348 }
349 else if (std::get_if<sim_output::PassThrough>(&behavior->behavior)) {
350 this->pass_through(params, user_data, behavior->data_block_map);
351 }
352 else if (auto *info = std::get_if<sim_output::StoreNewState>(&behavior->behavior)) {
353 this->store(params, user_data, behavior->data_block_map, *info);
354 }
355 else if (auto *info = std::get_if<sim_output::ReadError>(&behavior->behavior)) {
356 if (geo_eval_log::GeoTreeLogger *tree_logger = local_user_data.try_get_tree_logger(
357 user_data))
358 {
359 tree_logger->node_warnings.append(
360 *tree_logger->allocator, {node_.identifier, {NodeWarningType::Error, info->message}});
361 }
363 }
364 else {
366 }
367 }
368
373
375 GeoNodesLFUserData &user_data,
376 bke::bake::BakeDataBlockMap *data_block_map) const
377 {
378 std::optional<bake::BakeState> bake_state = this->get_bake_state_from_inputs(params,
379 data_block_map);
380 if (!bake_state) {
381 /* Wait for inputs to be computed. */
382 return;
383 }
384 Array<void *> output_values(bake_items_.size());
385 for (const int i : bake_items_.index_range()) {
386 output_values[i] = params.get_output_data_ptr(i);
387 }
388 this->move_bake_state_to_values(std::move(*bake_state),
389 data_block_map,
390 *user_data.call_data->self_object(),
391 *user_data.compute_context,
392 output_values);
393 for (const int i : bake_items_.index_range()) {
394 params.output_set(i);
395 }
396 }
397
399 GeoNodesLFUserData &user_data,
400 bke::bake::BakeDataBlockMap *data_block_map,
401 const sim_output::StoreNewState &info) const
402 {
403 std::optional<bake::BakeState> bake_state = this->get_bake_state_from_inputs(params,
404 data_block_map);
405 if (!bake_state) {
406 /* Wait for inputs to be computed. */
407 return;
408 }
409 this->output_cached_state(params, user_data, data_block_map, *bake_state);
410 info.store_fn(std::move(*bake_state));
411 }
412
414 GeoNodesLFUserData &user_data,
415 bke::bake::BakeDataBlockMap *data_block_map,
416 const bake::BakeStateRef &bake_state) const
417 {
418 Array<void *> output_values(bake_items_.size());
419 for (const int i : bake_items_.index_range()) {
420 output_values[i] = params.get_output_data_ptr(i);
421 }
422 this->copy_bake_state_to_values(bake_state,
423 data_block_map,
424 *user_data.call_data->self_object(),
425 *user_data.compute_context,
426 output_values);
427 for (const int i : bake_items_.index_range()) {
428 params.output_set(i);
429 }
430 }
431
433 bke::bake::BakeDataBlockMap *data_block_map,
434 const Object &self_object,
435 const ComputeContext &compute_context,
436 const bake::BakeStateRef &prev_state,
438 const float mix_factor) const
439 {
440 Array<void *> output_values(bake_items_.size());
441 for (const int i : bake_items_.index_range()) {
442 output_values[i] = params.get_output_data_ptr(i);
443 }
444 this->copy_bake_state_to_values(
445 prev_state, data_block_map, self_object, compute_context, output_values);
446
447 Array<void *> next_values(bake_items_.size());
448 LinearAllocator<> allocator;
449 for (const int i : bake_items_.index_range()) {
450 const CPPType &type = *outputs_[i].type;
451 next_values[i] = allocator.allocate(type.size(), type.alignment());
452 }
453 this->copy_bake_state_to_values(
454 next_state, data_block_map, self_object, compute_context, next_values);
455
456 for (const int i : bake_items_.index_range()) {
457 mix_baked_data_item(eNodeSocketDatatype(bake_items_[i].socket_type),
458 output_values[i],
459 next_values[i],
460 mix_factor);
461 }
462
463 for (const int i : bake_items_.index_range()) {
464 const CPPType &type = *outputs_[i].type;
465 type.destruct(next_values[i]);
466 }
467
468 for (const int i : bake_items_.index_range()) {
469 params.output_set(i);
470 }
471 }
472
473 std::optional<bake::BakeState> get_bake_state_from_inputs(
474 lf::Params &params, bke::bake::BakeDataBlockMap *data_block_map) const
475 {
476 Array<void *> input_values(bake_items_.size());
477 for (const int i : bake_items_.index_range()) {
478 input_values[i] = params.try_get_input_data_ptr_or_request(i);
479 }
480 if (input_values.as_span().contains(nullptr)) {
481 /* Wait for inputs to be computed. */
482 return std::nullopt;
483 }
484
486 input_values, bake_socket_config_, data_block_map);
487
488 bake::BakeState bake_state;
489 for (const int i : bake_items_.index_range()) {
490 const NodeGeometryBakeItem &item = bake_items_[i];
491 std::unique_ptr<bake::BakeItem> &bake_item = bake_items[i];
492 if (bake_item) {
493 bake_state.items_by_id.add_new(item.identifier, std::move(bake_item));
494 }
495 }
496 return bake_state;
497 }
498
500 bke::bake::BakeDataBlockMap *data_block_map,
501 const Object &self_object,
502 const ComputeContext &compute_context,
503 Span<void *> r_output_values) const
504 {
505 Vector<bake::BakeItem *> bake_items;
506 for (const NodeGeometryBakeItem &item : bake_items_) {
507 std::unique_ptr<bake::BakeItem> *bake_item = bake_state.items_by_id.lookup_ptr(
508 item.identifier);
509 bake_items.append(bake_item ? bake_item->get() : nullptr);
510 }
512 bake_items,
513 bake_socket_config_,
514 data_block_map,
515 [&](const int i, const CPPType &type) {
516 return this->make_attribute_field(self_object, compute_context, bake_items_[i], type);
517 },
518 r_output_values);
519 }
520
522 bke::bake::BakeDataBlockMap *data_block_map,
523 const Object &self_object,
524 const ComputeContext &compute_context,
525 Span<void *> r_output_values) const
526 {
528 for (const NodeGeometryBakeItem &item : bake_items_) {
529 const bake::BakeItem *const *bake_item = bake_state.items_by_id.lookup_ptr(item.identifier);
530 bake_items.append(bake_item ? *bake_item : nullptr);
531 }
533 bake_items,
534 bake_socket_config_,
535 data_block_map,
536 [&](const int i, const CPPType &type) {
537 return this->make_attribute_field(self_object, compute_context, bake_items_[i], type);
538 },
539 r_output_values);
540 }
541
542 std::shared_ptr<AttributeFieldInput> make_attribute_field(const Object &self_object,
543 const ComputeContext &compute_context,
544 const NodeGeometryBakeItem &item,
545 const CPPType &type) const
546 {
547 std::string attribute_name = bke::hash_to_anonymous_attribute_name(
548 compute_context.hash(), self_object.id.name, node_.identifier, item.identifier);
549 std::string socket_inspection_name = make_anonymous_attribute_socket_inspection_string(
550 node_.label_or_name(), item.name);
551 return std::make_shared<AttributeFieldInput>(
552 std::move(attribute_name), type, std::move(socket_inspection_name));
553 }
554};
555
557{
558 BakeDrawContext ctx;
559 if (!get_bake_draw_context(&params.C, params.node, ctx)) {
560 return;
561 }
562 if (ctx.is_baked) {
564 row.text = get_baked_string(ctx);
565 params.rows.append(std::move(row));
566 }
567}
568
569static void node_layout(uiLayout *layout, bContext *C, PointerRNA *ptr)
570{
571 BakeDrawContext ctx;
572 const bNode &node = *static_cast<const bNode *>(ptr->data);
573 if (!get_bake_draw_context(C, node, ctx)) {
574 return;
575 }
576
578 uiLayout *col = uiLayoutColumn(layout, false);
579 {
580 uiLayout *row = uiLayoutRow(col, true);
581 uiLayoutSetEnabled(row, !ctx.is_baked);
582 uiItemR(row, &ctx.bake_rna, "bake_mode", UI_ITEM_R_EXPAND, IFACE_("Mode"), ICON_NONE);
583 }
585}
586
588{
589 draw_bake_items(C, layout, *ptr);
590
591 BakeDrawContext ctx;
592 const bNode &node = *static_cast<const bNode *>(ptr->data);
593 if (!get_bake_draw_context(C, node, ctx)) {
594 return;
595 }
596
598
599 {
600 uiLayout *col = uiLayoutColumn(layout, false);
601 {
602 uiLayout *row = uiLayoutRow(col, true);
603 uiLayoutSetEnabled(row, !ctx.is_baked);
604 uiItemR(row, &ctx.bake_rna, "bake_mode", UI_ITEM_R_EXPAND, IFACE_("Mode"), ICON_NONE);
605 }
606
607 draw_bake_button_row(ctx, col, true);
608 if (const std::optional<std::string> bake_state_str = get_bake_state_string(ctx)) {
609 uiLayout *row = uiLayoutRow(col, true);
610 uiItemL(row, bake_state_str->c_str(), ICON_NONE);
611 }
612 }
613
614 draw_common_bake_settings(C, ctx, layout);
615 draw_data_blocks(C, layout, ctx.bake_rna);
616}
617
619{
620 const eNodeSocketDatatype type = eNodeSocketDatatype(params.other_socket().type);
621 if (type == SOCK_GEOMETRY) {
622 params.add_item(IFACE_("Geometry"), [](LinkSearchOpParams &params) {
623 bNode &node = params.add_node("GeometryNodeBake");
624 params.connect_available_socket(node, "Geometry");
625 });
626 return;
627 }
628 if (!BakeItemsAccessor::supports_socket_type(type)) {
629 return;
630 }
631
632 params.add_item(IFACE_("Value"), [type](LinkSearchOpParams &params) {
633 bNode &node = params.add_node("GeometryNodeBake");
634 socket_items::add_item_with_socket_type_and_name<BakeItemsAccessor>(
635 node, type, params.socket.name);
636 params.update_and_connect_available_socket(node, params.socket.name);
637 });
638}
639
657NOD_REGISTER_NODE(node_register)
658
659} // namespace blender::nodes::node_geo_bake_cc
660
661namespace blender::nodes {
662
663bool get_bake_draw_context(const bContext *C, const bNode &node, BakeDrawContext &r_ctx)
664{
665 BLI_assert(ELEM(node.type, GEO_NODE_BAKE, GEO_NODE_SIMULATION_OUTPUT));
666 r_ctx.node = &node;
667 r_ctx.snode = CTX_wm_space_node(C);
668 if (!r_ctx.snode) {
669 return false;
670 }
671 std::optional<ed::space_node::ObjectAndModifier> object_and_modifier =
673 if (!object_and_modifier) {
674 return false;
675 }
676 r_ctx.object = object_and_modifier->object;
677 r_ctx.nmd = object_and_modifier->nmd;
678 const std::optional<int32_t> bake_id = ed::space_node::find_nested_node_id_in_root(*r_ctx.snode,
679 *r_ctx.node);
680 if (!bake_id) {
681 return false;
682 }
683 r_ctx.bake = nullptr;
684 for (const NodesModifierBake &iter_bake : Span(r_ctx.nmd->bakes, r_ctx.nmd->bakes_num)) {
685 if (iter_bake.id == *bake_id) {
686 r_ctx.bake = &iter_bake;
687 break;
688 }
689 }
690 if (!r_ctx.bake) {
691 return false;
692 }
693
695 const_cast<ID *>(&r_ctx.object->id), &RNA_NodesModifierBake, (void *)r_ctx.bake);
696 if (r_ctx.nmd->runtime->cache) {
697 const bke::bake::ModifierCache &cache = *r_ctx.nmd->runtime->cache;
698 std::lock_guard lock{cache.mutex};
699 if (const std::unique_ptr<bke::bake::BakeNodeCache> *node_cache_ptr =
700 cache.bake_cache_by_id.lookup_ptr(*bake_id))
701 {
702 const bke::bake::BakeNodeCache &node_cache = **node_cache_ptr;
703 if (!node_cache.bake.frames.is_empty()) {
704 const int first_frame = node_cache.bake.frames.first()->frame.frame();
705 const int last_frame = node_cache.bake.frames.last()->frame.frame();
706 r_ctx.baked_range = IndexRange(first_frame, last_frame - first_frame + 1);
707 }
708 }
709 else if (const std::unique_ptr<bke::bake::SimulationNodeCache> *node_cache_ptr =
710 cache.simulation_cache_by_id.lookup_ptr(*bake_id))
711 {
712 const bke::bake::SimulationNodeCache &node_cache = **node_cache_ptr;
713 if (!node_cache.bake.frames.is_empty() &&
715 {
716 const int first_frame = node_cache.bake.frames.first()->frame.frame();
717 const int last_frame = node_cache.bake.frames.last()->frame.frame();
718 r_ctx.baked_range = IndexRange(first_frame, last_frame - first_frame + 1);
719 }
720 }
721 }
722 const Scene *scene = CTX_data_scene(C);
724 *scene, *r_ctx.object, *r_ctx.nmd, r_ctx.bake->id);
725 r_ctx.bake_still = node.type == GEO_NODE_BAKE &&
727 r_ctx.is_baked = r_ctx.baked_range.has_value();
728 r_ctx.bake_target = bke::bake::get_node_bake_target(*r_ctx.object, *r_ctx.nmd, r_ctx.bake->id);
729
730 return true;
731}
732
733std::string get_baked_string(const BakeDrawContext &ctx)
734{
735 if (ctx.bake_still && ctx.baked_range->size() == 1) {
736 return fmt::format(RPT_("Baked Frame {}"), ctx.baked_range->first());
737 }
738 return fmt::format(RPT_("Baked {} - {}"), ctx.baked_range->first(), ctx.baked_range->last());
739}
740
741std::optional<std::string> get_bake_state_string(const BakeDrawContext &ctx)
742{
743 if (G.is_rendering) {
744 /* Avoid accessing data that is generated while baking. */
745 return std::nullopt;
746 }
747 if (ctx.is_baked) {
748 const std::string baked_str = get_baked_string(ctx);
750 BLI_str_format_byte_unit(size_str, ctx.bake->bake_size, true);
751 if (ctx.bake->packed) {
752 return fmt::format(RPT_("{} ({} packed)"), baked_str, size_str);
753 }
754 return fmt::format(RPT_("{} ({} on disk)"), baked_str, size_str);
755 }
756 if (ctx.frame_range.has_value()) {
757 if (!ctx.bake_still) {
758 return fmt::format(
759 RPT_("Frames {} - {}"), ctx.frame_range->first(), ctx.frame_range->last());
760 }
761 }
762 return std::nullopt;
763}
764
765void draw_bake_button_row(const BakeDrawContext &ctx, uiLayout *layout, const bool is_in_sidebar)
766{
767 uiLayout *col = uiLayoutColumn(layout, true);
768 uiLayout *row = uiLayoutRow(col, true);
769 {
770 const char *bake_label = IFACE_("Bake");
771 if (is_in_sidebar) {
772 bake_label = ctx.bake_target == NODES_MODIFIER_BAKE_TARGET_DISK ? IFACE_("Bake to Disk") :
773 IFACE_("Bake Packed");
774 }
775
777 uiItemFullO(row,
778 "OBJECT_OT_geometry_node_bake_single",
779 bake_label,
780 ICON_NONE,
781 nullptr,
784 &ptr);
786 RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name);
787 RNA_int_set(&ptr, "bake_id", ctx.bake->id);
788 }
789 {
790 uiLayout *subrow = uiLayoutRow(row, true);
791 uiLayoutSetActive(subrow, ctx.is_baked);
792 if (is_in_sidebar) {
793 if (ctx.is_baked && !G.is_rendering) {
794 if (ctx.bake->packed) {
796 uiItemFullO(subrow,
797 "OBJECT_OT_geometry_node_bake_unpack_single",
798 "",
799 ICON_PACKAGE,
800 nullptr,
803 &ptr);
805 RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name);
806 RNA_int_set(&ptr, "bake_id", ctx.bake->id);
807 }
808 else {
810 uiItemFullO(subrow,
811 "OBJECT_OT_geometry_node_bake_pack_single",
812 "",
813 ICON_UGLYPACKAGE,
814 nullptr,
817 &ptr);
819 RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name);
820 RNA_int_set(&ptr, "bake_id", ctx.bake->id);
821 }
822 }
823 else {
824 /* If the data is not yet baked, still show the icon based on the derived bake target.*/
825 const int icon = ctx.bake_target == NODES_MODIFIER_BAKE_TARGET_DISK ? ICON_UGLYPACKAGE :
826 ICON_PACKAGE;
828 uiItemFullO(subrow,
829 "OBJECT_OT_geometry_node_bake_pack_single",
830 "",
831 icon,
832 nullptr,
835 &ptr);
836 }
837 }
838 {
840 uiItemFullO(subrow,
841 "OBJECT_OT_geometry_node_bake_delete_single",
842 "",
843 ICON_TRASH,
844 nullptr,
847 &ptr);
849 RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name);
850 RNA_int_set(&ptr, "bake_id", ctx.bake->id);
851 }
852 }
853}
854
856{
857 uiLayoutSetPropSep(layout, true);
858 uiLayoutSetPropDecorate(layout, false);
859
860 uiLayout *settings_col = uiLayoutColumn(layout, false);
861 uiLayoutSetActive(settings_col, !ctx.is_baked);
862 {
863 uiLayout *col = uiLayoutColumn(settings_col, true);
864 uiItemR(col, &ctx.bake_rna, "bake_target", UI_ITEM_NONE, nullptr, ICON_NONE);
865 uiLayout *subcol = uiLayoutColumn(col, true);
867 uiItemR(
868 subcol, &ctx.bake_rna, "use_custom_path", UI_ITEM_NONE, IFACE_("Custom Path"), ICON_NONE);
869 uiLayout *subsubcol = uiLayoutColumn(subcol, true);
870 const bool use_custom_path = ctx.bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH;
871 uiLayoutSetActive(subsubcol, use_custom_path);
872 Main *bmain = CTX_data_main(C);
873 auto bake_path = bke::bake::get_node_bake_path(*bmain, *ctx.object, *ctx.nmd, ctx.bake->id);
874
875 char placeholder_path[FILE_MAX] = "";
876 if (StringRef(ctx.bake->directory).is_empty() &&
877 !(ctx.bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH) && bake_path.has_value() &&
878 bake_path->bake_dir.has_value())
879 {
880 STRNCPY(placeholder_path, bake_path->bake_dir->c_str());
882 BLI_path_rel(placeholder_path, BKE_main_blendfile_path(bmain));
883 }
884 }
885
886 uiItemFullR(subsubcol,
887 &ctx.bake_rna,
888 RNA_struct_find_property(&ctx.bake_rna, "directory"),
889 -1,
890 0,
892 IFACE_("Path"),
893 ICON_NONE,
894 placeholder_path);
895 }
896 {
897 uiLayout *col = uiLayoutColumn(settings_col, true);
898 uiItemR(col,
899 &ctx.bake_rna,
900 "use_custom_simulation_frame_range",
902 IFACE_("Custom Range"),
903 ICON_NONE);
904 uiLayout *subcol = uiLayoutColumn(col, true);
906 uiItemR(subcol, &ctx.bake_rna, "frame_start", UI_ITEM_NONE, IFACE_("Start"), ICON_NONE);
907 uiItemR(subcol, &ctx.bake_rna, "frame_end", UI_ITEM_NONE, IFACE_("End"), ICON_NONE);
908 }
909}
910
911static void draw_bake_data_block_list_item(uiList * /*ui_list*/,
912 const bContext * /*C*/,
913 uiLayout *layout,
914 PointerRNA * /*idataptr*/,
915 PointerRNA *itemptr,
916 int /*icon*/,
917 PointerRNA * /*active_dataptr*/,
918 const char * /*active_propname*/,
919 int /*index*/,
920 int /*flt_flag*/)
921{
922 auto &data_block = *static_cast<NodesModifierDataBlock *>(itemptr->data);
923 uiLayout *row = uiLayoutRow(layout, true);
924
925 std::string name;
926 if (StringRef(data_block.lib_name).is_empty()) {
927 name = data_block.id_name;
928 }
929 else {
930 name = fmt::format("{} [{}]", data_block.id_name, data_block.lib_name);
931 }
932
933 uiItemR(row, itemptr, "id", UI_ITEM_NONE, name.c_str(), ICON_NONE);
934}
935
936void draw_data_blocks(const bContext *C, uiLayout *layout, PointerRNA &bake_rna)
937{
938 static const uiListType *data_block_list = []() {
939 uiListType *list = MEM_cnew<uiListType>(__func__);
940 STRNCPY(list->idname, "DATA_UL_nodes_modifier_data_blocks");
941 list->draw_item = draw_bake_data_block_list_item;
942 WM_uilisttype_add(list);
943 return list;
944 }();
945
946 PointerRNA data_blocks_ptr = RNA_pointer_create(
947 bake_rna.owner_id, &RNA_NodesModifierBakeDataBlocks, bake_rna.data);
948
949 if (uiLayout *panel = uiLayoutPanel(
950 C, layout, "data_block_references", true, TIP_("Data-Block References")))
951 {
952 uiTemplateList(panel,
953 C,
954 data_block_list->idname,
955 "",
956 &bake_rna,
957 "data_blocks",
958 &data_blocks_ptr,
959 "active_index",
960 nullptr,
961 3,
962 5,
964 0,
966 }
967}
968
969std::unique_ptr<LazyFunction> get_bake_lazy_function(
970 const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
971{
972 namespace file_ns = blender::nodes::node_geo_bake_cc;
973 BLI_assert(node.type == GEO_NODE_BAKE);
974 return std::make_unique<file_ns::LazyFunctionForBakeNode>(node, lf_graph_info);
975}
976
977StructRNA *BakeItemsAccessor::item_srna = &RNA_NodeGeometryBakeItem;
979
981{
982 const auto &storage = *static_cast<const NodeGeometryBake *>(node.storage);
983 BLO_write_struct_array(writer, NodeGeometryBakeItem, storage.items_num, storage.items);
984 for (const NodeGeometryBakeItem &item : Span(storage.items, storage.items_num)) {
985 BLO_write_string(writer, item.name);
986 }
987}
988
990{
991 auto &storage = *static_cast<NodeGeometryBake *>(node.storage);
992 BLO_read_struct_array(reader, NodeGeometryBakeItem, storage.items_num, &storage.items);
993 for (const NodeGeometryBakeItem &item : Span(storage.items, storage.items_num)) {
994 BLO_read_string(reader, &item.name);
995 }
996}
997
998}; // namespace blender::nodes
SpaceNode * CTX_wm_space_node(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Main * CTX_data_main(const bContext *C)
const char * BKE_main_blendfile_path(const Main *bmain) ATTR_NONNULL()
Definition main.cc:832
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:418
#define GEO_NODE_BAKE
Definition BKE_node.hh:1350
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define FILE_MAX
bool BLI_path_is_rel(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
bool void BLI_path_rel(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1)
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
#define BLI_STR_FORMAT_INT64_BYTE_UNIT_SIZE
Definition BLI_string.h:28
void BLI_str_format_byte_unit(char dst[BLI_STR_FORMAT_INT64_BYTE_UNIT_SIZE], long long int bytes, bool base_10) ATTR_NONNULL(1)
Definition string.c:1192
#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 RPT_(msgid)
#define TIP_(msgid)
#define IFACE_(msgid)
#define DATA_(msgid)
#define ID_IS_EDITABLE(_id)
Definition DNA_ID.h:658
@ NODES_MODIFIER_BAKE_TARGET_DISK
@ NODES_MODIFIER_BAKE_CUSTOM_PATH
@ NODES_MODIFIER_BAKE_CUSTOM_SIMULATION_FRAME_RANGE
@ NODES_MODIFIER_BAKE_MODE_STILL
@ GEO_NODE_BAKE_ITEM_IS_ATTRIBUTE
eNodeSocketDatatype
@ SOCK_GEOMETRY
@ UILST_LAYOUT_DEFAULT
#define NOD_REGISTER_NODE(REGISTER_FUNC)
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Brightness Control the brightness and contrast of the input color Vector Map input vector components with curves Camera Retrieve information about the camera and how it relates to the current shading point s position Clamp a value between a minimum and a maximum Vector Perform vector math operation Invert Invert a color
void uiLayoutSetActive(uiLayout *layout, bool active)
@ UI_EMBOSS_NONE
void uiLayoutSetEnabled(uiLayout *layout, bool enabled)
void uiItemL(uiLayout *layout, const char *name, int icon)
void uiItemFullO(uiLayout *layout, const char *opname, const char *name, int icon, IDProperty *properties, wmOperatorCallContext context, eUI_Item_Flag flag, PointerRNA *r_opptr)
uiLayout * uiLayoutRow(uiLayout *layout, bool align)
void uiItemFullR(uiLayout *layout, PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, const char *name, int icon, const char *placeholder=nullptr)
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)
@ UI_ITEM_R_EXPAND
@ WM_OP_INVOKE_DEFAULT
Definition WM_types.hh:218
volatile int lock
Span< T > as_span() const
Definition BLI_array.hh:232
void destruct(void *ptr) const
const ComputeContextHash & hash() const
void * allocate(const int64_t size, const int64_t alignment)
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:271
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:531
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
constexpr bool is_empty() const
void append(const T &value)
void resize(const int64_t new_size)
virtual BakeNodeBehavior * get(const int id) const =0
void output_mixed_cached_state(lf::Params &params, bke::bake::BakeDataBlockMap *data_block_map, const Object &self_object, const ComputeContext &compute_context, const bake::BakeStateRef &prev_state, const bake::BakeStateRef &next_state, const float mix_factor) const
void copy_bake_state_to_values(const bake::BakeStateRef &bake_state, bke::bake::BakeDataBlockMap *data_block_map, const Object &self_object, const ComputeContext &compute_context, Span< void * > r_output_values) const
LazyFunctionForBakeNode(const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
void move_bake_state_to_values(bake::BakeState bake_state, bke::bake::BakeDataBlockMap *data_block_map, const Object &self_object, const ComputeContext &compute_context, Span< void * > r_output_values) const
std::shared_ptr< AttributeFieldInput > make_attribute_field(const Object &self_object, const ComputeContext &compute_context, const NodeGeometryBakeItem &item, const CPPType &type) const
void execute_impl(lf::Params &params, const lf::Context &context) const final
void pass_through(lf::Params &params, GeoNodesLFUserData &user_data, bke::bake::BakeDataBlockMap *data_block_map) const
void store(lf::Params &params, GeoNodesLFUserData &user_data, bke::bake::BakeDataBlockMap *data_block_map, const sim_output::StoreNewState &info) const
std::optional< bake::BakeState > get_bake_state_from_inputs(lf::Params &params, bke::bake::BakeDataBlockMap *data_block_map) const
void output_cached_state(lf::Params &params, GeoNodesLFUserData &user_data, bke::bake::BakeDataBlockMap *data_block_map, const bake::BakeStateRef &bake_state) const
local_group_size(16, 16) .push_constant(Type b
OperationNode * node
uint col
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
static void next_state()
#define G(x, y, z)
std::optional< NodesModifierBakeTarget > get_node_bake_target(const Object &object, const NodesModifierData &nmd, int node_id)
Array< std::unique_ptr< BakeItem > > move_socket_values_to_bake_items(Span< void * > socket_values, const BakeSocketConfig &config, BakeDataBlockMap *data_block_map)
std::optional< BakePath > get_node_bake_path(const Main &bmain, const Object &object, const NodesModifierData &nmd, int node_id)
std::optional< IndexRange > get_node_bake_frame_range(const Scene &scene, const Object &object, const NodesModifierData &nmd, int node_id)
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
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)
std::optional< ObjectAndModifier > get_modifier_for_node_editor(const SpaceNode &snode)
std::optional< int32_t > find_nested_node_id_in_root(const SpaceNode &snode, const bNode &node)
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
static void node_layout(uiLayout *layout, bContext *C, PointerRNA *ptr)
static void draw_bake_item(uiList *, const bContext *C, uiLayout *layout, PointerRNA *, PointerRNA *itemptr, int, PointerRNA *, const char *, int, int)
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
static void draw_bake_items(const bContext *C, uiLayout *layout, PointerRNA node_ptr)
static const CPPType & get_item_cpp_type(const eNodeSocketDatatype socket_type)
static void NODE_OT_bake_node_item_remove(wmOperatorType *ot)
static bake::BakeSocketConfig make_bake_socket_config(const Span< NodeGeometryBakeItem > bake_items)
static void node_extra_info(NodeExtraInfoParams &params)
static void NODE_OT_bake_node_item_add(wmOperatorType *ot)
static void NODE_OT_bake_node_item_move(wmOperatorType *ot)
static void node_free_storage(bNode *node)
static void node_copy_storage(bNodeTree *, bNode *dst_node, const bNode *src_node)
static void node_declare(NodeDeclarationBuilder &b)
static void node_init(bNodeTree *, bNode *node)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
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)
void draw_data_blocks(const bContext *C, uiLayout *layout, PointerRNA &bake_rna)
static void draw_bake_data_block_list_item(uiList *, const bContext *, uiLayout *layout, PointerRNA *, PointerRNA *itemptr, int, PointerRNA *, const char *, int, int)
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)
std::unique_ptr< LazyFunction > get_bake_lazy_function(const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
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 RNA_string_set(PointerRNA *ptr, const char *name, const char *value)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
void RNA_int_set(PointerRNA *ptr, const char *name, int value)
void RNA_float_get_array(PointerRNA *ptr, const char *name, float *values)
PointerRNA RNA_pointer_create(ID *id, StructRNA *type, void *data)
signed short int16_t
Definition stdint.h:76
Definition DNA_ID.h:413
char name[66]
Definition DNA_ID.h:425
NodeGeometryBakeItem * items
NodesModifierPackedBake * packed
NodesModifierRuntimeHandle * runtime
NodesModifierBake * bakes
ID * owner_id
Definition RNA_types.hh:40
void * data
Definition RNA_types.hh:42
void * storage
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(* draw_buttons_ex)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:240
NodeExtraInfoFunction get_extra_info
Definition BKE_node.hh:366
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:238
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
Map< int, std::unique_ptr< SimulationNodeCache > > simulation_cache_by_id
Map< int, std::unique_ptr< BakeNodeCache > > bake_cache_by_id
Vector< std::unique_ptr< FrameCache > > frames
std::optional< NodesModifierBakeTarget > bake_target
std::optional< IndexRange > frame_range
const NodesModifierData * nmd
std::optional< IndexRange > baked_range
const NodesModifierBake * bake
static std::string socket_identifier_for_item(const NodeGeometryBakeItem &item)
static void blend_read_data(BlendDataReader *reader, bNode &node)
static void blend_write(BlendWriter *writer, const bNode &node)
geo_eval_log::GeoTreeLogger * try_get_tree_logger(const GeoNodesLFUserData &user_data) const
ID * lookup_or_remember_missing(const bake::BakeDataBlockID &key) override
std::function< void(bke::bake::BakeState state)> store_fn
char idname[BKE_ST_MAXNAME]
PointerRNA * ptr
Definition wm_files.cc:4126
wmOperatorType * ot
Definition wm_files.cc:4125
void WM_operator_properties_id_lookup_set_from_id(PointerRNA *ptr, const ID *id)
void WM_operatortype_append(void(*opfunc)(wmOperatorType *))
bool WM_uilisttype_add(uiListType *ult)