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
This commit is contained in:
committed by
Nathan Vegdahl
parent
0a1ff2b2ff
commit
5ff12bdc98
@@ -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<blender::bke::path_templates::VariableMap> 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<blender::bke::path_templates::Error> 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<blender::bke::path_templates::Error> BKE_path_validate_template(
|
||||
blender::StringRef path, const blender::bke::path_templates::VariableMap &template_variables);
|
||||
|
||||
/**
|
||||
* Perform variable substitution and escaping on the given path.
|
||||
|
||||
@@ -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<VariableMap> 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<const Scene *>(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<Error> token_to_syntax_error(const Token &token)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
blender::Vector<Error> BKE_validate_template_syntax(blender::StringRef path)
|
||||
bool BKE_path_contains_template_syntax(blender::StringRef path)
|
||||
{
|
||||
const blender::Vector<Token> tokens = parse_template(path);
|
||||
|
||||
blender::Vector<Error> errors;
|
||||
for (const Token &token : tokens) {
|
||||
if (std::optional<Error> error = token_to_syntax_error(token)) {
|
||||
errors.append(*error);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
return path.find_first_of("{}") != std::string_view::npos;
|
||||
}
|
||||
|
||||
blender::Vector<Error> 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<Error> eval_template(char *out_path,
|
||||
const int out_path_max_length,
|
||||
blender::StringRef in_path,
|
||||
const VariableMap &template_variables)
|
||||
{
|
||||
const blender::Vector<Token> tokens = parse_template(path);
|
||||
if (out_path) {
|
||||
in_path.copy_utf8_truncated(out_path, out_path_max_length);
|
||||
}
|
||||
|
||||
const blender::Vector<Token> tokens = parse_template(in_path);
|
||||
|
||||
if (tokens.is_empty()) {
|
||||
/* No tokens found, so nothing to do. */
|
||||
@@ -648,15 +735,6 @@ blender::Vector<Error> BKE_path_apply_template(char *path,
|
||||
/* Accumulates errors as we process the tokens. */
|
||||
blender::Vector<Error> 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<char> 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<Error> 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<Error> 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<Error> 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<Error> BKE_path_apply_template(char *path,
|
||||
int path_max_length,
|
||||
const VariableMap &template_variables)
|
||||
{
|
||||
BLI_assert(path != nullptr);
|
||||
|
||||
blender::Vector<char> path_buffer(path_max_length);
|
||||
|
||||
const blender::Vector<Error> 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;
|
||||
}
|
||||
|
||||
@@ -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<Error> errors)
|
||||
static std::string errors_to_string(Span<Error> 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<Error> 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<Error> errors = BKE_path_apply_template(path, FILE_MAX, variables);
|
||||
const Vector<PathTemplateTestCase> 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<Error> 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<Error> 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<Error> 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<Error> 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<Error> 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<Error> 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<Error> errors = BKE_path_apply_template(path, FILE_MAX, variables);
|
||||
const Vector<Error> 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<Error> errors = BKE_path_apply_template(path, FILE_MAX, variables);
|
||||
const Vector<Error> 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<Error> errors = BKE_path_apply_template(path, FILE_MAX, variables);
|
||||
const Vector<Error> 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<Error> 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<Error> errors = BKE_path_apply_template(path, FILE_MAX, variables);
|
||||
const Vector<Error> 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<Error> errors = BKE_path_apply_template(path, FILE_MAX, variables);
|
||||
const Vector<Error> 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<Error> errors = BKE_path_apply_template(path, FILE_MAX, variables);
|
||||
const Vector<Error> 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<Error> errors = BKE_path_apply_template(path, FILE_MAX, variables);
|
||||
const Vector<Error> 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<Error> 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<Error> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<blender::bke::path_templates::VariableMap> variables =
|
||||
BKE_build_template_variables_for_prop(
|
||||
static_cast<const bContext *>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1071,16 +1071,22 @@ static std::unique_ptr<uiTooltipData> 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<blender::bke::path_templates::Error> errors =
|
||||
BKE_validate_template_syntax(path);
|
||||
if (BKE_path_contains_template_syntax(path)) {
|
||||
const std::optional<blender::bke::path_templates::VariableMap> 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<blender::bke::path_templates::Error> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<path_templates::Error> 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<path_templates::Error> 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<path_templates::Error> errors = BKE_image_path_from_imformat(
|
||||
filepath,
|
||||
scene->r.pic,
|
||||
|
||||
@@ -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<blender::bke::path_templates::VariableMap> variables =
|
||||
BKE_build_template_variables_for_prop(C, &ptr, prop);
|
||||
BLI_assert(variables.has_value());
|
||||
|
||||
const blender::Vector<blender::bke::path_templates::Error> 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;
|
||||
|
||||
@@ -1311,7 +1311,9 @@ static bool ffmpeg_filepath_get(MovieWriter *context,
|
||||
BLI_strncpy(filepath, rd->pic, FILE_MAX);
|
||||
|
||||
const blender::Vector<blender::bke::path_templates::Error> 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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<blender::bke::path_templates::Error> errors =
|
||||
BKE_image_path_from_imformat(filepath,
|
||||
|
||||
@@ -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<path_templates::Error> errors = BKE_image_path_from_imtype(
|
||||
r_image_path,
|
||||
base_path,
|
||||
|
||||
@@ -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<path_templates::Error> 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<path_templates::Error> 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<path_templates::Error> errors = BKE_image_path_from_imformat(
|
||||
filepath,
|
||||
rd.pic,
|
||||
|
||||
Reference in New Issue
Block a user