Blender V4.5
path_templates.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 <fmt/format.h>
6
7#include "BLT_translation.hh"
8
9#include "BLI_span.hh"
10
11#include "BKE_context.hh"
12#include "BKE_main.hh"
13#include "BKE_path_templates.hh"
14#include "BKE_scene.hh"
15
16#include "RNA_access.hh"
17#include "RNA_prototypes.hh"
18
19#include "DNA_ID_enums.h"
20#include "DNA_node_types.h"
21
23
25{
26 if (this->strings_.contains(name)) {
27 return true;
28 }
29 if (this->integers_.contains(name)) {
30 return true;
31 }
32 if (this->floats_.contains(name)) {
33 return true;
34 }
35 return false;
36}
37
39{
40 if (this->strings_.remove(name)) {
41 return true;
42 }
43 if (this->integers_.remove(name)) {
44 return true;
45 }
46 if (this->floats_.remove(name)) {
47 return true;
48 }
49 return false;
50}
51
53{
54 if (this->contains(name)) {
55 return false;
56 }
57 this->strings_.add_new(name, value);
58 return true;
59}
60
62{
63 if (this->contains(name)) {
64 return false;
65 }
66 this->integers_.add_new(name, value);
67 return true;
68}
69
70bool VariableMap::add_float(blender::StringRef name, const double value)
71{
72 if (this->contains(name)) {
73 return false;
74 }
75 this->floats_.add_new(name, value);
76 return true;
77}
78
79std::optional<blender::StringRefNull> VariableMap::get_string(blender::StringRef name) const
80{
81 const std::string *value = this->strings_.lookup_ptr(name);
82 if (value == nullptr) {
83 return std::nullopt;
84 }
85 return blender::StringRefNull(*value);
86}
87
88std::optional<int64_t> VariableMap::get_integer(blender::StringRef name) const
89{
90 const int64_t *value = this->integers_.lookup_ptr(name);
91 if (value == nullptr) {
92 return std::nullopt;
93 }
94 return *value;
95}
96
97std::optional<double> VariableMap::get_float(blender::StringRef name) const
98{
99 const double *value = this->floats_.lookup_ptr(name);
100 if (value == nullptr) {
101 return std::nullopt;
102 }
103 return *value;
104}
105
106bool operator==(const Error &left, const Error &right)
107{
108 return left.type == right.type && left.byte_range == right.byte_range;
109}
110
111} // namespace blender::bke::path_templates
112
113using namespace blender::bke::path_templates;
114
115std::optional<VariableMap> BKE_build_template_variables_for_prop(const bContext *C,
117 PropertyRNA *prop)
118{
119 /*
120 * This function should be maintained such that it always produces variables
121 * consistent with the variables produced elsewhere in the code base for the
122 * same property. For example, render paths are processed in the rendering
123 * code and variables are built for that purpose there, and this function
124 * should produce variables consistent with that for those render path
125 * properties here.
126 *
127 * The recommended strategy when adding support for additional path templating
128 * use cases (that don't already have an appropriate
129 * `PropertyPathTemplateType` item) is to:
130 *
131 * 1. Create a separate function to build variables for that use case (see
132 * e.g. `BKE_build_template_variables_for_render_path()`).
133 * 2. Call that function from here in the switch statement below.
134 * 3. Also call that function from the other parts of the code base that need
135 * it.
136 */
137
138 /* No property passed, or it doesn't support path templates. */
139 if (ptr == nullptr || prop == nullptr ||
141 {
142 return std::nullopt;
143 }
144
145 switch (RNA_property_path_template_type(prop)) {
146 case PROP_VARIABLES_NONE: {
148 false,
149 "Should never have `PROP_VARIABLES_NONE` for a path that supports path templates.");
150 return VariableMap();
151 }
152
153 /* Scene render output path, the compositor's File Output node's paths, etc. */
155 const RenderData *render_data;
156 if (GS(ptr->owner_id->name) == ID_SCE) {
157 render_data = &reinterpret_cast<const Scene *>(ptr->owner_id)->r;
158 }
159 else {
160 const Scene *scene = CTX_data_scene(C);
161 render_data = scene ? &scene->r : nullptr;
162 }
163
165 }
166 }
167
168 /* All paths that support path templates should be handled above, and any that
169 * aren't should already be rejected by the test at the top of the function. */
171
172 return std::nullopt;
173}
174
176{
177 VariableMap variables;
178
179 /* Blend file name of the currently open blend file. */
180 {
181 const char *blend_file_path = BKE_main_blendfile_path_from_global();
182 const char *file_name = BLI_path_basename(blend_file_path);
183 const char *file_name_end = BLI_path_extension_or_end(file_name);
184 if (file_name[0] == '\0') {
185 /* If the file has never been saved (indicated by an empty file name),
186 * default to "Unsaved". */
187 variables.add_string("blend_name", blender::StringRef(DATA_("Unsaved")));
188 }
189 else if (file_name_end == file_name) {
190 /* When the filename has no extension, but starts with a period. */
191 variables.add_string("blend_name", blender::StringRef(file_name));
192 }
193 else {
194 /* Normal case. */
195 variables.add_string("blend_name", blender::StringRef(file_name, file_name_end));
196 }
197 }
198
199 /* Render resolution and fps. */
200 if (render_data) {
201 int res_x, res_y;
202 BKE_render_resolution(render_data, false, &res_x, &res_y);
203 variables.add_integer("resolution_x", res_x);
204 variables.add_integer("resolution_y", res_y);
205
206 /* FPS eval code copied from `BKE_cachefile_filepath_get()`.
207 *
208 * TODO: should probably use one function for this everywhere to ensure that
209 * fps is computed consistently, but at the time of writing no such function
210 * seems to exist. Every place in the code base just has its own bespoke
211 * code, using different precision, etc. */
212 const double fps = double(render_data->frs_sec) / double(render_data->frs_sec_base);
213 variables.add_float("fps", fps);
214 }
215
216 return variables;
217}
218
219/* -------------------------------------------------------------------- */
220
221#define FORMAT_BUFFER_SIZE 128
222
223namespace {
224
225enum class FormatSpecifierType {
226 /* No format specifier given. Use default formatting. */
227 NONE = 0,
228
229 /* The format specifier was a string of just "#" characters. E.g. "####". */
230 INTEGER,
231
232 /* The format specifier was a string of "#" characters with a single ".". E.g.
233 * "###.##". */
234 FLOAT,
235
236 /* The format specifier was invalid due to incorrect syntax. */
237 SYNTAX_ERROR,
238};
239
244struct FormatSpecifier {
245 FormatSpecifierType type = FormatSpecifierType::NONE;
246
247 /* For INTEGER and FLOAT formatting types, the number of digits indicated on
248 * either side of the decimal point. */
249 std::optional<uint8_t> integer_digit_count;
250 std::optional<uint8_t> fractional_digit_count;
251};
252
253enum class TokenType {
254 /* Either "{variable_name}" or "{variable_name:format_spec}". */
255 VARIABLE_EXPRESSION,
256
257 /* "{{", which is an escaped "{". */
258 LEFT_CURLY_BRACE,
259
260 /* "}}", which is an escaped "}". */
261 RIGHT_CURLY_BRACE,
262
263 /* Encountered a syntax error while trying to parse a variable expression. */
264 VARIABLE_SYNTAX_ERROR,
265
266 /* Encountered an unescaped curly brace in an invalid position. */
267 UNESCAPED_CURLY_BRACE_ERROR,
268};
269
273struct Token {
274 TokenType type = TokenType::VARIABLE_EXPRESSION;
275
276 /* Byte index range (exclusive on the right) of the token or syntax error in
277 * the path string. */
278 blender::IndexRange byte_range;
279
280 /* Reference to the variable name as written in the template string. Note
281 * that this points into the template string, and does not own the value.
282 *
283 * Only relevant when `type == VARIABLE_EXPRESSION`. */
284 blender::StringRef variable_name;
285
286 /* Indicates how the variable's value should be formatted into a string. This
287 * is derived from the format specification (e.g. the "###" in "{blah:###}").
288 *
289 * Only relevant when `type == VARIABLE_EXPRESSION`. */
290 FormatSpecifier format;
291};
292
293} // namespace
294
303static int format_int_to_string(const FormatSpecifier &format,
304 const int64_t integer_value,
305 char r_output_string[FORMAT_BUFFER_SIZE])
306{
307 BLI_assert(format.type != FormatSpecifierType::SYNTAX_ERROR);
308
309 r_output_string[0] = '\0';
310 int output_length = 0;
311
312 switch (format.type) {
313 case FormatSpecifierType::NONE: {
314 output_length =
315 fmt::format_to_n(r_output_string, FORMAT_BUFFER_SIZE - 1, "{}", integer_value).size;
316 r_output_string[output_length] = '\0';
317 break;
318 }
319
320 case FormatSpecifierType::INTEGER: {
321 BLI_assert(format.integer_digit_count.has_value());
322 BLI_assert(*format.integer_digit_count > 0);
323 output_length = fmt::format_to_n(r_output_string,
325 "{:0{}}",
326 integer_value,
327 *format.integer_digit_count)
328 .size;
329 r_output_string[output_length] = '\0';
330 break;
331 }
332
333 case FormatSpecifierType::FLOAT: {
334 /* Formatting an integer as a float: we do *not* defer to the float
335 * formatter for this because we could lose precision with very large
336 * numbers. Instead we simply print the integer, and then append ".000..."
337 * to it. */
338 BLI_assert(format.fractional_digit_count.has_value());
339 BLI_assert(*format.fractional_digit_count > 0);
340
341 if (format.integer_digit_count.has_value()) {
342 BLI_assert(*format.integer_digit_count > 0);
343 output_length = fmt::format_to_n(r_output_string,
345 "{:0{}}",
346 integer_value,
347 *format.integer_digit_count)
348 .size;
349 }
350 else {
351 output_length =
352 fmt::format_to_n(r_output_string, FORMAT_BUFFER_SIZE - 1, "{}", integer_value).size;
353 }
354
355 r_output_string[output_length] = '.';
356 output_length++;
357
358 for (int i = 0; i < *format.fractional_digit_count && i < (FORMAT_BUFFER_SIZE - 1); i++) {
359 r_output_string[output_length] = '0';
360 output_length++;
361 }
362
363 r_output_string[output_length] = '\0';
364
365 break;
366 }
367
368 case FormatSpecifierType::SYNTAX_ERROR: {
370 false,
371 "Format specifiers with invalid syntax should have been rejected before getting here.");
372 break;
373 }
374 }
375
376 return output_length;
377}
378
387static int format_float_to_string(const FormatSpecifier &format,
388 const double float_value,
389 char r_output_string[FORMAT_BUFFER_SIZE])
390{
391 BLI_assert(format.type != FormatSpecifierType::SYNTAX_ERROR);
392
393 r_output_string[0] = '\0';
394 int output_length = 0;
395
396 switch (format.type) {
397 case FormatSpecifierType::NONE: {
398 /* When no format specification is given, we attempt to replicate Python's
399 * behavior in the same situation. The only major thing we can't replicate
400 * via `libfmt` is that in Python whole numbers are printed with a trailing
401 * ".0". So we handle that bit manually. */
402 output_length =
403 fmt::format_to_n(r_output_string, FORMAT_BUFFER_SIZE - 1, "{}", float_value).size;
404 r_output_string[output_length] = '\0';
405
406 /* If the string consists only of digits and a possible negative sign, then
407 * we append a ".0" to match Python. */
408 if (blender::StringRef(r_output_string).find_first_not_of("-0123456789") ==
409 std::string::npos)
410 {
411 r_output_string[output_length] = '.';
412 r_output_string[output_length + 1] = '0';
413 r_output_string[output_length + 2] = '\0';
414 output_length += 2;
415 }
416 break;
417 }
418
419 case FormatSpecifierType::INTEGER: {
420 /* Defer to the integer formatter with a rounded value. */
421 return format_int_to_string(format, std::round(float_value), r_output_string);
422 }
423
424 case FormatSpecifierType::FLOAT: {
425 BLI_assert(format.fractional_digit_count.has_value());
426 BLI_assert(*format.fractional_digit_count > 0);
427
428 if (format.integer_digit_count.has_value()) {
429 /* Both integer and fractional component lengths are specified. */
430 BLI_assert(*format.integer_digit_count > 0);
431 output_length = fmt::format_to_n(r_output_string,
433 "{:0{}.{}f}",
434 float_value,
435 *format.integer_digit_count +
436 *format.fractional_digit_count + 1,
437 *format.fractional_digit_count)
438 .size;
439 r_output_string[output_length] = '\0';
440 }
441 else {
442 /* Only fractional component length is specified. */
443 output_length = fmt::format_to_n(r_output_string,
445 "{:.{}f}",
446 float_value,
447 *format.fractional_digit_count)
448 .size;
449 r_output_string[output_length] = '\0';
450 }
451
452 break;
453 }
454
455 case FormatSpecifierType::SYNTAX_ERROR: {
457 false,
458 "Format specifiers with invalid syntax should have been rejected before getting here.");
459 break;
460 }
461 }
462
463 return output_length;
464}
465
473static FormatSpecifier parse_format_specifier(blender::StringRef format_specifier)
474{
475 FormatSpecifier format = {};
476
477 /* A ":" was used, but no format specifier was given, which is invalid. */
478 if (format_specifier.is_empty()) {
479 format.type = FormatSpecifierType::SYNTAX_ERROR;
480 return format;
481 }
482
483 /* If it's all digit specifiers, then format as an integer. */
484 if (format_specifier.find_first_not_of("#") == std::string::npos) {
485 format.integer_digit_count = format_specifier.size();
486
487 format.type = FormatSpecifierType::INTEGER;
488 return format;
489 }
490
491 /* If it's digit specifiers and a dot, format as a float. */
492 const int64_t dot_index = format_specifier.find_first_of('.');
493 const int64_t dot_index_last = format_specifier.find_last_of('.');
494 const bool found_dot = dot_index != std::string::npos;
495 const bool only_one_dot = dot_index == dot_index_last;
496 if (format_specifier.find_first_not_of(".#") == std::string::npos && found_dot && only_one_dot) {
497 blender::StringRef left = format_specifier.substr(0, dot_index);
498 blender::StringRef right = format_specifier.substr(dot_index + 1);
499
500 /* We currently require that the fractional digits are specified, so bail if
501 * they aren't. */
502 if (right.is_empty()) {
503 format.type = FormatSpecifierType::SYNTAX_ERROR;
504 return format;
505 }
506
507 if (!left.is_empty()) {
508 format.integer_digit_count = left.size();
509 }
510
511 format.fractional_digit_count = right.size();
512
513 format.type = FormatSpecifierType::FLOAT;
514 return format;
515 }
516
517 format.type = FormatSpecifierType::SYNTAX_ERROR;
518 return format;
519}
520
532static std::optional<Token> next_token(blender::StringRef path, const int from_char)
533{
534 Token token;
535
536 /* We use the magic number -1 here to indicate that a component hasn't been
537 * found yet. When a component is found, the respective token here is set
538 * to the byte offset it was found at. */
539 int start = -1; /* "{" */
540 int format_specifier_split = -1; /* ":" */
541 int end = -1; /* "}" */
542
543 for (int byte_index = from_char; byte_index < path.size(); byte_index++) {
544 /* Check for escaped "{". */
545 if (start == -1 && (byte_index + 1) < path.size() && path[byte_index] == '{' &&
546 path[byte_index + 1] == '{')
547 {
548 Token token;
549 token.type = TokenType::LEFT_CURLY_BRACE;
550 token.byte_range = blender::IndexRange::from_begin_end(byte_index, byte_index + 2);
551 return token;
552 }
553
554 /* Check for escaped "}".
555 *
556 * Note that we only do this check when not already inside a variable
557 * expression, since it could be a valid closing "}" followed by additional
558 * escaped closing braces. */
559 if (start == -1 && (byte_index + 1) < path.size() && path[byte_index] == '}' &&
560 path[byte_index + 1] == '}')
561 {
562 token.type = TokenType::RIGHT_CURLY_BRACE;
563 token.byte_range = blender::IndexRange::from_begin_end(byte_index, byte_index + 2);
564 return token;
565 }
566
567 /* Check for unescaped "}", which outside of a variable expression is
568 * illegal. */
569 if (start == -1 && path[byte_index] == '}') {
570 token.type = TokenType::UNESCAPED_CURLY_BRACE_ERROR;
571 token.byte_range = blender::IndexRange::from_begin_end(byte_index, byte_index + 1);
572 return token;
573 }
574
575 /* Check if we've found a starting "{". */
576 if (path[byte_index] == '{') {
577 if (start != -1) {
578 /* Already inside a variable expression. */
579 token.type = TokenType::VARIABLE_SYNTAX_ERROR;
580 token.byte_range = blender::IndexRange::from_begin_end(start, byte_index);
581 return token;
582 }
583 start = byte_index;
584 format_specifier_split = -1;
585 continue;
586 }
587
588 /* If we haven't found a start, we shouldn't try to parse the other bits
589 * yet. */
590 if (start == -1) {
591 continue;
592 }
593
594 /* Check if we've found a format splitter. */
595 if (path[byte_index] == ':') {
596 if (format_specifier_split == -1) {
597 /* Only set if it's the first ":" we've encountered in the variable
598 * expression. Subsequent ones will be handled in the format specifier
599 * parsing. */
600 format_specifier_split = byte_index;
601 }
602 continue;
603 }
604
605 /* Check if we've found the closing "}". */
606 if (path[byte_index] == '}') {
607 end = byte_index + 1; /* Exclusive end. */
608 break;
609 }
610 }
611
612 /* No variable expression found. */
613 if (start == -1) {
614 return std::nullopt;
615 }
616
617 /* Unclosed variable expression. Syntax error. */
618 if (end == -1) {
619 token.type = TokenType::VARIABLE_SYNTAX_ERROR;
620 token.byte_range = blender::IndexRange::from_begin_end(start, path.size());
621 return token;
622 }
623
624 /* Parse the variable expression we found. */
625 token.byte_range = blender::IndexRange::from_begin_end(start, end);
626 if (format_specifier_split == -1) {
627 /* No format specifier. */
628 token.variable_name = path.substr(start + 1, (end - 1) - (start + 1));
629 }
630 else {
631 /* Found format specifier. */
632 token.variable_name = path.substr(start + 1, format_specifier_split - (start + 1));
633 token.format = parse_format_specifier(
634 path.substr(format_specifier_split + 1, (end - 1) - (format_specifier_split + 1)));
635 if (token.format.type == FormatSpecifierType::SYNTAX_ERROR) {
636 token.type = TokenType::VARIABLE_SYNTAX_ERROR;
637 return token;
638 }
639 }
640
641 return token;
642}
643
644/* Parse the given template and return the list of tokens found, in the same
645 * order as they appear in the template. */
647{
649
650 for (int bytes_read = 0; bytes_read < path.size();) {
651 const std::optional<Token> token = next_token(path, bytes_read);
652
653 if (!token.has_value()) {
654 break;
655 }
656
657 bytes_read = token->byte_range.one_after_last();
658 tokens.append(*token);
659 }
660
661 return tokens;
662}
663
664/* Convert a token to its corresponding syntax error. If the token doesn't have
665 * an error, returns nullopt. */
666static std::optional<Error> token_to_syntax_error(const Token &token)
667{
668 switch (token.type) {
669 case TokenType::VARIABLE_SYNTAX_ERROR: {
670 if (token.format.type == FormatSpecifierType::SYNTAX_ERROR) {
671 return {{ErrorType::FORMAT_SPECIFIER, token.byte_range}};
672 }
673 else {
674 return {{ErrorType::VARIABLE_SYNTAX, token.byte_range}};
675 }
676 }
677
678 case TokenType::UNESCAPED_CURLY_BRACE_ERROR: {
679 return {{ErrorType::UNESCAPED_CURLY_BRACE, token.byte_range}};
680 }
681
682 /* Non-errors. */
683 case TokenType::VARIABLE_EXPRESSION:
684 case TokenType::LEFT_CURLY_BRACE:
685 case TokenType::RIGHT_CURLY_BRACE:
686 return std::nullopt;
687 }
688
689 BLI_assert_msg(false, "Unhandled token type.");
690 return std::nullopt;
691}
692
694{
695 return path.find_first_of("{}") != std::string_view::npos;
696}
697
719 const int out_path_max_length,
720 blender::StringRef in_path,
721 const VariableMap &template_variables)
722{
723 if (out_path) {
724 in_path.copy_bytes_truncated(out_path, out_path_max_length);
725 }
726
727 const blender::Vector<Token> tokens = parse_template(in_path);
728
729 if (tokens.is_empty()) {
730 /* No tokens found, so nothing to do. */
731 return {};
732 }
733
734 /* Accumulates errors as we process the tokens. */
736
737 /* Tracks the change in string length due to the modifications as we go. We
738 * need this to properly map the token byte ranges to the being-modified
739 * string. */
740 int length_diff = 0;
741
742 for (const Token &token : tokens) {
743 /* Syntax errors. */
744 if (std::optional<Error> error = token_to_syntax_error(token)) {
745 errors.append(*error);
746 continue;
747 }
748
749 char replacement_string[FORMAT_BUFFER_SIZE];
750
751 switch (token.type) {
752 /* Syntax errors should have been handled above. */
753 case TokenType::VARIABLE_SYNTAX_ERROR:
754 case TokenType::UNESCAPED_CURLY_BRACE_ERROR: {
755 BLI_assert_msg(false, "Unhandled syntax error.");
756 continue;
757 }
758
759 /* Curly brace escapes. */
760 case TokenType::LEFT_CURLY_BRACE: {
761 strcpy(replacement_string, "{");
762 break;
763 }
764 case TokenType::RIGHT_CURLY_BRACE: {
765 strcpy(replacement_string, "}");
766 break;
767 }
768
769 /* Expand variable expression into the variable's value. */
770 case TokenType::VARIABLE_EXPRESSION: {
771 if (std::optional<blender::StringRefNull> string_value = template_variables.get_string(
772 token.variable_name))
773 {
774 /* String variable found, but we only process it if there's no format
775 * specifier: string variables do not support format specifiers. */
776 if (token.format.type != FormatSpecifierType::NONE) {
777 /* String variables don't take format specifiers: error. */
778 errors.append({ErrorType::FORMAT_SPECIFIER, token.byte_range});
779 continue;
780 }
781 BLI_strncpy(replacement_string, string_value->c_str(), sizeof(replacement_string));
782 break;
783 }
784
785 if (std::optional<int64_t> integer_value = template_variables.get_integer(
786 token.variable_name))
787 {
788 /* Integer variable found. */
789 format_int_to_string(token.format, *integer_value, replacement_string);
790 break;
791 }
792
793 if (std::optional<double> float_value = template_variables.get_float(token.variable_name))
794 {
795 /* Float variable found. */
796 format_float_to_string(token.format, *float_value, replacement_string);
797 break;
798 }
799
800 /* No matching variable found: error. */
801 errors.append({ErrorType::UNKNOWN_VARIABLE, token.byte_range});
802 continue;
803 }
804 }
805
806 /* Perform the actual substitution with the expanded value. */
807 if (out_path) {
808 /* We're off the end of the available space. */
809 if (token.byte_range.start() + length_diff >= out_path_max_length) {
810 break;
811 }
812
814 out_path_max_length,
815 token.byte_range.start() + length_diff,
816 token.byte_range.one_after_last() + length_diff,
817 replacement_string);
818
819 length_diff -= token.byte_range.size();
820 length_diff += strlen(replacement_string);
821 }
822 }
823
824 return errors;
825}
826
829{
830 return eval_template(nullptr, 0, path, template_variables);
831}
832
834 int path_max_length,
835 const VariableMap &template_variables)
836{
837 BLI_assert(path != nullptr);
838
839 blender::Vector<char> path_buffer(path_max_length);
840
842 path_buffer.data(), path_buffer.size(), path, template_variables);
843
844 if (errors.is_empty()) {
845 /* No errors, so copy the modified path back to the original. */
846 BLI_strncpy(path, path_buffer.data(), path_max_length);
847 }
848 return errors;
849}
850
852{
853 blender::StringRef subpath = path.substr(error.byte_range.start(), error.byte_range.size());
854
855 switch (error.type) {
857 return std::string("Unescaped curly brace '") + subpath + "'.";
858 }
859
861 return std::string("Invalid or incomplete template expression '") + subpath + "'.";
862 }
863
865 return std::string("Invalid format specifier in template expression '") + subpath + "'.";
866 }
867
869 return std::string("Unknown variable referenced in template expression '") + subpath + "'.";
870 }
871 }
872
873 BLI_assert_msg(false, "Unhandled error type.");
874 return "Unknown error.";
875}
876
878 const eReportType report_type,
881{
882 BLI_assert(!errors.is_empty());
883
884 std::string error_message = "Parse errors in path '" + path + "':";
885 for (const Error &error : errors) {
886 error_message += "\n- " + BKE_path_template_error_to_string(error, path);
887 }
888
889 BKE_report(reports, report_type, error_message.c_str());
890}
891
892std::optional<std::string> BKE_path_template_format_float(
893 const blender::StringRef format_specifier, const double value)
894{
895 const FormatSpecifier format = parse_format_specifier(format_specifier);
896 if (format.type == FormatSpecifierType::SYNTAX_ERROR) {
897 return std::nullopt;
898 }
899 char buffer[FORMAT_BUFFER_SIZE];
900 format_float_to_string(format, value, buffer);
901 return buffer;
902}
903
904std::optional<std::string> BKE_path_template_format_int(const blender::StringRef format_specifier,
905 const int64_t value)
906{
907 const FormatSpecifier format = parse_format_specifier(format_specifier);
908 if (format.type == FormatSpecifierType::SYNTAX_ERROR) {
909 return std::nullopt;
910 }
911 char buffer[FORMAT_BUFFER_SIZE];
912 format_int_to_string(format, value, buffer);
913 return buffer;
914}
Scene * CTX_data_scene(const bContext *C)
const char * BKE_main_blendfile_path_from_global()
Definition main.cc:882
Functions and classes for applying templates with variable expressions to filepaths.
void BKE_report_path_template_errors(ReportList *reports, eReportType report_type, blender::StringRef path, blender::Span< blender::bke::path_templates::Error > errors)
std::optional< blender::bke::path_templates::VariableMap > BKE_build_template_variables_for_prop(const bContext *C, PointerRNA *ptr, PropertyRNA *prop)
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)
std::string BKE_path_template_error_to_string(const blender::bke::path_templates::Error &error, blender::StringRef path)
blender::bke::path_templates::VariableMap BKE_build_template_variables_for_render_path(const RenderData *render_data)
blender::Vector< blender::bke::path_templates::Error > BKE_path_validate_template(blender::StringRef path, const blender::bke::path_templates::VariableMap &template_variables)
bool BKE_path_contains_template_syntax(blender::StringRef path)
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
void BKE_render_resolution(const RenderData *r, const bool use_crop, int *r_width, int *r_height)
Definition scene.cc:2927
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
void void void const char * BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
const char * BLI_path_extension_or_end(const char *filepath) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
size_t BLI_string_replace_range(char *string, size_t string_maxncpy, int src_beg, int src_end, const char *dst)
#define DATA_(msgid)
Enumerations for DNA_ID.h.
@ ID_SCE
@ PROP_VARIABLES_RENDER_OUTPUT
Definition RNA_types.hh:458
@ PROP_VARIABLES_NONE
Definition RNA_types.hh:457
@ PROP_PATH_SUPPORTS_TEMPLATES
Definition RNA_types.hh:443
#define C
Definition RandGen.cpp:29
ReportList * reports
Definition WM_types.hh:1025
@ NONE
long long int int64_t
static constexpr IndexRange from_begin_end(const int64_t begin, const int64_t end)
constexpr bool is_empty() const
Definition BLI_span.hh:260
constexpr int64_t find_last_of(StringRef chars, int64_t pos=INT64_MAX) const
constexpr int64_t find_first_not_of(StringRef chars, int64_t pos=0) const
constexpr bool is_empty() const
constexpr StringRef substr(int64_t start, int64_t size) const
void copy_bytes_truncated(char *dst, int64_t dst_size) const
Definition string_ref.cc:47
constexpr int64_t find_first_of(StringRef chars, int64_t pos=0) const
constexpr int64_t size() const
int64_t size() const
void append(const T &value)
bool is_empty() const
std::optional< blender::StringRefNull > get_string(blender::StringRef name) const
std::optional< int64_t > get_integer(blender::StringRef name) const
bool add_string(blender::StringRef name, blender::StringRef value)
bool contains(blender::StringRef name) const
bool add_float(blender::StringRef name, double value)
bool remove(blender::StringRef name)
bool add_integer(blender::StringRef name, int64_t value)
std::optional< double > get_float(blender::StringRef name) const
#define GS(a)
format
static int left
static void error(const char *str)
bool operator==(const Error &left, const Error &right)
static blender::Vector< Error > eval_template(char *out_path, const int out_path_max_length, blender::StringRef in_path, const VariableMap &template_variables)
static int format_float_to_string(const FormatSpecifier &format, const double float_value, char r_output_string[FORMAT_BUFFER_SIZE])
#define FORMAT_BUFFER_SIZE
static int format_int_to_string(const FormatSpecifier &format, const int64_t integer_value, char r_output_string[FORMAT_BUFFER_SIZE])
static blender::Vector< Token > parse_template(blender::StringRef path)
static std::optional< Token > next_token(blender::StringRef path, const int from_char)
static FormatSpecifier parse_format_specifier(blender::StringRef format_specifier)
blender::Vector< Error > BKE_path_apply_template(char *path, int path_max_length, const VariableMap &template_variables)
static std::optional< Error > token_to_syntax_error(const Token &token)
PropertyPathTemplateType RNA_property_path_template_type(PropertyRNA *prop)
int RNA_property_flag(PropertyRNA *prop)
@ FLOAT
struct RenderData r
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4227