Blender V5.0
node_fn_format_string.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <charconv>
6#include <fmt/format.h>
7#include <regex>
8
9#include "RNA_enum_types.hh"
10
12#include "UI_resources.hh"
13
14#include "BLO_read_write.hh"
15
21
22#include "BKE_path_templates.hh"
23
24#include "node_function_util.hh"
25
27
29
31{
32 b.is_function_node();
33 b.use_custom_socket_order();
34 b.allow_any_socket_order();
35
36 b.add_input<decl::String>("Format").optional_label().description(
37 "Format string using a Python and path template compatible syntax. For example, \"Count: "
38 "{}\" would replace the {} with the first input value.");
39 b.add_output<decl::String>("String").align_with_previous();
40
41 const bNodeTree *ntree = b.tree_or_null();
42 const bNode *node = b.node_or_null();
43 if (!ntree || !node) {
44 return;
45 }
46
47 const NodeFunctionFormatString &storage = node_storage(*node);
48 for (const int i : IndexRange(storage.items_num)) {
49 const NodeFunctionFormatStringItem &item = storage.items[i];
50 const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
51 const StringRef name = item.name;
52 const std::string identifier = FormatStringItemsAccessor::socket_identifier_for_item(item);
53 b.add_input(socket_type, name, identifier)
54 .socket_name_ptr(&ntree->id, FormatStringItemsAccessor::item_srna, &item, "name");
55 }
56
57 b.add_input<decl::Extend>("", "__extend__");
58}
59
60static void node_init(bNodeTree * /*tree*/, bNode *node)
61{
63 node->storage = data;
64}
65
66static void node_copy_storage(bNodeTree * /*tree*/, bNode *dst_node, const bNode *src_node)
67{
68 const NodeFunctionFormatString &src_storage = node_storage(*src_node);
69 auto *dst_storage = MEM_dupallocN<NodeFunctionFormatString>(__func__, src_storage);
70 dst_node->storage = dst_storage;
71
73}
74
80
86
91
93{
94 bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
95 bNode &node = *ptr->data_as<bNode>();
96 if (uiLayout *panel = layout->panel(C, "format_string_items", false, IFACE_("Format Items"))) {
98 C, panel, tree, node);
100 tree, node, [&](PointerRNA *item_ptr) {
101 panel->use_property_split_set(true);
102 panel->use_property_decorate_set(false);
103 panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
104 });
105 }
106}
107
108static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
109{
111}
112
113static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
114{
116}
117
118static std::optional<StringRef> find_format_specifier(const StringRef format)
119{
120 BLI_assert(format[0] == '{');
121 int64_t braces_depth = 1;
122 for (const char &c : format.substr(1)) {
123 if (c == '{') {
124 braces_depth++;
125 }
126 else if (c == '}') {
127 braces_depth--;
128 }
129 if (braces_depth == 0) {
130 const int length = &c - format.data() + 1;
131 return format.substr(0, length);
132 }
133 }
134 return std::nullopt;
135}
136
138 const int64_t start,
139 std::string &r_out)
140{
141 int64_t i = start;
142 while (i < format.size()) {
143 const char c = format[i];
144 switch (c) {
145 case '{':
146 case '}': {
147 if (i + 1 < format.size()) {
148 const char next_c = format[i + 1];
149 if (next_c == c) {
150 i += 2;
151 r_out += c;
152 continue;
153 }
154 }
155 return i;
156 }
157 default: {
158 r_out += c;
159 i++;
160 break;
161 }
162 }
163 }
164 return format.size();
165}
166
168 std::string pattern_str;
169 std::regex pattern;
171 std::optional<int> precision_group;
172};
173
176{
177 std::string pattern;
178 int groups_num = 0;
179
180 /* Beginning of string. */
181 pattern += '^';
182 /* Fill and Align. */
183 pattern += "([^{}]?[<>^])?";
184 groups_num += 1;
185 if (type.is<float>() || type.is<int>()) {
186 /* Sign. */
187 pattern += "[+\\- ]?";
188 /* '#' for alternate form is omitted for better potential future compatibility with
189 * path templates (#BKE_path_apply_template). */
190 /* Sign-aware zero padding. */
191 pattern += "0?";
192 }
193 const std::string integer_or_identifier = "(\\d+|(\\{.*\\}))";
194 /* Width. */
195 pattern += integer_or_identifier;
196 pattern += "?";
197 groups_num += 2;
198 const int width_group = groups_num;
199
200 std::optional<int> precision_group;
201 if (type.is<float>() || type.is<std::string>()) {
202 /* Precision. */
203 pattern += "(\\.";
204 pattern += integer_or_identifier;
205 pattern += ")?";
206 groups_num += 3;
207 precision_group = groups_num;
208 }
209 /* "L" is omitted, because we take the current locale into account in Geometry Nodes. */
210 /* Allowed type specifiers vary by data type. */
211 if (type.is<std::string>()) {
212 pattern += "[s\\?]?";
213 }
214 else if (type.is<int>()) {
215 pattern += "[bBcdoxX]?";
216 }
217 else if (type.is<float>()) {
218 pattern += "[aAeEfFgG]?";
219 }
220 /* End of string. */
221 pattern += '$';
222 return {pattern, std::regex{pattern}, width_group, precision_group};
223}
224
226{
227 if (type.is<float>()) {
229 return &info;
230 }
231 if (type.is<int>()) {
233 return &info;
234 }
235 if (type.is<std::string>()) {
237 return &info;
238 }
239 return nullptr;
240}
241
243 private:
244 const Span<GVArray> inputs_;
245 const VectorSet<std::string> &input_names_;
246 int64_t next_auto_index_ = 0;
251 bool non_auto_index_used_ = false;
252
253 public:
255 : inputs_(inputs), input_names_(input_names)
256 {
257 }
258
259 const GVArray *find_next_input(const StringRef identifier, std::optional<std::string> &r_error)
260 {
261 const std::optional<int64_t> input_index = this->find_next_input_index(identifier, r_error);
262 if (!input_index.has_value()) {
263 return nullptr;
264 }
265 return &inputs_[*input_index];
266 }
267
268 std::optional<int64_t> find_next_input_index(const StringRef identifier,
269 std::optional<std::string> &r_error)
270 {
271 if (identifier.is_empty()) {
272 if (non_auto_index_used_) {
273 /* Once the first explicit identifier is used, it's not allowed to use the auto-index
274 * anymore. Only other explicit identifiers are allowed. */
275 if (!r_error) {
276 r_error = TIP_(
277 "Empty identifier cannot be used when explicit identifier was used before. For "
278 "example, \"{} {x}\" is ok but \"{x} {}\" is not.");
279 }
280 return std::nullopt;
281 }
282 if (next_auto_index_ == inputs_.size()) {
283 /* Not enough inputs provided. */
284 if (!r_error) {
285 r_error = TIP_("Format uses more inputs than provided.");
286 }
287 return std::nullopt;
288 }
289 return next_auto_index_++;
290 }
291 non_auto_index_used_ = true;
292 if (std::isdigit(identifier[0])) {
293 int64_t index;
294 std::from_chars_result res = std::from_chars(identifier.begin(), identifier.end(), index);
295 if (res.ec != std::errc()) {
296 if (!r_error) {
297 r_error = fmt::format(fmt::runtime(TIP_("Invalid identifier: \"{}\"")), identifier);
298 }
299 return std::nullopt;
300 }
301 if (res.ptr < identifier.end()) {
302 /* There are other characters after the number. */
303 if (!r_error) {
304 r_error = fmt::format(
305 fmt::runtime(TIP_("An input name cannot start with a digit: \"{}\"")), identifier);
306 }
307 return std::nullopt;
308 }
309 if (index >= inputs_.size()) {
310 if (!r_error) {
311 if (inputs_.is_empty()) {
312 r_error = fmt::format(fmt::runtime(TIP_("There are no inputs.")), identifier);
313 }
314 else {
315 r_error = fmt::format(
316 fmt::runtime(TIP_("Input with index {} does not exist. Currently, the maximum "
317 "possible index is {}. Did you mean to use {{:{}}}?")),
318 identifier,
319 inputs_.size() - 1,
320 identifier);
321 }
322 }
323 return std::nullopt;
324 }
325 return index;
326 }
327 const int index = input_names_.index_of_try_as(identifier);
328 if (index == -1) {
329 if (!r_error) {
330 r_error = fmt::format(fmt::runtime(TIP_("Input does not exist: \"{}\"")), identifier);
331 }
332 return std::nullopt;
333 }
334 return index;
335 }
336};
337
339 const GVArray *widths = nullptr;
340 const GVArray *precisions = nullptr;
345 std::string fmt_format_str;
346};
347
349 const StringRef format_outer,
350 const FormatPatternInfo &pattern)
351{
352 for (const char c : format) {
353 if (pattern.pattern_str.find(c) == std::string::npos && std::isprint(c) && !std::isdigit(c)) {
354 return fmt::format(
355 fmt::runtime(TIP_("Format contains unsupported \"{}\" character: \"{}\"")),
356 c,
357 format_outer);
358 }
359 }
360 return fmt::format(fmt::runtime(TIP_("Invalid format: \"{}\"")), format_outer);
361}
362
363static std::optional<ProcessedPythonCompatibleFormat> preprocess_python_compatible_syntax(
364 const StringRef format,
365 const StringRef format_outer,
366 const CPPType &type,
367 FormatInputsLookup &inputs_lookup,
368 std::optional<std::string> &r_error)
369{
370 const FormatPatternInfo *allowed_pattern = get_pattern_by_type(type);
371 if (!allowed_pattern) {
372 /* The type can't be formatted. The user shouldn't be able to trigger this error but nice to
373 * handle it anyway. */
374 if (!r_error) {
375 r_error = fmt::format(fmt::runtime(TIP_("Type \"{}\" cannot be formatted")), type.name());
376 }
377 return std::nullopt;
378 }
379
380 /* Check the syntax of the format string with what is allowed. */
381 std::cmatch m;
382 if (!std::regex_search(format.begin(), format.end(), m, allowed_pattern->pattern)) {
383 if (!r_error) {
385 format, format_outer, *allowed_pattern);
386 }
387 return std::nullopt;
388 }
389
391
392 /* Identifiers that are used to specify the width or precision will be replaced with {}. */
393 Vector<std::string> formats_to_replace;
394
395 /* Check if a dynamic width is specified. */
396 const std::string width_outer = m.str(allowed_pattern->width_group);
397 if (!width_outer.empty()) {
398 const StringRef width_inner = StringRef(width_outer).drop_prefix(1).drop_suffix(1);
399 result.widths = inputs_lookup.find_next_input(width_inner, r_error);
400 if (!result.widths) {
401 return std::nullopt;
402 }
403 if (!result.widths->type().is<int>()) {
404 if (!r_error) {
405 r_error = fmt::format(
406 fmt::runtime(TIP_("Only integer inputs can be used as dynamic width: \"{}\"")),
407 format_outer);
408 }
409 return std::nullopt;
410 }
411 formats_to_replace.append(width_outer);
412 }
413
414 /* Check if a dynamic precision is specified. */
415 if (allowed_pattern->precision_group.has_value()) {
416 const std::string precision_outer = m.str(*allowed_pattern->precision_group);
417 if (!precision_outer.empty()) {
418 const StringRef precision_inner = StringRef(precision_outer).drop_prefix(1).drop_suffix(1);
419 result.precisions = inputs_lookup.find_next_input(precision_inner, r_error);
420 if (!result.precisions) {
421 return std::nullopt;
422 }
423 if (!result.precisions->type().is<int>()) {
424 if (!r_error) {
425 r_error = fmt::format(
426 fmt::runtime(TIP_("Only integer inputs can be used as dynamic precision: \"{}\"")),
427 format_outer);
428 }
429 return std::nullopt;
430 }
431 formats_to_replace.append(precision_outer);
432 }
433 }
434
435 result.fmt_format_str = "{:";
436 result.fmt_format_str.append(format.begin(), format.end());
437 result.fmt_format_str += '}';
438
439 /* Replace identifiers with {}, because the source identifiers are not passed to fmt. */
440 for (const std::string &old : formats_to_replace) {
441 const int64_t old_start = result.fmt_format_str.find(old);
442 if (old_start != std::string::npos) {
443 result.fmt_format_str.replace(old_start, old.size(), "{}");
444 }
445 }
446
447 return result;
448}
449
450static void format_with_fmt(const fmt::runtime_format_string<> format,
451 const GVArray &input,
452 const GVArray *widths,
453 const GVArray *precisions,
454 const IndexMask &mask,
455 MutableSpan<std::string> r_formatted_strings)
456{
457 const auto append_single_formatted_string = [&](const auto &varray) {
458 mask.foreach_index([&](const int64_t i) {
459 std::string &output = r_formatted_strings[i];
460 auto output_inserter = std::back_inserter(output);
461 try {
462 if (precisions) {
463 const int precision = std::max(0, precisions->get<int>(i));
464 if (widths) {
465 const int width = std::max(0, widths->get<int>(i));
466 fmt::format_to(output_inserter, format, varray[i], width, precision);
467 }
468 else {
469 fmt::format_to(output_inserter, format, varray[i], precision);
470 }
471 }
472 else {
473 if (widths) {
474 const int width = std::max(0, widths->get<int>(i));
475 fmt::format_to(output_inserter, format, varray[i], width);
476 }
477 else {
478 fmt::format_to(output_inserter, format, varray[i]);
479 }
480 }
481 }
482 catch (const fmt::format_error & /*error*/) {
483 /* Invalid patterns should have been caught before already. */
485 }
486 });
487 };
488
489 const CPPType &type = input.type();
490 if (type.is<float>()) {
491 append_single_formatted_string(input.typed<float>());
492 }
493 else if (type.is<int>()) {
494 append_single_formatted_string(input.typed<int>());
495 }
496 else if (type.is<std::string>()) {
497 append_single_formatted_string(input.typed<std::string>());
498 }
499 else {
500 /* The input type should have been checked earlier already. */
502 }
503}
504
505static void format_with_python_compatible_syntax(const StringRef format_pattern,
506 const StringRef format_outer,
507 const GVArray &input,
508 const IndexMask &mask,
509 FormatInputsLookup &inputs_lookup,
510 MutableSpan<std::string> r_formatted_strings,
511 std::optional<std::string> &r_error)
512{
513 const CPPType &type = input.type();
514 /* Extract information like width and precision inputs. */
515 std::optional<ProcessedPythonCompatibleFormat> processed_format =
517 format_pattern, format_outer, type, inputs_lookup, r_error);
518 if (!processed_format.has_value()) {
519 BLI_assert(r_error);
520 return;
521 }
522 format_with_fmt(fmt::runtime(processed_format->fmt_format_str),
523 input,
524 processed_format->widths,
525 processed_format->precisions,
526 mask,
527 r_formatted_strings);
528}
529
530static void format_with_hash_syntax(const StringRef format_pattern,
531 const GVArray &input,
532 const IndexMask &mask,
533 MutableSpan<std::string> r_formatted_strings,
534 std::optional<std::string> &r_error)
535{
536 const CPPType &type = input.type();
537 if (type.is<float>()) {
538 mask.foreach_index([&](const int64_t i) {
539 std::string &output = r_formatted_strings[i];
540 const float value = input.get<float>(i);
541 if (const std::optional<std::string> value_str = BKE_path_template_format_float(
542 format_pattern, value))
543 {
544 output.append(*value_str);
545 }
546 else if (!r_error) {
547 r_error = fmt::format(fmt::runtime(TIP_("Invalid format specifier: \"{}\"")),
548 format_pattern);
549 }
550 });
551 }
552 else if (type.is<int>()) {
553 mask.foreach_index([&](const int64_t i) {
554 std::string &output = r_formatted_strings[i];
555 const int64_t value = input.get<int>(i);
556 if (const std::optional<std::string> value_str = BKE_path_template_format_int(format_pattern,
557 value))
558 {
559 output.append(*value_str);
560 }
561 else if (!r_error) {
562 r_error = fmt::format(fmt::runtime(TIP_("Invalid format specifier: \"{}\"")),
563 format_pattern);
564 }
565 });
566 }
567 else if (type.is<std::string>()) {
568 if (!r_error) {
569 r_error = fmt::format(fmt::runtime(TIP_("Invalid format specifier for string: \"{}\"")),
570 format_pattern);
571 }
572 }
573 else if (!r_error) {
574 r_error = fmt::format(fmt::runtime(TIP_("Type \"{}\" cannot be formatted")), type.name());
575 }
576}
577
579 const IndexMask &mask,
580 MutableSpan<std::string> r_formatted_strings,
581 std::optional<std::string> &r_error)
582{
583 const CPPType &type = input.type();
584 if (type.is<float>()) {
585 mask.foreach_index([&](const int64_t i) {
586 const float value = input.get<float>(i);
587 std::string &output = r_formatted_strings[i];
588 std::string value_str = fmt::format("{}", value);
589 /* Add ".0" if there are no decimals yet to match Python. */
590 if (StringRef(value_str).find_first_not_of("-0123456789") == StringRef::not_found) {
591 value_str.append(".0");
592 }
593 output += value_str;
594 });
595 }
596 else if (type.is<int>()) {
597 mask.foreach_index([&](const int64_t i) {
598 const int64_t value = input.get<int>(i);
599 std::string &output = r_formatted_strings[i];
600 output += fmt::format("{}", value);
601 });
602 }
603 else if (type.is<std::string>()) {
604 mask.foreach_index([&](const int64_t i) {
605 const std::string value = input.get<std::string>(i);
606 std::string &output = r_formatted_strings[i];
607 output += value;
608 });
609 }
610 else if (!r_error) {
611 r_error = fmt::format(fmt::runtime(TIP_("Type \"{}\" cannot be formatted")), type.name());
612 }
613}
614
616 const Span<GVArray> inputs,
617 const VectorSet<std::string> &input_names,
618 const IndexMask &mask,
619 MutableSpan<std::string> r_formatted_strings,
620 std::optional<std::string> &r_error)
621{
623
624 FormatInputsLookup inputs_lookup{inputs, input_names};
625
626 int64_t current_index = 0;
627 while (current_index < format.size()) {
628 /* Find the string until the next format starts or the string ends. */
629 std::string copy_str;
630 const int64_t next_format_start_or_end = find_next_format_start_or_end(
631 format, current_index, copy_str);
632
633 /* Append the non-formatted string to the outputs. */
634 if (!copy_str.empty()) {
635 mask.foreach_index([&](const int64_t i) {
636 std::string &output = r_formatted_strings[i];
637 output.append(copy_str);
638 });
639 }
640
641 /* The string has ended, so return successfully. */
642 if (next_format_start_or_end == format.size()) {
643 break;
644 }
645 current_index = next_format_start_or_end;
646
647 /* Find the format specifier starting at the current index. */
648 const std::optional<StringRef> format_outer = find_format_specifier(
649 format.substr(current_index));
650 if (!format_outer.has_value()) {
651 if (!r_error) {
652 r_error = fmt::format(fmt::runtime(TIP_("Format specifier is not closed: \"{}\"")),
653 format.substr(current_index));
654 }
655 return false;
656 }
657 const StringRef format_inner = format_outer->substr(1, format_outer->size() - 2);
658
659 /* Extract the identifier and the pattern which are split by a colon. */
660 StringRef identifier;
661 StringRef format_pattern;
662 const int64_t colon_index = format_inner.find(':');
663 if (colon_index == StringRef::not_found) {
664 identifier = format_inner;
665 }
666 else {
667 identifier = format_inner.substr(0, colon_index);
668 format_pattern = format_inner.substr(colon_index + 1);
669 }
670
671 /* Find the typed input values and get the corresponding allowed pattern. */
672 const GVArray *input = inputs_lookup.find_next_input(identifier, r_error);
673 if (!input) {
674 return false;
675 }
676
677 if (format_pattern.is_empty()) {
678 format_without_format_specifier(*input, mask, r_formatted_strings, r_error);
679 }
680 else if (format_pattern.find('#') == StringRef::not_found) {
682 *format_outer,
683 *input,
684 mask,
685 inputs_lookup,
686 r_formatted_strings,
687 r_error);
688 }
689 else {
690 format_with_hash_syntax(format_pattern, *input, mask, r_formatted_strings, r_error);
691 }
692 if (r_error) {
693 return false;
694 }
695
696 current_index += format_outer->size();
697 }
698 return true;
699}
700
701class FormatStringMultiFunction : public mf::MultiFunction {
702 private:
703 const bNode &node_;
704 VectorSet<std::string> input_names_;
705 mf::Signature signature_;
706
707 public:
708 FormatStringMultiFunction(const bNode &node) : node_(node)
709 {
710 const NodeFunctionFormatString &storage = node_storage(node);
711
712 mf::SignatureBuilder builder{"Format String", signature_};
713 builder.single_input<std::string>("Format");
714 for (const int i : IndexRange(storage.items_num)) {
715 const NodeFunctionFormatStringItem &item = storage.items[i];
716 const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
717 const CPPType &type = *bke::socket_type_to_geo_nodes_base_cpp_type(socket_type);
718 builder.single_input(item.name, type);
719 input_names_.add_new(StringRef(item.name));
720 }
721
722 builder.single_output<std::string>("String");
723
724 this->set_signature(&signature_);
725 }
726
727 void call(const IndexMask &mask, mf::Params params, mf::Context context) const override
728 {
729 const NodeFunctionFormatString &storage = node_storage(node_);
730
731 const VArray<std::string> formats = params.readonly_single_input<std::string>(0, "Format");
732 MutableSpan<std::string> outputs = params.uninitialized_single_output<std::string>(
733 storage.items_num + 1, "String");
734
736 for (const int i : IndexRange(storage.items_num)) {
737 inputs[i] = params.readonly_single_input(i + 1);
738 }
739
740 std::optional<std::string> error_message;
741
742 if (const std::optional<std::string> single_format = formats.get_if_single()) {
743 if (!format_strings(*single_format, inputs, input_names_, mask, outputs, error_message)) {
744 mask.foreach_index([&](const int64_t i) { outputs[i].clear(); });
745 }
746 }
747 else {
748 mask.foreach_index(GrainSize(256), [&](const int64_t i) {
749 const StringRef format = formats[i];
750 if (!format_strings(
751 format, inputs, input_names_, IndexRange::from_single(i), outputs, error_message))
752 {
753 outputs[i].clear();
754 }
755 });
756 }
757
758 if (error_message.has_value()) {
759 report_from_multi_function(context, NodeWarningType::Error, std::move(*error_message));
760 }
761 }
762};
763
768
769static void node_register()
770{
771 static blender::bke::bNodeType ntype;
772
773 fn_node_type_base(&ntype, "FunctionNodeFormatString");
774 ntype.ui_name = "Format String";
775 ntype.ui_description =
776 "Insert values into a string using a Python and path template compatible formatting syntax";
779 ntype, "NodeFunctionFormatString", node_free_storage, node_copy_storage);
780 ntype.declare = node_declare;
782 ntype.initfunc = node_init;
789}
791
792} // namespace blender::nodes::node_fn_format_string_cc
793
794namespace blender::nodes {
795
796StructRNA *FormatStringItemsAccessor::item_srna = &RNA_NodeFunctionFormatStringItem;
797
799{
800 BLO_write_string(writer, item.name);
801}
802
807
809{
810 /* The goal is to find a single-letter name that is not used already. Ideally, it starts with the
811 * same letter as the given name. */
812
813 const auto &storage = *static_cast<NodeFunctionFormatString *>(node.storage);
814 char initial = 'a';
815 if (!src_name.is_empty()) {
816 const char first_c = src_name[0];
817 if (first_c >= 'a' && first_c <= 'z') {
818 initial = first_c;
819 }
820 else if (first_c >= 'A' && first_c <= 'Z') {
821 initial = first_c - 'A' + 'a';
822 }
823 }
824 for (const int i : IndexRange('z' - 'a' + 1)) {
825 char c = initial + i;
826 if (c > 'z') {
827 /* Start at 'a' again. */
828 c = c - 'z' + 'a' - 1;
829 }
830 const std::string potential_name = std::string(1, c);
831 const bool name_exists = std::any_of(
832 storage.items,
833 storage.items + storage.items_num,
834 [&](const NodeFunctionFormatStringItem &item) { return item.name == potential_name; });
835 if (!name_exists) {
836 return potential_name;
837 }
838 }
839 return src_name;
840}
841
843{
844 /* The name has to start with a letter or underscore. The remaining letters may additionally be
845 * digits. */
846 std::string result;
847 if (name.is_empty()) {
848 return result;
849 }
850 const char first_char = name[0];
851 if (!std::isalpha(first_char) && first_char != '_') {
852 result += '_';
853 }
854 for (const char c : name) {
855 if (std::isalnum(c) || c == '_') {
856 result += c;
857 }
858 if (ELEM(c, '-', '.', ' ', '\t')) {
859 result += '_';
860 }
861 }
862
863 return result;
864}
865
866} // namespace blender::nodes
#define NODE_CLASS_CONVERTER
Definition BKE_node.hh:453
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1240
Functions and classes for evaluating template expressions in filepaths.
std::optional< std::string > BKE_path_template_format_int(blender::StringRef format_specifier, int64_t value)
std::optional< std::string > BKE_path_template_format_float(blender::StringRef format_specifier, double value)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define ELEM(...)
void BLO_read_string(BlendDataReader *reader, char **ptr_p)
Definition readfile.cc:5828
void BLO_write_string(BlendWriter *writer, const char *data_ptr)
#define TIP_(msgid)
#define IFACE_(msgid)
eNodeSocketDatatype
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define C
Definition RandGen.cpp:29
#define UI_ITEM_NONE
BMesh const char void * data
long long int int64_t
static const CPPType & get()
void value_initialize_indices(void *ptr, const IndexMask &mask) const
StringRefNull name() const
bool is() const
void get(int64_t index, void *r_value) const
static constexpr IndexRange from_single(const int64_t index)
constexpr T * data() const
Definition BLI_span.hh:539
static constexpr int64_t not_found
constexpr int64_t find(char c, int64_t pos=0) const
constexpr const char * begin() const
constexpr const char * end() const
constexpr bool is_empty() const
constexpr StringRef substr(int64_t start, int64_t size) const
constexpr int64_t size() const
constexpr StringRef drop_prefix(int64_t n) const
constexpr StringRef drop_suffix(int64_t n) const
std::optional< T > get_if_single() const
void append(const T &value)
void set_signature(const Signature *signature)
FormatInputsLookup(const Span< GVArray > inputs, const VectorSet< std::string > &input_names)
std::optional< int64_t > find_next_input_index(const StringRef identifier, std::optional< std::string > &r_error)
const GVArray * find_next_input(const StringRef identifier, std::optional< std::string > &r_error)
void call(const IndexMask &mask, mf::Params params, mf::Context context) const override
KDTree_3d * tree
#define input
#define output
float length(VecOp< float, D >) RET
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
format
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)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
const CPPType * socket_type_to_geo_nodes_base_cpp_type(eNodeSocketDatatype type)
Definition node.cc:5202
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 FormatPatternInfo get_pattern_by_type_impl(const CPPType &type)
static void format_without_format_specifier(const GVArray &input, const IndexMask &mask, MutableSpan< std::string > r_formatted_strings, std::optional< std::string > &r_error)
static bool format_strings(const StringRef format, const Span< GVArray > inputs, const VectorSet< std::string > &input_names, const IndexMask &mask, MutableSpan< std::string > r_formatted_strings, std::optional< std::string > &r_error)
static void node_blend_read(bNodeTree &, bNode &node, BlendDataReader &reader)
static void node_copy_storage(bNodeTree *, bNode *dst_node, const bNode *src_node)
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
static int64_t find_next_format_start_or_end(const StringRef format, const int64_t start, std::string &r_out)
static std::optional< ProcessedPythonCompatibleFormat > preprocess_python_compatible_syntax(const StringRef format, const StringRef format_outer, const CPPType &type, FormatInputsLookup &inputs_lookup, std::optional< std::string > &r_error)
static void format_with_fmt(const fmt::runtime_format_string<> format, const GVArray &input, const GVArray *widths, const GVArray *precisions, const IndexMask &mask, MutableSpan< std::string > r_formatted_strings)
static const FormatPatternInfo * get_pattern_by_type(const CPPType &type)
static void node_declare(NodeDeclarationBuilder &b)
static bool node_insert_link(bke::NodeInsertLinkParams &params)
static void format_with_python_compatible_syntax(const StringRef format_pattern, const StringRef format_outer, const GVArray &input, const IndexMask &mask, FormatInputsLookup &inputs_lookup, MutableSpan< std::string > r_formatted_strings, std::optional< std::string > &r_error)
static std::string create_invalid_python_compatible_format_error(const StringRef format, const StringRef format_outer, const FormatPatternInfo &pattern)
static void node_blend_write(const bNodeTree &, const bNode &node, BlendWriter &writer)
static std::optional< StringRef > find_format_specifier(const StringRef format)
static void format_with_hash_syntax(const StringRef format_pattern, const GVArray &input, const IndexMask &mask, MutableSpan< std::string > r_formatted_strings, std::optional< std::string > &r_error)
static void node_init(bNodeTree *, bNode *node)
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
static void draw_items_list_with_operators(const bContext *C, uiLayout *layout, const bNodeTree &tree, const bNode &node)
static void draw_active_item_props(const bNodeTree &tree, const bNode &node, const FunctionRef< void(PointerRNA *item_ptr)> draw_item)
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)
void report_from_multi_function(const mf::Context &context, NodeWarningType type, std::string message)
void fn_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
static blender::bke::bNodeSocketTemplate outputs[]
static blender::bke::bNodeSocketTemplate inputs[]
const char * name
NodeFunctionFormatStringItem * items
void * storage
Defines a node type.
Definition BKE_node.hh:238
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
NodeMultiFunctionBuildFunction build_multi_function
Definition BKE_node.hh:351
bool(* insert_link)(NodeInsertLinkParams &params)
Definition BKE_node.hh:333
NodeDeclareFunction declare
Definition BKE_node.hh:362
void(* register_operators)()
Definition BKE_node.hh:417
static void blend_write_item(BlendWriter *writer, const ItemT &item)
static void blend_read_data_item(BlendDataReader *reader, ItemT &item)
static std::string custom_initial_name(const bNode &node, StringRef src_name)
static std::string validate_name(const StringRef name)
static std::string socket_identifier_for_item(const NodeFunctionFormatStringItem &item)
PanelLayout panel(const bContext *C, blender::StringRef idname, bool default_closed)
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4238