From 5ff12bdc98d9687f42e5b5e9c84fd74e961b1a44 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Mon, 2 Jun 2025 11:40:05 +0200 Subject: [PATCH] Core: Show all path template errors in the UI The path templates feature implemented in #134860 highlights path properties that have syntax errors in red and provides a list of those errors in the path's tool tip. However, syntax errors are not the only kind of error. For example, if given the render output path `//render/{blend_nam}`, with a typo in the variable `blend_name`, this error is not indicated until actually trying to render. This is because the path is *syntactically* correct, and the error is due to `blend_nam` not being an available variable. This PR resolves this: paths with path template support now indicate all errors in the UI, not just syntax errors, with red highlights and a list of errors in the tool tip. The primary mechanisms for this are: - A new function `BKE_build_template_variables_for_prop()`, which takes an RNA pointer and produces an appropriate template variable map for the given property, which can then be used to fully validate a path property's value in the UI code. - A new enum `PropertyPathTemplateType` has been added to `PropertyRNA`, which determines what variables should be made available to a property and how they should be build. This is used by `BKE_build_template_variables_for_prop()`. Additionally, the following changes have been made to help ensure that `BKE_path_apply_template()` and `BKE_path_validate_template()` produce identical errors: - Both functions now call into a third static function `eval_template()` that does the actual work, optionally modifying the input path or not. - Previously only `BKE_path_apply_template()` had unit tests, but now the unit tests have been reorganized to simultaneously test both `BKE_path_apply_template()` and `BKE_path_validate_template()`. Pull Request: https://projects.blender.org/blender/blender/pulls/138679 --- .../blender/blenkernel/BKE_path_templates.hh | 52 ++- .../blenkernel/intern/path_templates.cc | 181 ++++++-- .../blenkernel/intern/path_templates_test.cc | 411 +++++++++--------- .../editors/interface/interface_layout.cc | 11 +- .../regions/interface_region_tooltip.cc | 22 +- .../blender/editors/render/render_opengl.cc | 12 +- .../editors/space_buttons/buttons_ops.cc | 9 +- .../blender/imbuf/movie/intern/movie_write.cc | 4 +- source/blender/makesrna/RNA_access.hh | 1 + source/blender/makesrna/RNA_define.hh | 2 + source/blender/makesrna/RNA_types.hh | 19 + source/blender/makesrna/intern/makesrna.cc | 1 + source/blender/makesrna/intern/rna_access.cc | 5 + source/blender/makesrna/intern/rna_define.cc | 6 + .../makesrna/intern/rna_internal_types.hh | 9 + .../blender/makesrna/intern/rna_nodetree.cc | 2 + source/blender/makesrna/intern/rna_scene.cc | 6 +- .../blender/makesrna/intern/rna_scene_api.cc | 2 +- .../nodes/node_composite_file_output.cc | 9 +- source/blender/render/intern/pipeline.cc | 12 +- 20 files changed, 491 insertions(+), 285 deletions(-) diff --git a/source/blender/blenkernel/BKE_path_templates.hh b/source/blender/blenkernel/BKE_path_templates.hh index 0536842bb21..bb8430c3f7b 100644 --- a/source/blender/blenkernel/BKE_path_templates.hh +++ b/source/blender/blenkernel/BKE_path_templates.hh @@ -22,6 +22,10 @@ #include "DNA_scene_types.h" +struct bContext; +struct PointerRNA; +struct PropertyRNA; + namespace blender::bke::path_templates { /** @@ -129,7 +133,22 @@ bool operator==(const Error &left, const Error &right); } // namespace blender::bke::path_templates /** - * Build a template variable map based on available information. + * Build a template variable map for the passed RNA property. + * + * \param C: the context to use for building some variables. This is needed in + * some cases when the property and its owner do not provide the data needed for + * a variable. This parameter can be null, but the variables it's needed for + * will then be absent in the returned variable map. + * + * \return On success, returns the template variables for the property. If no + * property is provided or if the property doesn't support path templates, + * returns nullopt. + */ +std::optional BKE_build_template_variables_for_prop( + const bContext *C, PointerRNA *ptr, PropertyRNA *prop); + +/** + * Build a template variable map for render output paths. * * All parameters are allowed to be null, in which case the variables derived * from those parameters will simply not be included. @@ -154,21 +173,34 @@ bool operator==(const Error &left, const Error &right); * * \see BLI_path_abs() */ -blender::bke::path_templates::VariableMap BKE_build_template_variables( +blender::bke::path_templates::VariableMap BKE_build_template_variables_for_render_path( const char *blend_file_path, const RenderData *render_data); /** - * Validate the template syntax in the given path. + * Check if a path contains any templating syntax at all. * - * This does *not* validate whether any variables referenced in the path exist - * or not, nor whether the format specification in a variable expression is - * appropriate for its type. This only validates that the template syntax itself - * is valid. + * This is primarily intended to be used as a pre-check in performance-sensitive + * code to skip path template processing when it's not needed. * - * \return An empty vector if valid, or a vector of the parse errors if invalid. + * \return False if the path contains no templating syntax (no template + * processing is needed). True if the path does contain templating syntax + * (template processing *is* needed). */ -blender::Vector BKE_validate_template_syntax( - blender::StringRef path); +bool BKE_path_contains_template_syntax(blender::StringRef path); + +/** + * Validate the templating in the given path. + * + * This produces identical errors as `BKE_path_apply_template()`, but + * without modifying the path on success. + * + * \return An empty vector if the templating in the path is valid, or a vector + * of the errors if invalid. + * + * \see BKE_path_apply_template() + */ +blender::Vector BKE_path_validate_template( + blender::StringRef path, const blender::bke::path_templates::VariableMap &template_variables); /** * Perform variable substitution and escaping on the given path. diff --git a/source/blender/blenkernel/intern/path_templates.cc b/source/blender/blenkernel/intern/path_templates.cc index 2ffba803720..a7b531aebaf 100644 --- a/source/blender/blenkernel/intern/path_templates.cc +++ b/source/blender/blenkernel/intern/path_templates.cc @@ -6,9 +6,19 @@ #include "BLT_translation.hh" +#include "BLI_span.hh" + +#include "BKE_context.hh" +#include "BKE_main.hh" #include "BKE_path_templates.hh" #include "BKE_scene.hh" +#include "RNA_access.hh" +#include "RNA_prototypes.hh" + +#include "DNA_ID_enums.h" +#include "DNA_node_types.h" + namespace blender::bke::path_templates { bool VariableMap::contains(blender::StringRef name) const @@ -102,8 +112,69 @@ bool operator==(const Error &left, const Error &right) using namespace blender::bke::path_templates; -VariableMap BKE_build_template_variables(const char *blend_file_path, - const RenderData *render_data) +std::optional BKE_build_template_variables_for_prop(const bContext *C, + PointerRNA *ptr, + PropertyRNA *prop) +{ + /* + * This function should be maintained such that it always produces variables + * consistent with the variables produced elsewhere in the code base for the + * same property. For example, render paths are processed in the rendering + * code and variables are built for that purpose there, and this function + * should produce variables consistent with that for those render path + * properties here. + * + * The recommended strategy when adding support for additional path templating + * use cases (that don't already have an appropriate + * `PropertyPathTemplateType` item) is to: + * + * 1. Create a separate function to build variables for that use case (see + * e.g. `BKE_build_template_variables_for_render_path()`). + * 2. Call that function from here in the switch statement below. + * 3. Also call that function from the other parts of the code base that need + * it. + */ + + /* No property passed, or it doesn't support path templates. */ + if (ptr == nullptr || prop == nullptr || + (RNA_property_flag(prop) & PROP_PATH_SUPPORTS_TEMPLATES) == 0) + { + return std::nullopt; + } + + switch (RNA_property_path_template_type(prop)) { + case PROP_VARIABLES_NONE: { + BLI_assert_msg( + false, + "Should never have `PROP_VARIABLES_NONE` for a path that supports path templates."); + return VariableMap(); + } + + /* Scene render output path, the compositor's File Output node's paths, etc. */ + case PROP_VARIABLES_RENDER_OUTPUT: { + const RenderData *render_data; + if (GS(ptr->owner_id->name) == ID_SCE) { + render_data = &reinterpret_cast(ptr->owner_id)->r; + } + else { + const Scene *scene = CTX_data_scene(C); + render_data = scene ? &scene->r : nullptr; + } + + return BKE_build_template_variables_for_render_path(BKE_main_blendfile_path_from_global(), + render_data); + } + } + + /* All paths that support path templates should be handled above, and any that + * aren't should already be rejected by the test at the top of the function. */ + BLI_assert_unreachable(); + + return std::nullopt; +} + +VariableMap BKE_build_template_variables_for_render_path(const char *blend_file_path, + const RenderData *render_data) { VariableMap variables; @@ -620,25 +691,41 @@ static std::optional token_to_syntax_error(const Token &token) return std::nullopt; } -blender::Vector BKE_validate_template_syntax(blender::StringRef path) +bool BKE_path_contains_template_syntax(blender::StringRef path) { - const blender::Vector tokens = parse_template(path); - - blender::Vector errors; - for (const Token &token : tokens) { - if (std::optional error = token_to_syntax_error(token)) { - errors.append(*error); - } - } - - return errors; + return path.find_first_of("{}") != std::string_view::npos; } -blender::Vector BKE_path_apply_template(char *path, - int path_max_length, - const VariableMap &template_variables) +/** + * Evaluates the path template in `in_path` and writes the result to `out_path` + * if provided. + * + * \param out_path: buffer to write the evaluated path to. May be null, in which + * case writing is skipped, and this function just acts to validate the + * templating in the path. + * + * \param out_path_max_length: The maximum length that template expansion is + * allowed to make the template-expanded path (in bytes), including the null + * terminator. In general, this should be the size of the underlying allocation + * of `out_path`. + * + * \param template_variables: map of variables and their values to use during + * template substitution. + * + * \return An empty vector on success, or a vector of templating errors on + * failure. Note that even if there are errors, `out_path` may get modified, and + * it should be treated as bogus data in that case. + */ +static blender::Vector eval_template(char *out_path, + const int out_path_max_length, + blender::StringRef in_path, + const VariableMap &template_variables) { - const blender::Vector tokens = parse_template(path); + if (out_path) { + in_path.copy_utf8_truncated(out_path, out_path_max_length); + } + + const blender::Vector tokens = parse_template(in_path); if (tokens.is_empty()) { /* No tokens found, so nothing to do. */ @@ -648,15 +735,6 @@ blender::Vector BKE_path_apply_template(char *path, /* Accumulates errors as we process the tokens. */ blender::Vector errors; - /* We work on a copy of the path, for two reasons: - * - * 1. So that if there are errors we can leave the original unmodified. - * 2. So that the contents of the StringRefs in the Token structs don't change - * out from under us while we're generating the modified path.*/ - blender::Vector path_buffer(path_max_length); - char *path_modified = path_buffer.data(); - strcpy(path_modified, path); - /* Tracks the change in string length due to the modifications as we go. We * need this to properly map the token byte ranges to the being-modified * string. */ @@ -701,7 +779,7 @@ blender::Vector BKE_path_apply_template(char *path, errors.append({ErrorType::FORMAT_SPECIFIER, token.byte_range}); continue; } - strcpy(replacement_string, string_value->c_str()); + BLI_strncpy(replacement_string, string_value->c_str(), sizeof(replacement_string)); break; } @@ -726,24 +804,47 @@ blender::Vector BKE_path_apply_template(char *path, } } - /* We're off the end of the available space. */ - if (token.byte_range.start() + length_diff >= path_max_length) { - break; + /* Perform the actual substitution with the expanded value. */ + if (out_path) { + /* We're off the end of the available space. */ + if (token.byte_range.start() + length_diff >= out_path_max_length) { + break; + } + + BLI_string_replace_range(out_path, + out_path_max_length, + token.byte_range.start() + length_diff, + token.byte_range.one_after_last() + length_diff, + replacement_string); + + length_diff -= token.byte_range.size(); + length_diff += strlen(replacement_string); } - - BLI_string_replace_range(path_modified, - path_max_length, - token.byte_range.start() + length_diff, - token.byte_range.one_after_last() + length_diff, - replacement_string); - - length_diff -= token.byte_range.size(); - length_diff += strlen(replacement_string); } + return errors; +} + +blender::Vector BKE_path_validate_template( + blender::StringRef path, const blender::bke::path_templates::VariableMap &template_variables) +{ + return eval_template(nullptr, 0, path, template_variables); +} + +blender::Vector BKE_path_apply_template(char *path, + int path_max_length, + const VariableMap &template_variables) +{ + BLI_assert(path != nullptr); + + blender::Vector path_buffer(path_max_length); + + const blender::Vector errors = eval_template( + path_buffer.data(), path_buffer.size(), path, template_variables); + if (errors.is_empty()) { /* No errors, so copy the modified path back to the original. */ - strcpy(path, path_modified); + BLI_strncpy(path, path_buffer.data(), path_max_length); } return errors; } diff --git a/source/blender/blenkernel/intern/path_templates_test.cc b/source/blender/blenkernel/intern/path_templates_test.cc index 6ab99bb2072..1aa663bfe7a 100644 --- a/source/blender/blenkernel/intern/path_templates_test.cc +++ b/source/blender/blenkernel/intern/path_templates_test.cc @@ -12,7 +12,7 @@ namespace blender::bke::tests { using namespace blender::bke::path_templates; -[[maybe_unused]] static void debug_print_error(const Error &error) +static std::string error_to_string(const Error &error) { const char *type; switch (error.type) { @@ -29,17 +29,35 @@ using namespace blender::bke::path_templates; type = "UNKNOWN_VARIABLE"; break; } - fmt::print("({}, ({}, {}))", type, error.byte_range.start(), error.byte_range.size()); + + std::string s; + fmt::format_to(std::back_inserter(s), + "({}, ({}, {}))", + type, + error.byte_range.start(), + error.byte_range.size()); + + return s; } -[[maybe_unused]] static void debug_print_errors(Span errors) +static std::string errors_to_string(Span errors) { - fmt::print("["); + std::string s; + + fmt::format_to(std::back_inserter(s), "["); + bool is_first = true; for (const Error &error : errors) { - debug_print_error(error); - fmt::print(", "); + if (is_first) { + is_first = false; + } + else { + fmt::format_to(std::back_inserter(s), ", "); + } + fmt::format_to(std::back_inserter(s), "{}", error_to_string(error)); } - fmt::print("]\n"); + fmt::format_to(std::back_inserter(s), "]"); + + return s; } TEST(path_templates, VariableMap) @@ -107,7 +125,13 @@ TEST(path_templates, VariableMap) EXPECT_FALSE(map.remove("what")); } -TEST(path_templates, path_apply_variables) +struct PathTemplateTestCase { + char path_in[FILE_MAX]; + char path_result[FILE_MAX]; + Vector expected_errors; +}; + +TEST(path_templates, validate_and_apply_template) { VariableMap variables; { @@ -126,218 +150,205 @@ TEST(path_templates, path_apply_variables) variables.add_float("tiny", 0.000000000000000000000000000000002); } - /* Simple case, testing all variables. */ - { - char path[FILE_MAX] = - "{hi}_{bye}_{the_answer}_{prime}_{i_negative}_{pi}_{e}_{ntsc}_{two}_{f_negative}_{huge}_{" - "tiny}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); + const Vector test_cases = { + /* Simple case, testing all variables. */ + { + "{hi}_{bye}_{the_answer}_{prime}_{i_negative}_{pi}_{e}_{ntsc}_{two}_{f_negative}_{huge}_" + "{tiny}", + "hello_goodbye_42_7_-7_3.141592653589793_2.718281828459045_29.970029970029973_2.0_-3." + "141592653589793_2e+32_2e-33", + {}, + }, - EXPECT_TRUE(errors.is_empty()); - EXPECT_EQ(blender::StringRef(path), - "hello_goodbye_42_7_-7_3.141592653589793_2.718281828459045_29.970029970029973_2.0_-" - "3.141592653589793_2e+32_2e-33"); - } + /* Integer formatting. */ + { + "{the_answer:#}_{the_answer:##}_{the_answer:####}_{i_negative:####}", + "42_42_0042_-007", + {}, + }, - /* Integer formatting. */ - { - char path[FILE_MAX] = "{the_answer:#}_{the_answer:##}_{the_answer:####}_{i_negative:####}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); + /* Integer formatting as float. */ + { + "{the_answer:.###}_{the_answer:#.##}_{the_answer:###.##}_{i_negative:###.####}", + "42.000_42.00_042.00_-07.0000", + {}, + }, - EXPECT_TRUE(errors.is_empty()); - EXPECT_EQ(blender::StringRef(path), "42_42_0042_-007"); - } + /* Float formatting: specify fractional digits only. */ + { + "{pi:.####}_{e:.###}_{ntsc:.########}_{two:.##}_{f_negative:.##}_{huge:.##}_{tiny:.##}", + "3.1416_2.718_29.97002997_2.00_-3.14_200000000000000010732324408786944.00_0.00", + {}, + }, - /* Integer formatting as float. */ - { - char path[FILE_MAX] = - "{the_answer:.###}_{the_answer:#.##}_{the_answer:###.##}_{i_negative:###.####}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); + /* Float formatting: specify both integer and fractional digits. */ + { + "{pi:##.####}_{e:####.###}_{ntsc:#.########}_{two:###.##}_{f_negative:###.##}_{huge:###." + "##}_{tiny:###.##}", + "03.1416_0002.718_29.97002997_002.00_-03.14_200000000000000010732324408786944.00_000.00", + {}, + }, - EXPECT_TRUE(errors.is_empty()); - EXPECT_EQ(blender::StringRef(path), "42.000_42.00_042.00_-07.0000"); - } + /* Float formatting: format as integer. */ + { + "{pi:##}_{e:####}_{ntsc:#}_{two:###}", + "03_0003_30_002", + {}, + }, - /* Float formatting: specify fractional digits only. */ - { - char path[FILE_MAX] = - "{pi:.####}_{e:.###}_{ntsc:.########}_{two:.##}_{f_negative:.##}_{huge:.##}_{tiny:.##}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); + /* Escaping. "{{" and "}}" are the escape codes for literal "{" and "}". */ + { + "{hi}_{{hi}}_{{{bye}}}_{bye}", + "hello_{hi}_{goodbye}_goodbye", + {}, + }, - EXPECT_TRUE(errors.is_empty()); - EXPECT_EQ(blender::StringRef(path), - "3.1416_2.718_29.97002997_2.00_-3.14_200000000000000010732324408786944.00_0.00"); - } + /* Error: string variables do not support format specifiers. */ + { + "{hi:##}_{bye:#}", + "{hi:##}_{bye:#}", + { + {ErrorType::FORMAT_SPECIFIER, IndexRange(0, 7)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(8, 7)}, + }, + }, - /* Float formatting: specify both integer and fractional digits. */ - { - char path[FILE_MAX] = - "{pi:##.####}_{e:####.###}_{ntsc:#.########}_{two:###.##}_{f_negative:###.##}_{huge:###.##" - "}_{tiny:###.##}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); + /* Error: float formatting: specifying integer digits only (but still wanting + * it printed as a float) is currently not supported. */ + { + "{pi:##.}_{e:####.}_{ntsc:#.}_{two:###.}_{f_negative:###.}_{huge:###.}_{tiny:###.}", + "{pi:##.}_{e:####.}_{ntsc:#.}_{two:###.}_{f_negative:###.}_{huge:###.}_{tiny:###.}", + { + {ErrorType::FORMAT_SPECIFIER, IndexRange(0, 8)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(9, 9)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(19, 9)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(29, 10)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(40, 17)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(58, 11)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(70, 11)}, + }, + }, - EXPECT_TRUE(errors.is_empty()); - EXPECT_EQ( - blender::StringRef(path), - "03.1416_0002.718_29.97002997_002.00_-03.14_200000000000000010732324408786944.00_000.00"); - } + /* Error: missing variable. */ + { + "{hi}_{missing}_{bye}", + "{hi}_{missing}_{bye}", + { + {ErrorType::UNKNOWN_VARIABLE, IndexRange(5, 9)}, + }, + }, - /* Float formatting: format as integer. */ - { - char path[FILE_MAX] = "{pi:##}_{e:####}_{ntsc:#}_{two:###}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); + /* Error: incomplete variable expression. */ + { + "foo{hi", + "foo{hi", + { + {ErrorType::VARIABLE_SYNTAX, IndexRange(3, 3)}, + }, + }, - EXPECT_TRUE(errors.is_empty()); - EXPECT_EQ(blender::StringRef(path), "03_0003_30_002"); - } + /* Error: incomplete variable expression after complete one. */ + { + "foo{bye}{hi", + "foo{bye}{hi", + { + {ErrorType::VARIABLE_SYNTAX, IndexRange(8, 3)}, + }, + }, - /* Escaping. "{{" and "}}" are the escape codes for literal "{" and "}". */ - { - char path[FILE_MAX] = "{hi}_{{hi}}_{{{bye}}}_{bye}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); + /* Error: invalid format specifiers. */ + { + "{prime:}_{prime:.}_{prime:#.#.#}_{prime:sup}_{prime::sup}_{prime}", + "{prime:}_{prime:.}_{prime:#.#.#}_{prime:sup}_{prime::sup}_{prime}", + { + {ErrorType::FORMAT_SPECIFIER, IndexRange(0, 8)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(9, 9)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(19, 13)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(33, 11)}, + {ErrorType::FORMAT_SPECIFIER, IndexRange(45, 12)}, + }, + }, - EXPECT_TRUE(errors.is_empty()); - EXPECT_EQ(blender::StringRef(path), "hello_{hi}_{goodbye}_goodbye"); - } + /* Error: unclosed variable. */ + { + "{hi_{hi}_{bye}", + "{hi_{hi}_{bye}", + { + {ErrorType::VARIABLE_SYNTAX, IndexRange(0, 4)}, + }, + }, - /* Error: string variables do not support format specifiers. */ - { - char path[FILE_MAX] = "{hi:##}_{bye:#}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); - const Vector expected_errors = { - {ErrorType::FORMAT_SPECIFIER, IndexRange(0, 7)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(8, 7)}, - }; + /* Error: escaped braces inside variable. */ + { + "{hi_{{hi}}_{bye}", + "{hi_{{hi}}_{bye}", + { + {ErrorType::VARIABLE_SYNTAX, IndexRange(0, 4)}, + }, + }, - EXPECT_EQ(errors, expected_errors); - EXPECT_EQ(blender::StringRef(path), "{hi:##}_{bye:#}"); - } + /* Test what happens when the path would expand to a string that's longer than + * `FILE_MAX`. + * + * We don't care so much about any kind of "correctness" here, we just want to + * ensure that it still results in a valid null-terminated string that fits in + * `FILE_MAX` bytes. + * + * NOTE: this test will have to be updated if `FILE_MAX` is ever changed. */ + { + "___{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}" + "{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" + "long}{long}", - /* Error: float formatting: specifying integer digits only (but still wanting - * it printed as a float) is currently not supported. */ - { - char path[FILE_MAX] = - "{pi:##.}_{e:####.}_{ntsc:#.}_{two:###.}_{f_negative:###.}_{huge:###.}_{tiny:###.}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); - const Vector expected_errors = { - {ErrorType::FORMAT_SPECIFIER, IndexRange(0, 8)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(9, 9)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(19, 9)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(29, 10)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(40, 17)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(58, 11)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(70, 11)}, - }; + "___This string is exactly 32 bytes.This string is exactly 32 bytes.This string is " + "exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This " + "string is exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 " + "bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This string is " + "exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This " + "string is exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 " + "bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This string is " + "exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This " + "string is exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 " + "bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This string is " + "exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This " + "string is exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 " + "by", - EXPECT_EQ(errors, expected_errors); - EXPECT_EQ(blender::StringRef(path), - "{pi:##.}_{e:####.}_{ntsc:#.}_{two:###.}_{f_negative:###.}_{huge:###.}_{tiny:###.}"); - } + {}, + }, + }; - /* Error: missing variable. */ - { - char path[FILE_MAX] = "{hi}_{missing}_{bye}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); - const Vector expected_errors = { - {ErrorType::UNKNOWN_VARIABLE, IndexRange(5, 9)}, - }; + for (const PathTemplateTestCase &test_case : test_cases) { + char path[FILE_MAX]; + BLI_strncpy(path, test_case.path_in, FILE_MAX); - EXPECT_EQ(errors, expected_errors); - EXPECT_EQ(blender::StringRef(path), "{hi}_{missing}_{bye}"); - } + /* Do validation first, which shouldn't modify the path. */ + const Vector validation_errors = BKE_path_validate_template(path, variables); + EXPECT_EQ(validation_errors, test_case.expected_errors) + << " Template errors: " << errors_to_string(validation_errors) << std::endl + << " Expected errors: " << errors_to_string(test_case.expected_errors) << std::endl + << " Note: test_case.path_in = " << test_case.path_in << std::endl; + EXPECT_EQ(blender::StringRef(path), test_case.path_in) + << " Note: test_case.path_in = " << test_case.path_in << std::endl; - /* Error: incomplete variable expression. */ - { - char path[FILE_MAX] = "foo{hi"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); - const Vector expected_errors = { - {ErrorType::VARIABLE_SYNTAX, IndexRange(3, 3)}, - }; - - EXPECT_EQ(errors, expected_errors); - EXPECT_EQ(blender::StringRef(path), "foo{hi"); - } - - /* Error: invalid format specifiers. */ - { - char path[FILE_MAX] = "{prime:}_{prime:.}_{prime:#.#.#}_{prime:sup}_{prime::sup}_{prime}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); - const Vector expected_errors = { - {ErrorType::FORMAT_SPECIFIER, IndexRange(0, 8)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(9, 9)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(19, 13)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(33, 11)}, - {ErrorType::FORMAT_SPECIFIER, IndexRange(45, 12)}, - }; - - EXPECT_EQ(errors, expected_errors); - EXPECT_EQ(blender::StringRef(path), - "{prime:}_{prime:.}_{prime:#.#.#}_{prime:sup}_{prime::sup}_{prime}"); - } - - /* Error: unclosed variable. */ - { - char path[FILE_MAX] = "{hi_{hi}_{bye}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); - const Vector expected_errors = { - {ErrorType::VARIABLE_SYNTAX, IndexRange(0, 4)}, - }; - - EXPECT_EQ(errors, expected_errors); - EXPECT_EQ(blender::StringRef(path), "{hi_{hi}_{bye}"); - } - - /* Error: escaped braces inside variable. */ - { - char path[FILE_MAX] = "{hi_{{hi}}_{bye}"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); - const Vector expected_errors = { - {ErrorType::VARIABLE_SYNTAX, IndexRange(0, 4)}, - }; - - EXPECT_EQ(errors, expected_errors); - EXPECT_EQ(blender::StringRef(path), "{hi_{{hi}}_{bye}"); - } - - /* Test what happens when the path would expand to a string that's longer than - * `FILE_MAX`. - * - * We don't care so much about any kind of "correctness" here, we just want to - * ensure that it still results in a valid null-terminated string that fits in - * `FILE_MAX` bytes. - * - * NOTE: this test will have to be updated if `FILE_MAX` is ever changed. */ - { - char path[FILE_MAX] = - "___{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" - "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}" - "{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" - "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}" - "{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" - "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}" - "{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" - "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}" - "{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" - "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}" - "{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{" - "long}{long}{long}{long}{long}{long}{long}{long}{long}{long}{long}"; - const char result[FILE_MAX] = - "___This string is exactly 32 bytes.This string is exactly 32 bytes.This string is " - "exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This " - "string is exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 " - "bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This string is " - "exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This " - "string is exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 " - "bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This string is " - "exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This " - "string is exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 " - "bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This string is " - "exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 bytes.This " - "string is exactly 32 bytes.This string is exactly 32 bytes.This string is exactly 32 by"; - const Vector errors = BKE_path_apply_template(path, FILE_MAX, variables); - - EXPECT_TRUE(errors.is_empty()); - EXPECT_EQ(blender::StringRef(path), blender::StringRef(result)); + /* Then do application, which should modify the path. */ + const Vector application_errors = BKE_path_apply_template(path, FILE_MAX, variables); + EXPECT_EQ(application_errors, test_case.expected_errors) + << " Template errors: " << errors_to_string(application_errors) << std::endl + << " Expected errors: " << errors_to_string(test_case.expected_errors) << std::endl + << " Note: test_case.path_in = " << test_case.path_in << std::endl; + EXPECT_EQ(blender::StringRef(path), test_case.path_result) + << " Note: test_case.path_in = " << test_case.path_in << std::endl; } } diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc index 4c31e6374c8..24d70e8c843 100644 --- a/source/blender/editors/interface/interface_layout.cc +++ b/source/blender/editors/interface/interface_layout.cc @@ -1122,8 +1122,15 @@ static uiBut *ui_item_with_label(uiLayout *layout, if (ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_NONE)) { if ((RNA_property_flag(prop) & PROP_PATH_SUPPORTS_TEMPLATES) != 0) { const std::string path = RNA_property_string_get(ptr, prop); - if (!BKE_validate_template_syntax(path.c_str()).is_empty()) { - UI_but_flag_enable(but, UI_BUT_REDALERT); + if (BKE_path_contains_template_syntax(path)) { + const std::optional variables = + BKE_build_template_variables_for_prop( + static_cast(block->evil_C), ptr, prop); + BLI_assert(variables.has_value()); + + if (!BKE_path_validate_template(path, *variables).is_empty()) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } } } } diff --git a/source/blender/editors/interface/regions/interface_region_tooltip.cc b/source/blender/editors/interface/regions/interface_region_tooltip.cc index 83f5639e7c7..bf3c76bfbc7 100644 --- a/source/blender/editors/interface/regions/interface_region_tooltip.cc +++ b/source/blender/editors/interface/regions/interface_region_tooltip.cc @@ -1071,16 +1071,22 @@ static std::unique_ptr ui_tooltip_data_from_button_or_extra_icon( /* Template parse errors, for paths that support it. */ if ((RNA_property_flag(rnaprop) & PROP_PATH_SUPPORTS_TEMPLATES) != 0) { const std::string path = RNA_property_string_get(&but->rnapoin, rnaprop); - const blender::Vector errors = - BKE_validate_template_syntax(path); + if (BKE_path_contains_template_syntax(path)) { + const std::optional variables = + BKE_build_template_variables_for_prop(C, &but->rnapoin, rnaprop); + BLI_assert(variables.has_value()); - if (!errors.is_empty()) { - std::string error_message("Syntax error(s):"); - for (const blender::bke::path_templates::Error &error : errors) { - error_message += "\n - " + BKE_path_template_error_to_string(error, path); + const blender::Vector errors = + BKE_path_validate_template(path, *variables); + + if (!errors.is_empty()) { + std::string error_message("Path template error(s):"); + for (const blender::bke::path_templates::Error &error : errors) { + error_message += "\n - " + BKE_path_template_error_to_string(error, path); + } + UI_tooltip_text_field_add( + *data, error_message, {}, UI_TIP_STYLE_NORMAL, UI_TIP_LC_ALERT); } - UI_tooltip_text_field_add( - *data, error_message, {}, UI_TIP_STYLE_NORMAL, UI_TIP_LC_ALERT); } } } diff --git a/source/blender/editors/render/render_opengl.cc b/source/blender/editors/render/render_opengl.cc index 63932307eda..56fa310c9f1 100644 --- a/source/blender/editors/render/render_opengl.cc +++ b/source/blender/editors/render/render_opengl.cc @@ -418,8 +418,8 @@ static void screen_opengl_render_write(OGLRender *oglrender) rr = RE_AcquireResultRead(oglrender->re); const char *relbase = BKE_main_blendfile_path(oglrender->bmain); - const path_templates::VariableMap template_variables = BKE_build_template_variables(relbase, - &scene->r); + const path_templates::VariableMap template_variables = + BKE_build_template_variables_for_render_path(relbase, &scene->r); const blender::Vector errors = BKE_image_path_from_imformat( filepath, scene->r.pic, @@ -1048,8 +1048,8 @@ static void write_result(TaskPool *__restrict pool, WriteTaskData *task_data) */ char filepath[FILE_MAX]; const char *relbase = BKE_main_blendfile_path(oglrender->bmain); - const path_templates::VariableMap template_variables = BKE_build_template_variables(relbase, - &scene->r); + const path_templates::VariableMap template_variables = + BKE_build_template_variables_for_render_path(relbase, &scene->r); const blender::Vector errors = BKE_image_path_from_imformat( filepath, scene->r.pic, @@ -1149,8 +1149,8 @@ static bool screen_opengl_render_anim_step(OGLRender *oglrender) if (!is_movie) { const char *relbase = BKE_main_blendfile_path(oglrender->bmain); - const path_templates::VariableMap template_variables = BKE_build_template_variables(relbase, - &scene->r); + const path_templates::VariableMap template_variables = + BKE_build_template_variables_for_render_path(relbase, &scene->r); const blender::Vector errors = BKE_image_path_from_imformat( filepath, scene->r.pic, diff --git a/source/blender/editors/space_buttons/buttons_ops.cc b/source/blender/editors/space_buttons/buttons_ops.cc index 48a8c5a238a..c6f1e2a0534 100644 --- a/source/blender/editors/space_buttons/buttons_ops.cc +++ b/source/blender/editors/space_buttons/buttons_ops.cc @@ -304,11 +304,12 @@ static wmOperatorStatus file_browse_invoke(bContext *C, wmOperator *op, const wm path = RNA_property_string_get_alloc(&ptr, prop, nullptr, 0, nullptr); if ((RNA_property_flag(prop) & PROP_PATH_SUPPORTS_TEMPLATES) != 0) { - const Scene *scene = CTX_data_scene(C); + const std::optional variables = + BKE_build_template_variables_for_prop(C, &ptr, prop); + BLI_assert(variables.has_value()); + const blender::Vector errors = BKE_path_apply_template( - path, - FILE_MAX, - BKE_build_template_variables(BKE_main_blendfile_path_from_global(), &scene->r)); + path, FILE_MAX, *variables); if (!errors.is_empty()) { BKE_report_path_template_errors(op->reports, RPT_ERROR, path, errors); return OPERATOR_CANCELLED; diff --git a/source/blender/imbuf/movie/intern/movie_write.cc b/source/blender/imbuf/movie/intern/movie_write.cc index 6d70d174bdd..88f3d3d52a8 100644 --- a/source/blender/imbuf/movie/intern/movie_write.cc +++ b/source/blender/imbuf/movie/intern/movie_write.cc @@ -1311,7 +1311,9 @@ static bool ffmpeg_filepath_get(MovieWriter *context, BLI_strncpy(filepath, rd->pic, FILE_MAX); const blender::Vector errors = BKE_path_apply_template( - filepath, FILE_MAX, BKE_build_template_variables(BKE_main_blendfile_path_from_global(), rd)); + filepath, + FILE_MAX, + BKE_build_template_variables_for_render_path(BKE_main_blendfile_path_from_global(), rd)); if (!errors.is_empty()) { BKE_report_path_template_errors(reports, RPT_ERROR, filepath, errors); return false; diff --git a/source/blender/makesrna/RNA_access.hh b/source/blender/makesrna/RNA_access.hh index 5969dc0c39f..f1561393341 100644 --- a/source/blender/makesrna/RNA_access.hh +++ b/source/blender/makesrna/RNA_access.hh @@ -223,6 +223,7 @@ int RNA_property_override_flag(PropertyRNA *prop); * the only way to set tags. Hence, at this point we assume the tag bit-field to be valid. */ int RNA_property_tags(PropertyRNA *prop); +PropertyPathTemplateType RNA_property_path_template_type(PropertyRNA *prop); bool RNA_property_builtin(PropertyRNA *prop); void *RNA_property_py_data_get(PropertyRNA *prop); diff --git a/source/blender/makesrna/RNA_define.hh b/source/blender/makesrna/RNA_define.hh index 3c2855955b4..67007d4c120 100644 --- a/source/blender/makesrna/RNA_define.hh +++ b/source/blender/makesrna/RNA_define.hh @@ -582,6 +582,8 @@ void RNA_def_parameter_flags(PropertyRNA *prop, void RNA_def_parameter_clear_flags(PropertyRNA *prop, PropertyFlag flag_property, ParameterFlag flag_parameter); +void RNA_def_property_path_template_type(PropertyRNA *prop, + PropertyPathTemplateType path_template_type); /* Dynamic Enums * strings are not freed, assumed pointing to static location. */ diff --git a/source/blender/makesrna/RNA_types.hh b/source/blender/makesrna/RNA_types.hh index 81bd35f1528..347bd0cae9c 100644 --- a/source/blender/makesrna/RNA_types.hh +++ b/source/blender/makesrna/RNA_types.hh @@ -431,6 +431,14 @@ enum PropertyFlag { /** * Paths that are evaluated with templating. + * + * Note that this doesn't cause the property to support templating, but rather + * *indicates* to other parts of Blender whether it supports templating. + * Support for templating needs to be manually implemented. + * + * When this is set, the property's `path_template_type` field should also be + * set to something other than `PROP_VARIABLES_NONE`, to indicate which + * template variables it supports. */ PROP_PATH_SUPPORTS_TEMPLATES = (1 << 14), @@ -439,6 +447,17 @@ enum PropertyFlag { }; ENUM_OPERATORS(PropertyFlag, PROP_TEXTEDIT_UPDATE) +/** + * For properties that support path templates, this indicates which variables + * should be available to them and how those variables should be built. + * + * \see BKE_build_template_variables_for_prop() + */ +enum PropertyPathTemplateType { + PROP_VARIABLES_NONE = 0, + PROP_VARIABLES_RENDER_OUTPUT, +}; + /** * Flags related to comparing and overriding RNA properties. * Make sure enums are updated with these. diff --git a/source/blender/makesrna/intern/makesrna.cc b/source/blender/makesrna/intern/makesrna.cc index 683c166a41b..563c0326d0a 100644 --- a/source/blender/makesrna/intern/makesrna.cc +++ b/source/blender/makesrna/intern/makesrna.cc @@ -4350,6 +4350,7 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr prop->flag_parameter, prop->flag_internal, prop->tags); + fprintf(f, "PropertyPathTemplateType(%d), ", prop->path_template_type); rna_print_c_string(f, prop->name); fprintf(f, ",\n\t"); rna_print_c_string(f, prop->description); diff --git a/source/blender/makesrna/intern/rna_access.cc b/source/blender/makesrna/intern/rna_access.cc index 3aa03049c6c..bd28ad831cf 100644 --- a/source/blender/makesrna/intern/rna_access.cc +++ b/source/blender/makesrna/intern/rna_access.cc @@ -1210,6 +1210,11 @@ int RNA_property_tags(PropertyRNA *prop) return rna_ensure_property(prop)->tags; } +PropertyPathTemplateType RNA_property_path_template_type(PropertyRNA *prop) +{ + return rna_ensure_property(prop)->path_template_type; +} + bool RNA_property_builtin(PropertyRNA *prop) { return (rna_ensure_property(prop)->flag_internal & PROP_INTERN_BUILTIN) != 0; diff --git a/source/blender/makesrna/intern/rna_define.cc b/source/blender/makesrna/intern/rna_define.cc index 84e208eb22f..9940b6b037b 100644 --- a/source/blender/makesrna/intern/rna_define.cc +++ b/source/blender/makesrna/intern/rna_define.cc @@ -1557,6 +1557,12 @@ void RNA_def_parameter_clear_flags(PropertyRNA *prop, prop->flag_parameter &= ~flag_parameter; } +void RNA_def_property_path_template_type(PropertyRNA *prop, + PropertyPathTemplateType path_template_type) +{ + prop->path_template_type = path_template_type; +} + void RNA_def_property_subtype(PropertyRNA *prop, PropertySubType subtype) { prop->subtype = subtype; diff --git a/source/blender/makesrna/intern/rna_internal_types.hh b/source/blender/makesrna/intern/rna_internal_types.hh index 60bf45aa683..bacb2a59b94 100644 --- a/source/blender/makesrna/intern/rna_internal_types.hh +++ b/source/blender/makesrna/intern/rna_internal_types.hh @@ -350,6 +350,15 @@ struct PropertyRNA { /** The subset of #StructRNA::prop_tag_defines values that applies to this property. */ short tags; + /** + * Indicates which set of template variables this property supports. + * + * Must be set for path properties that are marked as supporting path + * templates (`PROP_PATH_SUPPORTS_TEMPLATES` in `flag`). Is ignored for other + * properties. + */ + PropertyPathTemplateType path_template_type; + /** User readable name. */ const char *name; /** Single line description, displayed in the tool-tip for example. */ diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index a3bdf79ecea..9961f4020bc 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -7829,6 +7829,7 @@ static void rna_def_cmp_output_file_slot_file(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Path", "Subpath used for this slot"); RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_EDITOR_FILEBROWSER); RNA_def_property_flag(prop, PROP_PATH_OUTPUT | PROP_PATH_SUPPORTS_TEMPLATES); + RNA_def_property_path_template_type(prop, PROP_VARIABLES_RENDER_OUTPUT); RNA_def_property_update(prop, NC_NODE | NA_EDITED, nullptr); } static void rna_def_cmp_output_file_slot_layer(BlenderRNA *brna) @@ -7907,6 +7908,7 @@ static void def_cmp_output_file(BlenderRNA *brna, StructRNA *srna) RNA_def_property_ui_text(prop, "Base Path", "Base output path for the image"); RNA_def_property_flag( prop, PROP_PATH_OUTPUT | PROP_PATH_SUPPORTS_BLEND_RELATIVE | PROP_PATH_SUPPORTS_TEMPLATES); + RNA_def_property_path_template_type(prop, PROP_VARIABLES_RENDER_OUTPUT); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "active_input_index", PROP_INT, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index c8398ac8909..540871cd347 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -7354,13 +7354,13 @@ static void rna_def_scene_render_data(BlenderRNA *brna) prop = RNA_def_property(srna, "filepath", PROP_STRING, PROP_FILEPATH); RNA_def_property_string_sdna(prop, nullptr, "pic"); - RNA_def_property_flag(prop, PROP_PATH_SUPPORTS_BLEND_RELATIVE); + RNA_def_property_flag( + prop, PROP_PATH_OUTPUT | PROP_PATH_SUPPORTS_BLEND_RELATIVE | PROP_PATH_SUPPORTS_TEMPLATES); + RNA_def_property_path_template_type(prop, PROP_VARIABLES_RENDER_OUTPUT); RNA_def_property_ui_text(prop, "Output Path", "Directory/name to save animations, # characters define the position " "and padding of frame numbers"); - RNA_def_property_flag( - prop, PROP_PATH_OUTPUT | PROP_PATH_SUPPORTS_BLEND_RELATIVE | PROP_PATH_SUPPORTS_TEMPLATES); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr); /* Render result EXR cache. */ diff --git a/source/blender/makesrna/intern/rna_scene_api.cc b/source/blender/makesrna/intern/rna_scene_api.cc index a4a6d1988a9..de9ce72edb9 100644 --- a/source/blender/makesrna/intern/rna_scene_api.cc +++ b/source/blender/makesrna/intern/rna_scene_api.cc @@ -119,7 +119,7 @@ static void rna_SceneRender_get_frame_path(RenderData *rd, else { const char *relbase = BKE_main_blendfile_path(bmain); const blender::bke::path_templates::VariableMap template_variables = - BKE_build_template_variables(relbase, rd); + BKE_build_template_variables_for_render_path(relbase, rd); const blender::Vector errors = BKE_image_path_from_imformat(filepath, diff --git a/source/blender/nodes/composite/nodes/node_composite_file_output.cc b/source/blender/nodes/composite/nodes/node_composite_file_output.cc index 1a0d1b934cd..7aea957e01d 100644 --- a/source/blender/nodes/composite/nodes/node_composite_file_output.cc +++ b/source/blender/nodes/composite/nodes/node_composite_file_output.cc @@ -870,8 +870,9 @@ class FileOutputOperation : public NodeOperation { */ bool get_single_layer_image_base_path(const char *base_name, char *r_base_path) { - const path_templates::VariableMap template_variables = BKE_build_template_variables( - BKE_main_blendfile_path_from_global(), &context().get_render_data()); + const path_templates::VariableMap template_variables = + BKE_build_template_variables_for_render_path(BKE_main_blendfile_path_from_global(), + &context().get_render_data()); /* Do template expansion on the node's base path. */ char node_base_path[FILE_MAX] = ""; @@ -953,8 +954,8 @@ class FileOutputOperation : public NodeOperation { const RenderData &render_data = context().get_render_data(); const char *suffix = BKE_scene_multiview_view_suffix_get(&render_data, view); const char *relbase = BKE_main_blendfile_path_from_global(); - const path_templates::VariableMap template_variables = BKE_build_template_variables( - relbase, &render_data); + const path_templates::VariableMap template_variables = + BKE_build_template_variables_for_render_path(relbase, &render_data); blender::Vector errors = BKE_image_path_from_imtype( r_image_path, base_path, diff --git a/source/blender/render/intern/pipeline.cc b/source/blender/render/intern/pipeline.cc index f532e59d183..5189e7edf0b 100644 --- a/source/blender/render/intern/pipeline.cc +++ b/source/blender/render/intern/pipeline.cc @@ -2097,8 +2097,8 @@ void RE_RenderFrame(Render *re, else { char filepath_override[FILE_MAX]; const char *relbase = BKE_main_blendfile_path(bmain); - const path_templates::VariableMap template_variables = BKE_build_template_variables( - relbase, &scene->r); + const path_templates::VariableMap template_variables = + BKE_build_template_variables_for_render_path(relbase, &scene->r); const blender::Vector errors = BKE_image_path_from_imformat( filepath_override, rd.pic, @@ -2329,8 +2329,8 @@ static bool do_write_image_or_movie( } else { const char *relbase = BKE_main_blendfile_path(bmain); - const path_templates::VariableMap template_variables = BKE_build_template_variables( - relbase, &scene->r); + const path_templates::VariableMap template_variables = + BKE_build_template_variables_for_render_path(relbase, &scene->r); const blender::Vector errors = BKE_image_path_from_imformat( filepath, scene->r.pic, @@ -2532,8 +2532,8 @@ void RE_RenderAnim(Render *re, /* Touch/NoOverwrite options are only valid for image's */ if (is_movie == false && do_write_file) { const char *relbase = BKE_main_blendfile_path(bmain); - const path_templates::VariableMap template_variables = BKE_build_template_variables(relbase, - &rd); + const path_templates::VariableMap template_variables = + BKE_build_template_variables_for_render_path(relbase, &rd); const blender::Vector errors = BKE_image_path_from_imformat( filepath, rd.pic,