mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-21 06:21:16 +00:00
fix: error on calculations with post-processed placeholders (#16597)
Placeholders that GCodeProcessor fills in after the G-code is generated
(print_time_total_sec and the other print_time_* parts,
used_filament_length, and the print statistics such as total_weight)
hold a tag such as @TOTAL_WEIGHT@ while the template is evaluated.
Multiplying one already failed with a type error. Adding a number
joined the tag as text, so {total_weight + 5} printed 123.455 for a
123.45 g print, and a comparison compared the tag as text, so
{if total_weight > 100} was always true. Neither gave an error.
PlaceholderParser::set_deferred() now registers such a value. Printing
it and joining it to text work as before. A calculation, a comparison,
a regex or a condition on it fails with an error that says the value is
known only after the G-code is generated, also after it was copied to a
variable or through regex_replace(). Adding a number or another
deferred value to one is treated as a sum and fails too. Text that
already contains one joins a number as text, like any other string.
filament_change() inside an {if} branch that is not taken now does
nothing. It used to set the current extruder from an uninitialized
value.
This commit is contained in:
1 parent
3d0cb36544
commit
8a14a0383f
5 files changed
+165
-22
No files matched your search
+14
-14
@@ -3979,22 +3979,22 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
this->placeholder_parser().set("hold_chamber_temp_for_flat_print", new ConfigOptionBool(hold_chamber_temp_for_flat_print));
|
||||
}
|
||||
|
||||
this->placeholder_parser().set("print_time_total_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Total_Sec_Placeholder)));
|
||||
this->placeholder_parser().set("print_time_day", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Day_Placeholder)));
|
||||
this->placeholder_parser().set("print_time_hour", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Hour_Placeholder)));
|
||||
this->placeholder_parser().set("print_time_minute", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder)));
|
||||
this->placeholder_parser().set("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
|
||||
this->placeholder_parser().set("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
||||
this->placeholder_parser().set_deferred("print_time_total_sec", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Total_Sec_Placeholder));
|
||||
this->placeholder_parser().set_deferred("print_time_day", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Day_Placeholder));
|
||||
this->placeholder_parser().set_deferred("print_time_hour", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Hour_Placeholder));
|
||||
this->placeholder_parser().set_deferred("print_time_minute", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder));
|
||||
this->placeholder_parser().set_deferred("print_time_sec", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder));
|
||||
this->placeholder_parser().set_deferred("used_filament_length", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder));
|
||||
// The final print statistics are known only after the whole G-code is generated, so they are filled in during post-processing too.
|
||||
// The running extruded_* totals are left alone; profiles use them as numbers.
|
||||
this->placeholder_parser().set("used_filament", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
||||
this->placeholder_parser().set("print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder)));
|
||||
this->placeholder_parser().set("normal_print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder)));
|
||||
this->placeholder_parser().set("silent_print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Silent_Print_Time_Placeholder)));
|
||||
this->placeholder_parser().set("total_weight", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Weight_Placeholder)));
|
||||
this->placeholder_parser().set("total_cost", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Cost_Placeholder)));
|
||||
this->placeholder_parser().set("total_wipe_tower_cost", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Cost_Placeholder)));
|
||||
this->placeholder_parser().set("total_wipe_tower_filament", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Filament_Placeholder)));
|
||||
this->placeholder_parser().set_deferred("used_filament", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder));
|
||||
this->placeholder_parser().set_deferred("print_time", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder));
|
||||
this->placeholder_parser().set_deferred("normal_print_time", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder));
|
||||
this->placeholder_parser().set_deferred("silent_print_time", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Silent_Print_Time_Placeholder));
|
||||
this->placeholder_parser().set_deferred("total_weight", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Weight_Placeholder));
|
||||
this->placeholder_parser().set_deferred("total_cost", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Cost_Placeholder));
|
||||
this->placeholder_parser().set_deferred("total_wipe_tower_cost", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Cost_Placeholder));
|
||||
this->placeholder_parser().set_deferred("total_wipe_tower_filament", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Filament_Placeholder));
|
||||
|
||||
// Sync variant-mapped params into placeholder_parser before processing start gcode
|
||||
update_placeholder_parser_with_variant_params();
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace client
|
||||
struct expr
|
||||
{
|
||||
expr() {}
|
||||
expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range)
|
||||
expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range), m_deferred(rhs.m_deferred)
|
||||
{ if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); }
|
||||
expr(expr &&rhs) : expr(std::move(rhs), rhs.it_range.begin(), rhs.it_range.end()) {}
|
||||
|
||||
@@ -258,13 +258,16 @@ namespace client
|
||||
explicit expr(std::string &&s) : m_type(TYPE_STRING) { m_data.s = new std::string(std::move(s)); }
|
||||
explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) :
|
||||
m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); }
|
||||
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end }
|
||||
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end }, m_deferred(rhs.m_deferred)
|
||||
{
|
||||
m_data.set(rhs.m_data);
|
||||
rhs.m_type = TYPE_EMPTY;
|
||||
rhs.m_type = TYPE_EMPTY;
|
||||
rhs.m_deferred = Deferred::None;
|
||||
}
|
||||
expr &operator=(const expr &rhs)
|
||||
{
|
||||
// set_s() clears m_deferred, which is also rhs.m_deferred on a self-assignment.
|
||||
const Deferred deferred = rhs.m_deferred;
|
||||
if (rhs.type() == TYPE_STRING) {
|
||||
this->set_s(rhs.s());
|
||||
} else {
|
||||
@@ -272,6 +275,7 @@ namespace client
|
||||
m_data.set(rhs.m_data);
|
||||
}
|
||||
this->it_range = rhs.it_range;
|
||||
m_deferred = deferred;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -281,8 +285,10 @@ namespace client
|
||||
this->reset();
|
||||
m_type = rhs.type();
|
||||
this->it_range = rhs.it_range;
|
||||
m_deferred = rhs.m_deferred;
|
||||
m_data.set(rhs.m_data);
|
||||
rhs.m_type = TYPE_EMPTY;
|
||||
rhs.m_deferred = Deferred::None;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -291,7 +297,8 @@ namespace client
|
||||
{
|
||||
if (this->type() == TYPE_STRING)
|
||||
delete m_data.s;
|
||||
m_type = TYPE_EMPTY;
|
||||
m_type = TYPE_EMPTY;
|
||||
m_deferred = Deferred::None;
|
||||
}
|
||||
~expr() { reset(); }
|
||||
|
||||
@@ -304,6 +311,10 @@ namespace client
|
||||
};
|
||||
Type type() const { return m_type; }
|
||||
bool numeric_type() const { return m_type == TYPE_INT || m_type == TYPE_DOUBLE; }
|
||||
// A deferred string is either one deferred value or text that contains at least one.
|
||||
enum class Deferred : unsigned char { None, Value, Text };
|
||||
bool deferred() const { return m_deferred != Deferred::None; }
|
||||
void set_deferred(Deferred deferred) { m_deferred = deferred; }
|
||||
|
||||
bool& b() { return m_data.b; }
|
||||
bool b() const { return m_data.b; }
|
||||
@@ -323,18 +334,21 @@ namespace client
|
||||
std::string& s() { return *m_data.s; }
|
||||
const std::string& s() const { return *m_data.s; }
|
||||
void set_s(const std::string &s) {
|
||||
m_deferred = Deferred::None;
|
||||
if (this->type() == TYPE_STRING)
|
||||
*m_data.s = s;
|
||||
else
|
||||
this->set_s_take_ownership(new std::string(s));
|
||||
}
|
||||
void set_s(std::string &&s) {
|
||||
m_deferred = Deferred::None;
|
||||
if (this->type() == TYPE_STRING)
|
||||
*m_data.s = std::move(s);
|
||||
else
|
||||
this->set_s_take_ownership(new std::string(std::move(s)));
|
||||
}
|
||||
void set_s(const char *s) {
|
||||
m_deferred = Deferred::None;
|
||||
if (this->type() == TYPE_STRING)
|
||||
*m_data.s = s;
|
||||
else
|
||||
@@ -478,12 +492,21 @@ namespace client
|
||||
{
|
||||
if (this->type() == TYPE_EMPTY) {
|
||||
// Inside an if / else block to be skipped.
|
||||
} else if (m_deferred == Deferred::Value && (rhs.type() != TYPE_STRING || rhs.m_deferred == Deferred::Value)) {
|
||||
// A deferred value added to a number or to another deferred value is meant as a sum.
|
||||
this->throw_if_deferred();
|
||||
} else if (rhs.m_deferred == Deferred::Value && this->type() != TYPE_STRING) {
|
||||
rhs.throw_if_deferred();
|
||||
} else if (this->type() == TYPE_STRING) {
|
||||
// Convert the right hand side to string and append.
|
||||
*m_data.s += rhs.to_string();
|
||||
if (this->deferred() || rhs.deferred())
|
||||
m_deferred = Deferred::Text;
|
||||
} else if (rhs.type() == TYPE_STRING) {
|
||||
// Conver the left hand side to string, append rhs.
|
||||
this->set_s(this->to_string() + rhs.s());
|
||||
if (rhs.deferred())
|
||||
m_deferred = Deferred::Text;
|
||||
} else {
|
||||
const char *err_msg = L("Cannot add non-numeric types.");
|
||||
this->throw_if_not_numeric(err_msg);
|
||||
@@ -602,6 +625,8 @@ namespace client
|
||||
if (lhs.type() == TYPE_EMPTY)
|
||||
// Inside an if / else block to be skipped
|
||||
return;
|
||||
lhs.throw_if_deferred();
|
||||
rhs.throw_if_deferred();
|
||||
bool value = false;
|
||||
if (lhs.numeric_type() && rhs.numeric_type()) {
|
||||
// Both types are numeric.
|
||||
@@ -731,6 +756,7 @@ namespace client
|
||||
if (lhs.type() == TYPE_EMPTY)
|
||||
// Inside an if / else block to be skipped
|
||||
return;
|
||||
lhs.throw_if_deferred();
|
||||
const std::string *subject = nullptr;
|
||||
if (lhs.type() == TYPE_STRING) {
|
||||
// One type is string, the other could be converted to string.
|
||||
@@ -761,6 +787,7 @@ namespace client
|
||||
if (subject.type() == TYPE_EMPTY)
|
||||
// Inside an if / else block to be skipped
|
||||
return;
|
||||
subject.throw_if_deferred();
|
||||
if (subject.type() != TYPE_STRING)
|
||||
subject.throw_exception(L("regex_replace() first parameter must be a string."));
|
||||
try {
|
||||
@@ -785,6 +812,8 @@ namespace client
|
||||
return;
|
||||
}
|
||||
if (! out.b()) {
|
||||
match.throw_if_deferred();
|
||||
pattern.throw_if_deferred();
|
||||
if (match.type() != TYPE_STRING)
|
||||
match.throw_exception(L("one_of(): First parameter (the string to match against) has to be a string value"));
|
||||
if (pattern.type() != TYPE_STRING)
|
||||
@@ -818,6 +847,8 @@ namespace client
|
||||
if (lhs.type() == TYPE_EMPTY)
|
||||
// Inside an if / else block to be skipped
|
||||
return;
|
||||
lhs.throw_if_deferred();
|
||||
rhs.throw_if_deferred();
|
||||
bool value = false;
|
||||
if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) {
|
||||
value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b());
|
||||
@@ -830,8 +861,16 @@ namespace client
|
||||
static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); }
|
||||
static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); }
|
||||
|
||||
static const char* deferred_message()
|
||||
{
|
||||
return L("This value is known only after the G-code is generated. It can be printed, but not used in a calculation or a comparison.");
|
||||
}
|
||||
|
||||
// Reports the deferred message for a type error on a deferred value, since it holds a tag in place of the value.
|
||||
void throw_exception(const char *message) const
|
||||
{
|
||||
if (this->deferred())
|
||||
message = deferred_message();
|
||||
boost::throw_exception(qi::expectation_failure<Iterator>(
|
||||
this->it_range.begin(), this->it_range.end(), spirit::info(std::string("*") + message)));
|
||||
}
|
||||
@@ -842,11 +881,18 @@ namespace client
|
||||
this->throw_exception(message);
|
||||
}
|
||||
|
||||
void throw_if_deferred() const
|
||||
{
|
||||
if (this->deferred())
|
||||
this->throw_exception(deferred_message());
|
||||
}
|
||||
|
||||
private:
|
||||
// This object will take ownership of the parameter string object "s".
|
||||
void set_s_take_ownership(std::string* s) { assert(this->type() != TYPE_STRING); Data tmp; tmp.s = s; m_data.set(tmp); m_type = TYPE_STRING; }
|
||||
|
||||
Type m_type = TYPE_EMPTY;
|
||||
Deferred m_deferred = Deferred::None;
|
||||
|
||||
union Data {
|
||||
bool b;
|
||||
@@ -895,6 +941,8 @@ namespace client
|
||||
std::string error_message;
|
||||
// Local variables declared in {if} branches that were not taken, see PlaceholderParser::check_inactive_branches.
|
||||
mutable std::set<std::string> inactive_local_variables;
|
||||
// Tags of PlaceholderParser::set_deferred(), matched by value so that a copy in a variable is deferred too.
|
||||
const std::set<std::string> *deferred_tags = nullptr;
|
||||
|
||||
// Table to translate symbol tag to a human readable error message.
|
||||
static std::map<std::string, std::string> tag_to_error_message;
|
||||
@@ -970,6 +1018,18 @@ namespace client
|
||||
} else
|
||||
this->config_local.set_key_value(opt_key, opt.release());
|
||||
}
|
||||
void mark_deferred(expr &output) const {
|
||||
if (output.type() != expr::TYPE_STRING)
|
||||
return;
|
||||
expr::Deferred deferred = expr::Deferred::None;
|
||||
if (deferred_tags != nullptr && output.s().find('@') != std::string::npos)
|
||||
for (const std::string &tag : *deferred_tags)
|
||||
if (output.s().find(tag) != std::string::npos) {
|
||||
deferred = output.s() == tag ? expr::Deferred::Value : expr::Deferred::Text;
|
||||
break;
|
||||
}
|
||||
output.set_deferred(deferred);
|
||||
}
|
||||
|
||||
static void legacy_variable_expansion(const MyContext *ctx, IteratorRange &opt_key, std::string &output)
|
||||
{
|
||||
@@ -1430,6 +1490,7 @@ namespace client
|
||||
vector_element_to_expr(ctx, opt, output);
|
||||
else
|
||||
scalar_variable_to_expr(ctx, opt, output);
|
||||
ctx->mark_deferred(output);
|
||||
}
|
||||
output.it_range = opt.it_range;
|
||||
}
|
||||
@@ -1516,7 +1577,7 @@ namespace client
|
||||
if (ctx->skipping()) {
|
||||
} else if (lhs.opt) {
|
||||
if (lhs.opt->is_vector())
|
||||
rhs.throw_exception(L("Cannot assign a scalar value to a vector variable."));
|
||||
ctx->throw_exception(L("Cannot assign a scalar value to a vector variable."), rhs.it_range);
|
||||
OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true };
|
||||
scalar_variable_assign_scalar(ctx, lhs_opt, rhs);
|
||||
} else {
|
||||
@@ -1542,7 +1603,7 @@ namespace client
|
||||
if (ctx->skipping()) {
|
||||
} else if (lhs.opt) {
|
||||
if (lhs.opt->is_scalar())
|
||||
rhs_value.throw_exception(L("Cannot assign a vector value to a scalar variable."));
|
||||
ctx->throw_exception(L("Cannot assign a vector value to a scalar variable."), rhs_value.it_range);
|
||||
OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true };
|
||||
vector_variable_assign_expr_with_count(ctx, lhs_opt, rhs_count, rhs_value);
|
||||
} else {
|
||||
@@ -1568,7 +1629,7 @@ namespace client
|
||||
if (! ctx->skipping()) {
|
||||
check_writable(ctx, lhs);
|
||||
if (lhs.opt->is_scalar())
|
||||
rhs_value.throw_exception(L("Cannot assign a vector value to a scalar variable."));
|
||||
ctx->throw_exception(L("Cannot assign a vector value to a scalar variable."), rhs_value.it_range);
|
||||
vector_variable_assign_expr_with_count(ctx, lhs, rhs_count, rhs_value);
|
||||
}
|
||||
}
|
||||
@@ -1803,10 +1864,21 @@ namespace client
|
||||
|
||||
static void filament_change(const MyContext* ctx, expr& param)
|
||||
{
|
||||
if (param.type() == expr::TYPE_EMPTY)
|
||||
// Inside an if / else block to be skipped
|
||||
return;
|
||||
MyContext *context = const_cast<MyContext *>(ctx);
|
||||
param.throw_if_deferred();
|
||||
context->current_extruder_id = param.as_i();
|
||||
}
|
||||
|
||||
// The replacement may put a deferred tag into the result.
|
||||
static void regex_replace(const MyContext *ctx, expr &subject, IteratorRange &pattern, expr &replacement)
|
||||
{
|
||||
expr::regex_replace(subject, pattern, replacement);
|
||||
ctx->mark_deferred(subject);
|
||||
}
|
||||
|
||||
static void throw_exception(const std::string &msg, const IteratorRange &it_range)
|
||||
{
|
||||
// An asterix is added to the start of the string to differentiate the boost::spirit::info::tag content
|
||||
@@ -2409,7 +2481,7 @@ namespace client
|
||||
| (kw["zdigits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1))
|
||||
[ px::bind(&expr::digits<true>, _val, _2, _3) ]
|
||||
| (kw["regex_replace"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > regular_expression > ',' > conditional_expression(_r1) > ')')
|
||||
[ px::bind(&expr::regex_replace, _val, _2, _3) ]
|
||||
[ px::bind(&MyContext::regex_replace, _r1, _val, _2, _3) ]
|
||||
| (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ]
|
||||
| (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ]
|
||||
| (kw["ceil"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::ceil, _1, _val) ]
|
||||
@@ -2614,6 +2686,7 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu
|
||||
context.config = &this->config();
|
||||
context.config_override = config_override;
|
||||
context.config_outputs = config_outputs;
|
||||
context.deferred_tags = &m_deferred_tags;
|
||||
context.current_extruder_id = current_extruder_id;
|
||||
context.context_data = context_data;
|
||||
return process_macro(templ, context);
|
||||
|
||||
@@ -60,6 +60,9 @@ public:
|
||||
void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); }
|
||||
void set(const std::string &key, const std::vector<std::string> &values) { this->set(key, new ConfigOptionStrings(values)); }
|
||||
void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); }
|
||||
// Orca: sets a value known only after the G-code is generated, as a tag that post-processing replaces.
|
||||
// A template may print it or join it to text, and a calculation or a comparison on it throws.
|
||||
void set_deferred(const std::string &key, const std::string &tag) { this->set(key, tag); m_deferred_tags.insert(tag); }
|
||||
DynamicConfig& config_writable() { return m_config; }
|
||||
const DynamicConfig& config() const { return m_config; }
|
||||
const ConfigOption* option(const std::string &key) const { return m_config.option(key); }
|
||||
@@ -88,6 +91,7 @@ private:
|
||||
// config has a higher priority than external_config when looking up a symbol.
|
||||
DynamicConfig m_config;
|
||||
const DynamicConfig *m_external_config;
|
||||
std::set<std::string> m_deferred_tags;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include <catch2/matchers/catch_matchers.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
@@ -794,6 +795,20 @@ TEST_CASE("Print statistics in custom G-code match the final print statistics",
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Print statistics in custom G-code cannot be used in a calculation", "[Print]")
|
||||
{
|
||||
const std::string key = GENERATE(as<std::string>{}, "total_weight", "used_filament", "total_cost", "print_time", "normal_print_time",
|
||||
"silent_print_time", "total_wipe_tower_cost", "total_wipe_tower_filament", "print_time_total_sec",
|
||||
"print_time_day", "print_time_hour", "print_time_minute", "print_time_sec", "used_filament_length");
|
||||
CAPTURE(key);
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({ { "file_start_gcode", "; {" + key + " + 1}\n" } });
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({ cube(20) }, print, model, config);
|
||||
CHECK_THROWS_WITH(Slic3r::Test::gcode(print), Catch::Matchers::ContainsSubstring("known only after the G-code is generated"));
|
||||
}
|
||||
|
||||
TEST_CASE("export_gcode writes G-code without a result pointer", "[Print][export_gcode]")
|
||||
{
|
||||
Print print;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_message.hpp>
|
||||
#include <catch2/matchers/catch_matchers.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
#include "libslic3r/PlaceholderParser.hpp"
|
||||
@@ -398,3 +399,53 @@ SCENARIO("Placeholder parser names in branches that are not taken", "[Placeholde
|
||||
CHECK(PlaceholderParser::evaluate_boolean_expression("false ? no_such_var == 1 : true", config));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Placeholder parser deferred values can be printed but not computed with", "[PlaceholderParser]") {
|
||||
PlaceholderParser parser;
|
||||
parser.set_deferred("total_weight", "@TOTAL_WEIGHT@");
|
||||
parser.set_deferred("used_filament", "@USED_FILAMENT_LENGTH@");
|
||||
parser.set("label", "W");
|
||||
PlaceholderParser::ContextData context;
|
||||
context.global_config = std::make_unique<DynamicConfig>();
|
||||
auto process = [&parser, &context](const std::string &templ) { return parser.process(templ, 0, nullptr, nullptr, &context); };
|
||||
|
||||
SECTION("printed") {
|
||||
CHECK(process("{total_weight}") == "@TOTAL_WEIGHT@");
|
||||
CHECK(process("[total_weight]") == "@TOTAL_WEIGHT@");
|
||||
CHECK(process("{label + \": \" + total_weight + \"g\"}") == "W: @TOTAL_WEIGHT@g");
|
||||
CHECK(process("{total_weight + \"g, \" + used_filament + \"m\"}") == "@TOTAL_WEIGHT@g, @USED_FILAMENT_LENGTH@m");
|
||||
CHECK(process("{\"W \" + total_weight + \"g for \" + 3 + \" parts\"}") == "W @TOTAL_WEIGHT@g for 3 parts");
|
||||
CHECK(process("{true ? total_weight : 0}") == "@TOTAL_WEIGHT@");
|
||||
CHECK(process("{regex_replace(\"x\", /x/, total_weight)}") == "@TOTAL_WEIGHT@");
|
||||
CHECK(process("{local w = total_weight}{w}") == "@TOTAL_WEIGHT@");
|
||||
}
|
||||
SECTION("used in a calculation or a comparison") {
|
||||
const auto deferred_error = Catch::Matchers::ContainsSubstring("known only after the G-code is generated");
|
||||
for (const std::string templ : {
|
||||
"{total_weight + 1}", "{1 + total_weight}", "{total_weight * 2}", "{-total_weight}", "{int(total_weight)}",
|
||||
"{min(total_weight, 5)}", "{digits(total_weight, 5)}", "{total_weight + used_filament}",
|
||||
"{if total_weight > 100}heavy{endif}", "{if total_weight == \"1\"}one{endif}", "{total_weight and true}",
|
||||
"{total_weight ? 1 : 0}", "{if total_weight =~ /1.*/}one{endif}", "{one_of(total_weight, \"1\")}",
|
||||
"{regex_replace(total_weight, /1/, \"2\")}", "{(\"W\" + total_weight) == \"W1\"}", "{filament_change(total_weight)}",
|
||||
"{regex_replace(\"x\", /x/, total_weight) * 2}", "{if regex_replace(\"x\", /x/, \"W\" + total_weight) > \"W1\"}heavy{endif}",
|
||||
// A deferred value copied to a variable is still deferred.
|
||||
"{local w = total_weight}{w > 1}", "{global g = \"W\" + total_weight}{if g == \"W1\"}one{endif}" }) {
|
||||
CAPTURE(templ);
|
||||
CHECK_THROWS_WITH(process(templ), deferred_error);
|
||||
}
|
||||
// A mistake unrelated to the value being deferred keeps its own message.
|
||||
CHECK_THROWS_WITH(process("{global v = (1, 2)}{global v = \"W\" + total_weight}"),
|
||||
Catch::Matchers::ContainsSubstring("Cannot assign a scalar value to a vector variable"));
|
||||
}
|
||||
SECTION("ignored in a branch that is not taken") {
|
||||
CHECK(process("{if false}{total_weight * 2}{endif}ok") == "ok");
|
||||
struct Restore { ~Restore() { PlaceholderParser::check_inactive_branches = false; } } restore;
|
||||
PlaceholderParser::check_inactive_branches = true;
|
||||
CHECK(process("{if false}{total_weight * 2}{endif}ok") == "ok");
|
||||
}
|
||||
SECTION("other strings keep their behavior") {
|
||||
CHECK(process("{label + 1}") == "W1");
|
||||
CHECK(process("{if label > \"A\"}yes{endif}") == "yes");
|
||||
CHECK(process("{if regex_replace(label, /X/, total_weight) == \"W\"}yes{endif}") == "yes");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user