Merge branch 'master' into fs_undoredo

This commit is contained in:
Filip Sykala - NTB T15p 2023-03-23 16:29:33 +01:00
commit 369a881872
16 changed files with 676 additions and 531 deletions

View file

@ -4,12 +4,17 @@
#include <iostream>
#include <vector>
#include <numeric>
#include <memory>
#include <sstream>
#include <iomanip>
#include "ClipperUtils.hpp"
#include "GCodeProcessor.hpp"
#include "BoundingBox.hpp"
#include "LocalesUtils.hpp"
#include "Geometry.hpp"
#include "Surface.hpp"
#include "Fill/FillRectilinear.hpp"
#include <boost/algorithm/string/predicate.hpp>
@ -512,6 +517,8 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector<std::vector<fl
m_wipe_tower_width(float(config.wipe_tower_width)),
m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)),
m_wipe_tower_brim_width(float(config.wipe_tower_brim_width)),
m_wipe_tower_cone_angle(float(config.wipe_tower_cone_angle)),
m_extra_spacing(float(config.wipe_tower_extra_spacing/100.)),
m_y_shift(0.f),
m_z_pos(0.f),
m_bridging(float(config.wipe_tower_bridging)),
@ -1169,45 +1176,171 @@ WipeTower::ToolChangeResult WipeTower::finish_layer()
";------------------\n\n\n\n\n\n\n");
}
// outer perimeter (always):
writer.rectangle(wt_box, feedrate);
const float spacing = m_perimeter_width - m_layer_height*float(1.-M_PI_4);
// This block creates the stabilization cone.
// First define a lambda to draw the rectangle with stabilization.
auto supported_rectangle = [this, &writer, spacing](const box_coordinates& wt_box, double feedrate, bool infill_cone) -> Polygon {
const auto [R, support_scale] = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth, m_wipe_tower_cone_angle);
double z = m_no_sparse_layers ? (m_current_height + m_layer_info->height) : m_layer_info->z; // the former should actually work in both cases, but let's stay on the safe side (the 2.6.0 is close)
double r = std::tan(Geometry::deg2rad(m_wipe_tower_cone_angle/2.f)) * (m_wipe_tower_height - z);
Vec2f center = (wt_box.lu + wt_box.rd) / 2.;
double w = wt_box.lu.y() - wt_box.ld.y();
enum Type {
Arc,
Corner,
ArcStart,
ArcEnd
};
// First generate vector of annotated point which form the boundary.
std::vector<std::pair<Vec2f, Type>> pts = {{wt_box.ru, Corner}};
if (double alpha_start = std::asin((0.5*w)/r); ! std::isnan(alpha_start) && r > 0.5*w+0.01) {
for (double alpha = alpha_start; alpha < M_PI-alpha_start+0.001; alpha+=(M_PI-2*alpha_start) / 20.)
pts.emplace_back(Vec2f(center.x() + r*std::cos(alpha)/support_scale, center.y() + r*std::sin(alpha)), alpha == alpha_start ? ArcStart : Arc);
pts.back().second = ArcEnd;
}
pts.emplace_back(wt_box.lu, Corner);
pts.emplace_back(wt_box.ld, Corner);
for (int i=int(pts.size())-3; i>0; --i)
pts.emplace_back(Vec2f(pts[i].first.x(), 2*center.y()-pts[i].first.y()), i == int(pts.size())-3 ? ArcStart : i == 1 ? ArcEnd : Arc);
pts.emplace_back(wt_box.rd, Corner);
// Create a Polygon from the points.
Polygon poly;
for (const auto& [pt, tag] : pts)
poly.points.push_back(Point::new_scale(pt));
// Prepare polygons to be filled by infill.
Polylines polylines;
if (infill_cone && m_wipe_tower_width > 2*spacing && m_wipe_tower_depth > 2*spacing) {
ExPolygons infill_areas;
ExPolygon wt_contour(poly);
Polygon wt_rectangle(Points{Point::new_scale(wt_box.ld), Point::new_scale(wt_box.rd), Point::new_scale(wt_box.ru), Point::new_scale(wt_box.lu)});
wt_rectangle = offset(wt_rectangle, scale_(-spacing/2.)).front();
wt_contour = offset_ex(wt_contour, scale_(-spacing/2.)).front();
infill_areas = diff_ex(wt_contour, wt_rectangle);
if (infill_areas.size() == 2) {
ExPolygon& bottom_expoly = infill_areas.front().contour.points.front().y() < infill_areas.back().contour.points.front().y() ? infill_areas[0] : infill_areas[1];
std::unique_ptr<Fill> filler(Fill::new_from_type(ipMonotonicLines));
filler->angle = Geometry::deg2rad(45.f);
filler->spacing = spacing;
FillParams params;
params.density = 1.f;
Surface surface(stBottom, bottom_expoly);
filler->bounding_box = get_extents(bottom_expoly);
polylines = filler->fill_surface(&surface, params);
if (! polylines.empty()) {
if (polylines.front().points.front().x() > polylines.back().points.back().x()) {
std::reverse(polylines.begin(), polylines.end());
for (Polyline& p : polylines)
p.reverse();
}
}
}
}
// Find the closest corner and travel to it.
int start_i = 0;
double min_dist = std::numeric_limits<double>::max();
for (int i=0; i<int(pts.size()); ++i) {
if (pts[i].second == Corner) {
double dist = (pts[i].first - Vec2f(writer.x(), writer.y())).squaredNorm();
if (dist < min_dist) {
min_dist = dist;
start_i = i;
}
}
}
writer.travel(pts[start_i].first);
// Now actually extrude the boundary (and possibly infill):
int i = start_i+1 == int(pts.size()) ? 0 : start_i + 1;
while (i != start_i) {
writer.extrude(pts[i].first, feedrate);
if (pts[i].second == ArcEnd) {
// Extrude the infill.
if (! polylines.empty()) {
// Extrude the infill and travel back to where we were.
bool mirror = ((pts[i].first.y() - center.y()) * (unscale(polylines.front().points.front()).y() - center.y())) < 0.;
for (const Polyline& line : polylines) {
writer.travel(center - (mirror ? 1.f : -1.f) * (unscale(line.points.front()).cast<float>() - center));
for (size_t i=0; i<line.points.size(); ++i)
writer.extrude(center - (mirror ? 1.f : -1.f) * (unscale(line.points[i]).cast<float>() - center));
}
writer.travel(pts[i].first);
}
}
if (++i == int(pts.size()))
i = 0;
}
writer.extrude(pts[start_i].first, feedrate);
return poly;
};
// outer contour (always)
bool infill_cone = first_layer && m_wipe_tower_width > 2*spacing && m_wipe_tower_depth > 2*spacing;
Polygon poly = supported_rectangle(wt_box, feedrate, infill_cone);
// brim (first layer only)
if (first_layer) {
box_coordinates box = wt_box;
float spacing = m_perimeter_width - m_layer_height*float(1.-M_PI_4);
// How many perimeters shall the brim have?
size_t loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing;
for (size_t i = 0; i < loops_num; ++ i) {
box.expand(spacing);
writer.rectangle(box);
poly = offset(poly, scale_(spacing)).front();
int cp = poly.closest_point_index(Point::new_scale(writer.x(), writer.y()));
writer.travel(unscale(poly.points[cp]).cast<float>());
for (int i=cp+1; true; ++i ) {
if (i==int(poly.points.size()))
i = 0;
writer.extrude(unscale(poly.points[i]).cast<float>());
if (i == cp)
break;
}
}
// Save actual brim width to be later passed to the Print object, which will use it
// for skirt calculation and pass it to GLCanvas for precise preview box
m_wipe_tower_brim_width_real = wt_box.ld.x() - box.ld.x() + spacing/2.f;
wt_box = box;
m_wipe_tower_brim_width_real = loops_num * spacing;
}
// Now prepare future wipe. box contains rectangle that was extruded last (ccw).
Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd :
(writer.pos() == wt_box.rd ? wt_box.ru :
(writer.pos() == wt_box.ru ? wt_box.lu :
wt_box.ld)));
writer.add_wipe_point(writer.pos())
.add_wipe_point(target);
// Now prepare future wipe.
int i = poly.closest_point_index(Point::new_scale(writer.x(), writer.y()));
writer.add_wipe_point(writer.pos());
writer.add_wipe_point(unscale(poly.points[i==0 ? int(poly.points.size())-1 : i-1]).cast<float>());
// Ask our writer about how much material was consumed.
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
if (! m_no_sparse_layers || toolchanges_on_layer || first_layer)
if (! m_no_sparse_layers || toolchanges_on_layer || first_layer) {
if (m_current_tool < m_used_filament_length.size())
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
m_current_height += m_layer_info->height;
}
return construct_tcr(writer, false, old_tool);
}
// Static method to get the radius and x-scaling of the stabilizing cone base.
std::pair<double, double> WipeTower::get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg)
{
double R = std::tan(Geometry::deg2rad(angle_deg/2.)) * height;
double fake_width = 0.66 * width;
double diag = std::hypot(fake_width / 2., depth / 2.);
double support_scale = 1.;
if (R > diag) {
double w = fake_width;
double sin = 0.5 * depth / diag;
double tan = depth / w;
double t = (R - diag) * sin;
support_scale = (w / 2. + t / tan + t * tan) / (w / 2.);
}
return std::make_pair(R, support_scale);
}
// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box
void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool,
unsigned int new_tool, float wipe_volume)
@ -1250,6 +1383,8 @@ void WipeTower::plan_tower()
m_wipe_tower_depth = 0.f;
for (auto& layer : m_plan)
layer.depth = 0.f;
m_wipe_tower_height = m_plan.empty() ? 0.f : m_plan.back().z;
m_current_height = 0.f;
for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index)
{
@ -1334,8 +1469,6 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
if (m_plan.empty())
return;
m_extra_spacing = 1.f;
plan_tower();
for (int i=0;i<5;++i) {
save_on_last_wipe();
@ -1343,6 +1476,7 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
}
m_layer_info = m_plan.begin();
m_current_height = 0.f;
// we don't know which extruder to start with - we'll set it according to the first toolchange
for (const auto& layer : m_plan) {
@ -1358,7 +1492,7 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
m_old_temperature = -1; // reset last temperature written in the gcode
std::vector<WipeTower::ToolChangeResult> layer_result;
for (auto layer : m_plan)
for (const WipeTower::WipeTowerInfo& layer : m_plan)
{
set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z);
m_internal_rotation += 180.f;

View file

@ -23,6 +23,8 @@ class WipeTower
public:
static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; }
static std::pair<double, double> get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg);
struct Extrusion
{
Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {}
@ -142,6 +144,7 @@ public:
float get_depth() const { return m_wipe_tower_depth; }
float get_brim_width() const { return m_wipe_tower_brim_width_real; }
float get_wipe_tower_height() const { return m_wipe_tower_height; }
@ -253,6 +256,8 @@ private:
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
float m_wipe_tower_width; // Width of the wipe tower.
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
float m_wipe_tower_height = 0.f;
float m_wipe_tower_cone_angle = 0.f;
float m_wipe_tower_brim_width = 0.f; // Width of brim (mm) from config
float m_wipe_tower_brim_width_real = 0.f; // Width of brim (mm) after generation
float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
@ -359,6 +364,10 @@ private:
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end();
// This sums height of all extruded layers, not counting the layers which
// will be later removed when the "no_sparse_layers" is used.
float m_current_height = 0.f;
// Stores information about used filament length per extruder:
std::vector<float> m_used_filament_length;

View file

@ -170,7 +170,7 @@ namespace client
template<typename Iterator>
struct OptWithPos {
OptWithPos() {}
OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range<Iterator> it_range) : opt(opt), it_range(it_range) {}
OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range<Iterator> it_range, bool writable = false) : opt(opt), it_range(it_range), writable(writable) {}
ConfigOptionConstPtr opt { nullptr };
bool writable { false };
// -1 means it is a scalar variable, or it is a vector variable and index was not assigned yet or the whole vector is considered.
@ -720,9 +720,14 @@ namespace client
}
struct MyContext : public ConfigOptionResolver {
// Config provided as a parameter to PlaceholderParser invocation, overriding PlaceholderParser stored config.
const DynamicConfig *external_config = nullptr;
// Config stored inside PlaceholderParser.
const DynamicConfig *config = nullptr;
// Config provided as a parameter to PlaceholderParser invocation, evaluated after the two configs above.
const DynamicConfig *config_override = nullptr;
// Config provided as a parameter to PlaceholderParser invocation, containing variables that will be read out
// and processed by the PlaceholderParser callee.
mutable DynamicConfig *config_outputs = nullptr;
// Local variables, read / write
mutable DynamicConfig config_local;
@ -737,7 +742,8 @@ namespace client
// Table to translate symbol tag to a human readable error message.
static std::map<std::string, std::string> tag_to_error_message;
static void evaluate_full_macro(const MyContext *ctx, bool &result) { result = ! ctx->just_boolean_expression; }
// Should the parser consider the parsed string to be a macro or a boolean expression?
static bool evaluate_full_macro(const MyContext *ctx) { return ! ctx->just_boolean_expression; }
const ConfigOption* optptr(const t_config_option_key &opt_key) const override
{
@ -762,13 +768,13 @@ namespace client
out = this->config_local.optptr(opt_key);
return out;
}
void store_new_variable(const std::string &opt_key, ConfigOption *opt, bool global_variable) {
assert(opt != nullptr);
void store_new_variable(const std::string &opt_key, std::unique_ptr<ConfigOption> &&opt, bool global_variable) {
assert(opt);
if (global_variable) {
assert(this->context_data != nullptr && this->context_data->global_config);
this->context_data->global_config->set_key_value(opt_key, opt);
this->context_data->global_config->set_key_value(opt_key, opt.release());
} else
this->config_local.set_key_value(opt_key ,opt);
this->config_local.set_key_value(opt_key, opt.release());
}
template <typename Iterator>
@ -844,9 +850,10 @@ namespace client
boost::iterator_range<Iterator> &opt_key,
OptWithPos<Iterator> &output)
{
const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end()));
const std::string key{ opt_key.begin(), opt_key.end() };
const ConfigOption *opt = ctx->resolve_symbol(key);
if (opt == nullptr) {
opt = ctx->resolve_output_symbol(std::string(opt_key.begin(), opt_key.end()));
opt = ctx->resolve_output_symbol(key);
if (opt == nullptr)
ctx->throw_exception("Not a variable name", opt_key);
output.writable = true;
@ -872,32 +879,16 @@ namespace client
output.it_range.end() = it_end;
}
// Evaluating a scalar variable into expr,
// all possible ConfigOption types are supported.
template <typename Iterator>
static void variable_value(
static void scalar_variable_to_expr(
const MyContext *ctx,
OptWithPos<Iterator> &opt,
expr<Iterator> &output)
{
if (opt.opt->is_vector()) {
if (! opt.has_index())
ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range);
const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt.opt);
if (vec->empty())
ctx->throw_exception("Indexing an empty vector variable", opt.it_range);
size_t idx = (opt.index < 0) ? 0 : (opt.index >= int(vec->size())) ? 0 : size_t(opt.index);
switch (opt.opt->type()) {
case coFloats: output.set_d(static_cast<const ConfigOptionFloats *>(opt.opt)->values[idx]); break;
case coInts: output.set_i(static_cast<const ConfigOptionInts *>(opt.opt)->values[idx]); break;
case coStrings: output.set_s(static_cast<const ConfigOptionStrings *>(opt.opt)->values[idx]); break;
case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break;
case coPoints: output.set_s(to_string(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx])); break;
case coBools: output.set_b(static_cast<const ConfigOptionBools *>(opt.opt)->values[idx] != 0); break;
//case coEnums: output.set_s(opt.opt->vserialize()[idx]); break;
default:
ctx->throw_exception("Unknown vector variable type", opt.it_range);
}
} else {
assert(opt.opt->is_scalar());
switch (opt.opt->type()) {
case coFloat: output.set_d(opt.opt->getFloat()); break;
case coInt: output.set_i(opt.opt->getInt()); break;
@ -945,10 +936,176 @@ namespace client
break;
}
default:
ctx->throw_exception("Unknown scalar variable type", opt.it_range);
ctx->throw_exception("Unsupported scalar variable type", opt.it_range);
}
}
// Evaluating one element of a vector variable.
// all possible ConfigOption types are supported.
template <typename Iterator>
static void vector_element_to_expr(
const MyContext *ctx,
OptWithPos<Iterator> &opt,
expr<Iterator> &output)
{
assert(opt.opt->is_vector());
if (! opt.has_index())
ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range);
const ConfigOptionVectorBase* vec = static_cast<const ConfigOptionVectorBase*>(opt.opt);
if (vec->empty())
ctx->throw_exception("Indexing an empty vector variable", opt.it_range);
size_t idx = (opt.index < 0) ? 0 : (opt.index >= int(vec->size())) ? 0 : size_t(opt.index);
switch (opt.opt->type()) {
case coFloats: output.set_d(static_cast<const ConfigOptionFloats*>(opt.opt)->values[idx]); break;
case coInts: output.set_i(static_cast<const ConfigOptionInts*>(opt.opt)->values[idx]); break;
case coStrings: output.set_s(static_cast<const ConfigOptionStrings*>(opt.opt)->values[idx]); break;
case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break;
case coPoints: output.set_s(to_string(static_cast<const ConfigOptionPoints*>(opt.opt)->values[idx])); break;
case coBools: output.set_b(static_cast<const ConfigOptionBools*>(opt.opt)->values[idx] != 0); break;
//case coEnums: output.set_s(opt.opt->vserialize()[idx]); break;
default:
ctx->throw_exception("Unsupported vector variable type", opt.it_range);
}
}
template <typename Iterator>
static void check_writable(const MyContext *ctx, OptWithPos<Iterator> &opt) {
if (! opt.writable)
ctx->throw_exception("Cannot modify a read-only variable", opt.it_range);
}
template <typename Iterator>
static void check_numeric(const expr<Iterator> &param) {
if (! param.numeric_type())
param.throw_exception("Right side is not a numeric expression");
};
template <typename Iterator>
static size_t evaluate_count(const expr<Iterator> &expr_count) {
if (expr_count.type() != expr<Iterator>::TYPE_INT)
expr_count.throw_exception("Expected number of elements to fill a vector with.");
int count = expr_count.i();
if (count < 0)
expr_count.throw_exception("Negative number of elements specified.");
return size_t(count);
};
template <typename Iterator>
static void scalar_variable_assign_scalar(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const expr<Iterator> &rhs)
{
assert(lhs.opt->is_scalar());
check_writable(ctx, lhs);
ConfigOption *wropt = const_cast<ConfigOption*>(lhs.opt);
switch (wropt->type()) {
case coFloat:
check_numeric(rhs);
static_cast<ConfigOptionFloat*>(wropt)->value = rhs.as_d();
break;
case coInt:
check_numeric(rhs);
static_cast<ConfigOptionInt*>(wropt)->value = rhs.as_i();
break;
case coString:
static_cast<ConfigOptionString*>(wropt)->value = rhs.to_string();
break;
case coPercent:
check_numeric(rhs);
static_cast<ConfigOptionPercent*>(wropt)->value = rhs.as_d();
break;
case coBool:
if (rhs.type() != expr<Iterator>::TYPE_BOOL)
ctx->throw_exception("Right side is not a boolean expression", rhs.it_range);
static_cast<ConfigOptionBool*>(wropt)->value = rhs.b();
break;
default:
ctx->throw_exception("Unsupported output scalar variable type", lhs.it_range);
}
}
template <typename Iterator>
static void vector_variable_element_assign_scalar(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const expr<Iterator> &rhs)
{
assert(lhs.opt->is_vector());
check_writable(ctx, lhs);
if (! lhs.has_index())
ctx->throw_exception("Referencing an output vector variable when scalar is expected", lhs.it_range);
ConfigOptionVectorBase *vec = const_cast<ConfigOptionVectorBase*>(static_cast<const ConfigOptionVectorBase*>(lhs.opt));
if (vec->empty())
ctx->throw_exception("Indexing an empty vector variable", lhs.it_range);
if (lhs.index >= int(vec->size()))
ctx->throw_exception("Index out of range", lhs.it_range);
switch (lhs.opt->type()) {
case coFloats:
check_numeric(rhs);
static_cast<ConfigOptionFloats*>(vec)->values[lhs.index] = rhs.as_d();
break;
case coInts:
check_numeric(rhs);
static_cast<ConfigOptionInts*>(vec)->values[lhs.index] = rhs.as_i();
break;
case coStrings:
static_cast<ConfigOptionStrings*>(vec)->values[lhs.index] = rhs.to_string();
break;
case coPercents:
check_numeric(rhs);
static_cast<ConfigOptionPercents*>(vec)->values[lhs.index] = rhs.as_d();
break;
case coBools:
if (rhs.type() != expr<Iterator>::TYPE_BOOL)
ctx->throw_exception("Right side is not a boolean expression", rhs.it_range);
static_cast<ConfigOptionBools*>(vec)->values[lhs.index] = rhs.b();
break;
default:
ctx->throw_exception("Unsupported output vector variable type", lhs.it_range);
}
}
template <typename Iterator>
static void vector_variable_assign_expr_with_count(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const expr<Iterator> &rhs_count,
const expr<Iterator> &rhs_value)
{
size_t count = evaluate_count(rhs_count);
auto *opt = const_cast<ConfigOption*>(lhs.opt);
switch (lhs.opt->type()) {
case coFloats:
check_numeric(rhs_value);
static_cast<ConfigOptionFloats*>(opt)->values.assign(count, rhs_value.as_d());
break;
case coInts:
check_numeric(rhs_value);
static_cast<ConfigOptionInts*>(opt)->values.assign(count, rhs_value.as_i());
break;
case coStrings:
static_cast<ConfigOptionStrings*>(opt)->values.assign(count, rhs_value.to_string());
break;
case coBools:
if (rhs_value.type() != expr<Iterator>::TYPE_BOOL)
rhs_value.throw_exception("Right side is not a boolean expression");
static_cast<ConfigOptionBools*>(opt)->values.assign(count, rhs_value.b());
break;
default: assert(false);
}
}
template <typename Iterator>
static void variable_value(
const MyContext *ctx,
OptWithPos<Iterator> &opt,
expr<Iterator> &output)
{
if (opt.opt->is_vector())
vector_element_to_expr(ctx, opt, output);
else
scalar_variable_to_expr(ctx, opt, output);
output.it_range = opt.it_range;
}
@ -974,6 +1131,7 @@ namespace client
output.it_range = opt.it_range;
}
// Reference to an existing symbol, or a name of a new symbol.
template<typename Iterator>
struct NewOldVariable {
std::string name;
@ -1010,240 +1168,148 @@ namespace client
out.it_range = it_range;
}
template <typename Iterator>
static void new_scalar_variable(
const MyContext *ctx,
bool global_variable,
NewOldVariable<Iterator> &output_variable,
const expr<Iterator> &param)
{
auto check_numeric = [](const expr<Iterator> &param) {
if (! param.numeric_type())
param.throw_exception("Right side is not a numeric expression");
};
if (output_variable.opt) {
if (output_variable.opt->is_vector())
param.throw_exception("Cannot assign a scalar value to a vector variable.");
switch (output_variable.opt->type()) {
case coFloat:
check_numeric(param);
static_cast<ConfigOptionFloat*>(output_variable.opt)->value = param.as_d();
break;
case coInt:
check_numeric(param);
static_cast<ConfigOptionInt*>(output_variable.opt)->value = param.as_i();
break;
case coString:
static_cast<ConfigOptionString*>(output_variable.opt)->value = param.to_string();
break;
case coBool:
if (param.type() != expr<Iterator>::TYPE_BOOL)
param.throw_exception("Right side is not a boolean expression");
static_cast<ConfigOptionBool*>(output_variable.opt)->value = param.b();
break;
default: assert(false);
}
} else {
switch (param.type()) {
case expr<Iterator>::TYPE_BOOL: output_variable.opt = new ConfigOptionBool(param.b()); break;
case expr<Iterator>::TYPE_INT: output_variable.opt = new ConfigOptionInt(param.i()); break;
case expr<Iterator>::TYPE_DOUBLE: output_variable.opt = new ConfigOptionFloat(param.d()); break;
case expr<Iterator>::TYPE_STRING: output_variable.opt = new ConfigOptionString(param.s()); break;
default: assert(false);
}
const_cast<MyContext*>(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable);
}
}
template <typename Iterator>
static void check_writable(const MyContext *ctx, OptWithPos<Iterator> &opt) {
if (! opt.writable)
ctx->throw_exception("Cannot modify a read-only variable", opt.it_range);
}
// Decoding a scalar variable symbol "opt", assigning it a value of "param".
template <typename Iterator>
static void assign_scalar_variable(
static void scalar_variable_assign_scalar_expression(
const MyContext *ctx,
OptWithPos<Iterator> &opt,
expr<Iterator> &param)
const expr<Iterator> &param)
{
check_writable(ctx, opt);
auto check_numeric = [](const expr<Iterator> &param) {
if (! param.numeric_type())
param.throw_exception("Right side is not a numeric expression");
};
if (opt.opt->is_vector()) {
if (! opt.has_index())
ctx->throw_exception("Referencing an output vector variable when scalar is expected", opt.it_range);
ConfigOptionVectorBase *vec = const_cast<ConfigOptionVectorBase*>(static_cast<const ConfigOptionVectorBase*>(opt.opt));
if (vec->empty())
ctx->throw_exception("Indexing an empty vector variable", opt.it_range);
if (opt.index >= int(vec->size()))
ctx->throw_exception("Index out of range", opt.it_range);
switch (opt.opt->type()) {
case coFloats:
check_numeric(param);
static_cast<ConfigOptionFloats*>(vec)->values[opt.index] = param.as_d();
break;
case coInts:
check_numeric(param);
static_cast<ConfigOptionInts*>(vec)->values[opt.index] = param.as_i();
break;
case coStrings:
static_cast<ConfigOptionStrings*>(vec)->values[opt.index] = param.to_string();
break;
case coPercents:
check_numeric(param);
static_cast<ConfigOptionPercents*>(vec)->values[opt.index] = param.as_d();
break;
case coBools:
if (param.type() != expr<Iterator>::TYPE_BOOL)
ctx->throw_exception("Right side is not a boolean expression", param.it_range);
static_cast<ConfigOptionBools*>(vec)->values[opt.index] = param.b();
break;
default:
ctx->throw_exception("Unsupported output vector variable type", opt.it_range);
}
} else {
assert(opt.opt->is_scalar());
ConfigOption *wropt = const_cast<ConfigOption*>(opt.opt);
switch (wropt->type()) {
case coFloat:
check_numeric(param);
static_cast<ConfigOptionFloat*>(wropt)->value = param.as_d();
break;
case coInt:
check_numeric(param);
static_cast<ConfigOptionInt*>(wropt)->value = param.as_i();
break;
case coString:
static_cast<ConfigOptionString*>(wropt)->value = param.to_string();
break;
case coPercent:
check_numeric(param);
static_cast<ConfigOptionPercent*>(wropt)->value = param.as_d();
break;
case coBool:
if (param.type() != expr<Iterator>::TYPE_BOOL)
ctx->throw_exception("Right side is not a boolean expression", param.it_range);
static_cast<ConfigOptionBool*>(wropt)->value = param.b();
break;
default:
ctx->throw_exception("Unsupported output scalar variable type", opt.it_range);
}
}
if (opt.opt->is_vector())
vector_variable_element_assign_scalar(ctx, opt, param);
else
scalar_variable_assign_scalar(ctx, opt, param);
}
template <typename Iterator>
static void new_vector_variable_array(
static void scalar_variable_new_from_scalar_expression(
const MyContext *ctx,
bool global_variable,
NewOldVariable<Iterator> &output_variable,
const expr<Iterator> &expr_count,
const expr<Iterator> &expr_value)
NewOldVariable<Iterator> &lhs,
const expr<Iterator> &rhs)
{
auto check_numeric = [](const expr<Iterator> &param) {
if (! param.numeric_type())
param.throw_exception("Right side is not a numeric expression");
};
auto evaluate_count = [](const expr<Iterator> &expr_count) -> size_t {
if (expr_count.type() != expr<Iterator>::TYPE_INT)
expr_count.throw_exception("Expected number of elements to fill a vector with.");
int count = expr_count.i();
if (count < 0)
expr_count.throw_exception("Negative number of elements specified.");
return size_t(count);
};
if (output_variable.opt) {
if (output_variable.opt->is_scalar())
expr_value.throw_exception("Cannot assign a vector value to a scalar variable.");
size_t count = evaluate_count(expr_count);
switch (output_variable.opt->type()) {
case coFloats:
check_numeric(expr_value);
static_cast<ConfigOptionFloats*>(output_variable.opt)->values.assign(count, expr_value.as_d());
break;
case coInts:
check_numeric(expr_value);
static_cast<ConfigOptionInts*>(output_variable.opt)->values.assign(count, expr_value.as_i());
break;
case coStrings:
static_cast<ConfigOptionStrings*>(output_variable.opt)->values.assign(count, expr_value.to_string());
break;
case coBools:
if (expr_value.type() != expr<Iterator>::TYPE_BOOL)
expr_value.throw_exception("Right side is not a boolean expression");
static_cast<ConfigOptionBools*>(output_variable.opt)->values.assign(count, expr_value.b());
break;
default: assert(false);
}
if (lhs.opt) {
if (lhs.opt->is_vector())
rhs.throw_exception("Cannot assign a scalar value to a vector variable.");
OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true };
scalar_variable_assign_scalar(ctx, lhs_opt, rhs);
} else {
size_t count = evaluate_count(expr_count);
switch (expr_value.type()) {
case expr<Iterator>::TYPE_BOOL: output_variable.opt = new ConfigOptionBools(count, expr_value.b()); break;
case expr<Iterator>::TYPE_INT: output_variable.opt = new ConfigOptionInts(count, expr_value.i()); break;
case expr<Iterator>::TYPE_DOUBLE: output_variable.opt = new ConfigOptionFloats(count, expr_value.d()); break;
case expr<Iterator>::TYPE_STRING: output_variable.opt = new ConfigOptionStrings(count, expr_value.s()); break;
std::unique_ptr<ConfigOption> opt_new;
switch (rhs.type()) {
case expr<Iterator>::TYPE_BOOL: opt_new = std::make_unique<ConfigOptionBool>(rhs.b()); break;
case expr<Iterator>::TYPE_INT: opt_new = std::make_unique<ConfigOptionInt>(rhs.i()); break;
case expr<Iterator>::TYPE_DOUBLE: opt_new = std::make_unique<ConfigOptionFloat>(rhs.d()); break;
case expr<Iterator>::TYPE_STRING: opt_new = std::make_unique<ConfigOptionString>(rhs.s()); break;
default: assert(false);
}
const_cast<MyContext*>(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable);
const_cast<MyContext*>(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable);
}
}
template <typename Iterator>
static void assign_vector_variable_array(
static void vector_variable_new_from_array(
const MyContext *ctx,
bool global_variable,
NewOldVariable<Iterator> &lhs,
const expr<Iterator> &rhs_count,
const expr<Iterator> &rhs_value)
{
if (lhs.opt) {
if (lhs.opt->is_scalar())
rhs_value.throw_exception("Cannot assign a vector value to a scalar variable.");
OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true };
vector_variable_assign_expr_with_count(ctx, lhs_opt, rhs_count, rhs_value);
} else {
size_t count = evaluate_count(rhs_count);
std::unique_ptr<ConfigOption> opt_new;
switch (rhs_value.type()) {
case expr<Iterator>::TYPE_BOOL: opt_new = std::make_unique<ConfigOptionBools>(count, rhs_value.b()); break;
case expr<Iterator>::TYPE_INT: opt_new = std::make_unique<ConfigOptionInts>(count, rhs_value.i()); break;
case expr<Iterator>::TYPE_DOUBLE: opt_new = std::make_unique<ConfigOptionFloats>(count, rhs_value.d()); break;
case expr<Iterator>::TYPE_STRING: opt_new = std::make_unique<ConfigOptionStrings>(count, rhs_value.s()); break;
default: assert(false);
}
const_cast<MyContext*>(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable);
}
}
template <typename Iterator>
static void vector_variable_assign_array(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const expr<Iterator> &expr_count,
const expr<Iterator> &expr_value)
const expr<Iterator> &rhs_count,
const expr<Iterator> &rhs_value)
{
check_writable(ctx, lhs);
auto check_numeric = [](const expr<Iterator> &param) {
if (! param.numeric_type())
param.throw_exception("Right side is not a numeric expression");
};
auto evaluate_count = [](const expr<Iterator> &expr_count) -> size_t {
if (expr_count.type() != expr<Iterator>::TYPE_INT)
expr_count.throw_exception("Expected number of elements to fill a vector with.");
int count = expr_count.i();
if (count < 0)
expr_count.throw_exception("Negative number of elements specified.");
return size_t(count);
};
if (lhs.opt->is_scalar())
expr_value.throw_exception("Cannot assign a vector value to a scalar variable.");
auto *opt = const_cast<ConfigOption*>(lhs.opt);
size_t count = evaluate_count(expr_count);
rhs_value.throw_exception("Cannot assign a vector value to a scalar variable.");
vector_variable_assign_expr_with_count(ctx, lhs, rhs_count, rhs_value);
}
template<typename ConfigOptionType, typename Iterator, typename RightValueEvaluate>
static void fill_vector_from_initializer_list(ConfigOption *opt, const std::vector<expr<Iterator>> &il, RightValueEvaluate rv_eval) {
auto& out = static_cast<ConfigOptionType*>(opt)->values;
out.clear();
out.reserve(il.size());
for (const expr<Iterator>& i : il)
out.emplace_back(rv_eval(i));
}
template <typename Iterator>
static void vector_variable_assign_initializer_list(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const std::vector<expr<Iterator>> &il)
{
check_writable(ctx, lhs);
auto check_numeric_vector = [](const std::vector<expr<Iterator>> &il) {
for (auto &i : il)
if (! i.numeric_type())
i.throw_exception("Right side is not a numeric expression");
};
if (lhs.opt->is_scalar())
ctx->throw_exception("Cannot assign a vector value to a scalar variable.", lhs.it_range);
ConfigOption *opt = const_cast<ConfigOption*>(lhs.opt);
switch (lhs.opt->type()) {
case coFloats:
check_numeric(expr_value);
static_cast<ConfigOptionFloats*>(opt)->values.assign(count, expr_value.as_d());
check_numeric_vector(il);
fill_vector_from_initializer_list<ConfigOptionFloats>(opt, il, [](auto &v){ return v.as_d(); });
break;
case coInts:
check_numeric(expr_value);
static_cast<ConfigOptionInts*>(opt)->values.assign(count, expr_value.as_i());
check_numeric_vector(il);
fill_vector_from_initializer_list<ConfigOptionInts>(opt, il, [](auto &v){ return v.as_i(); });
break;
case coStrings:
static_cast<ConfigOptionStrings*>(opt)->values.assign(count, expr_value.to_string());
fill_vector_from_initializer_list<ConfigOptionStrings>(opt, il, [](auto &v){ return v.to_string(); });
break;
case coBools:
if (expr_value.type() != expr<Iterator>::TYPE_BOOL)
expr_value.throw_exception("Right side is not a boolean expression");
static_cast<ConfigOptionBools*>(opt)->values.assign(count, expr_value.b());
for (auto &i : il)
if (i.type() != expr<Iterator>::TYPE_BOOL)
i.throw_exception("Right side is not a boolean expression");
fill_vector_from_initializer_list<ConfigOptionBools>(opt, il, [](auto &v){ return v.b(); });
break;
default: assert(false);
}
}
template <typename Iterator>
static void new_vector_variable_initializer_list(
static void vector_variable_new_from_initializer_list(
const MyContext *ctx,
bool global_variable,
NewOldVariable<Iterator> &output_variable,
NewOldVariable<Iterator> &lhs,
const std::vector<expr<Iterator>> &il)
{
if (! output_variable.opt) {
if (lhs.opt) {
// Assign to an existing vector variable.
if (lhs.opt->is_scalar())
ctx->throw_exception("Cannot assign a vector value to a scalar variable.", lhs.it_range);
OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true };
vector_variable_assign_initializer_list(ctx, lhs_opt, il);
} else {
// Allocate a new vector variable.
// First guesstimate type of the output vector.
size_t num_bool = 0;
size_t num_int = 0;
@ -1257,186 +1323,53 @@ namespace client
case expr<Iterator>::TYPE_STRING: ++ num_string; break;
default: assert(false);
}
std::unique_ptr<ConfigOption> opt_new;
if (num_string > 0)
// Convert everything to strings.
output_variable.opt = new ConfigOptionStrings();
opt_new = std::make_unique<ConfigOptionStrings>();
else if (num_bool > 0) {
if (num_double + num_int > 0)
ctx->throw_exception("Right side is not valid: Mixing numeric and boolean types.", boost::iterator_range<Iterator>{ il.front().it_range.begin(), il.back().it_range.end() });
output_variable.opt = new ConfigOptionBools();
} else
opt_new = std::make_unique<ConfigOptionBools>();
} else {
// Output is numeric.
output_variable.opt = num_double == 0 ? static_cast<ConfigOption*>(new ConfigOptionInts()) : static_cast<ConfigOption*>(new ConfigOptionFloats());
const_cast<MyContext*>(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable);
}
auto check_numeric = [](const std::vector<expr<Iterator>> &il) {
for (auto& i : il)
if (!i.numeric_type())
i.throw_exception("Right side is not a numeric expression");
};
if (output_variable.opt->is_scalar())
ctx->throw_exception("Cannot assign a vector value to a scalar variable.", output_variable.it_range);
switch (output_variable.opt->type()) {
case coFloats:
{
check_numeric(il);
auto &out = static_cast<ConfigOptionFloats*>(output_variable.opt)->values;
out.clear();
out.reserve(il.size());
for (auto &i : il)
out.emplace_back(i.as_d());
break;
}
case coInts:
{
check_numeric(il);
auto &out = static_cast<ConfigOptionInts*>(output_variable.opt)->values;
out.clear();
out.reserve(il.size());
for (auto& i : il)
out.emplace_back(i.as_i());
break;
}
case coStrings:
{
auto &out = static_cast<ConfigOptionStrings*>(output_variable.opt)->values;
out.clear();
out.reserve(il.size());
for (auto &i : il)
out.emplace_back(i.to_string());
break;
}
case coBools:
{
auto &out = static_cast<ConfigOptionBools*>(output_variable.opt)->values;
out.clear();
out.reserve(il.size());
for (auto &i : il)
if (i.type() == expr<Iterator>::TYPE_BOOL)
out.emplace_back(i.b());
if (num_double == 0)
opt_new = std::make_unique<ConfigOptionInts>();
else
i.throw_exception("Right side is not a boolean expression");
break;
opt_new = std::make_unique<ConfigOptionFloats>();
}
default:
assert(false);
OptWithPos lhs_opt{ opt_new.get(), lhs.it_range, true };
vector_variable_assign_initializer_list(ctx, lhs_opt, il);
const_cast<MyContext*>(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable);
}
}
template <typename Iterator>
static void assign_vector_variable_initializer_list(
static void copy_vector_variable_to_vector_variable(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const std::vector<expr<Iterator>> &il)
const OptWithPos<Iterator> &rhs)
{
check_writable(ctx, lhs);
auto check_numeric = [](const std::vector<expr<Iterator>> &il) {
for (auto &i : il)
if (! i.numeric_type())
i.throw_exception("Right side is not a numeric expression");
};
if (lhs.opt->is_scalar())
ctx->throw_exception("Cannot assign a vector value to a scalar variable.", lhs.it_range);
ConfigOption *opt = const_cast<ConfigOption*>(lhs.opt);
assert(rhs.opt->is_vector());
if (! lhs.opt->is_vector())
ctx->throw_exception("Cannot assign vector to a scalar", lhs.it_range);
if (lhs.opt->type() != rhs.opt->type()) {
// Vector types are not compatible.
switch (lhs.opt->type()) {
case coFloats:
{
check_numeric(il);
auto &out = static_cast<ConfigOptionFloats*>(opt)->values;
out.clear();
out.reserve(il.size());
for (auto &i : il)
out.emplace_back(i.as_d());
break;
}
ctx->throw_exception("Left hand side is a float vector, while the right hand side is not.", lhs.it_range);
case coInts:
{
check_numeric(il);
auto &out = static_cast<ConfigOptionInts*>(opt)->values;
out.clear();
out.reserve(il.size());
for (auto& i : il)
out.emplace_back(i.as_i());
break;
}
ctx->throw_exception("Left hand side is an int vector, while the right hand side is not.", lhs.it_range);
case coStrings:
{
auto &out = static_cast<ConfigOptionStrings*>(opt)->values;
out.clear();
out.reserve(il.size());
for (auto &i : il)
out.emplace_back(i.to_string());
break;
}
ctx->throw_exception("Left hand side is a string vector, while the right hand side is not.", lhs.it_range);
case coBools:
{
auto &out = static_cast<ConfigOptionBools*>(opt)->values;
out.clear();
out.reserve(il.size());
for (auto &i : il)
if (i.type() == expr<Iterator>::TYPE_BOOL)
out.emplace_back(i.b());
else
i.throw_exception("Right side is not a boolean expression");
break;
}
ctx->throw_exception("Left hand side is a bool vector, while the right hand side is not.", lhs.it_range);
default:
assert(false);
ctx->throw_exception("Left hand side / right hand side vectors are not compatible.", lhs.it_range);
}
}
template <typename Iterator>
static bool new_vector_variable_copy(
const MyContext *ctx,
bool global_variable,
NewOldVariable<Iterator> &output_variable,
const OptWithPos<Iterator> &src_variable)
{
if (! is_vector_variable_reference(src_variable))
// Skip parsing this branch, bactrack.
return false;
if (! output_variable.opt) {
if (one_of(src_variable.opt->type(), { coFloats, coInts, coStrings, coBools }))
output_variable.opt = src_variable.opt->clone();
else if (src_variable.opt->type() == coPercents)
output_variable.opt = new ConfigOptionFloats(static_cast<const ConfigOptionPercents*>(src_variable.opt)->values);
else
ctx->throw_exception("Duplicating this vector variable is not supported", src_variable.it_range);
const_cast<MyContext*>(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable);
}
switch (output_variable.opt->type()) {
case coFloats:
if (output_variable.opt->type() != coFloats)
ctx->throw_exception("Left hand side is a float vector, while the right hand side is not.", boost::iterator_range<Iterator>{ output_variable.it_range.begin(), src_variable.it_range.end() });
static_cast<ConfigOptionFloats*>(output_variable.opt)->values = static_cast<const ConfigOptionFloats*>(src_variable.opt)->values;
break;
case coInts:
if (output_variable.opt->type() != coInts)
ctx->throw_exception("Left hand side is an int vector, while the right hand side is not.", boost::iterator_range<Iterator>{ output_variable.it_range.begin(), src_variable.it_range.end() });
static_cast<ConfigOptionInts*>(output_variable.opt)->values = static_cast<const ConfigOptionInts*>(src_variable.opt)->values;
break;
case coStrings:
if (output_variable.opt->type() != coStrings)
ctx->throw_exception("Left hand side is a string vector, while the right hand side is not.", boost::iterator_range<Iterator>{ output_variable.it_range.begin(), src_variable.it_range.end() });
static_cast<ConfigOptionStrings*>(output_variable.opt)->values = static_cast<const ConfigOptionStrings*>(src_variable.opt)->values;
break;
case coBools:
if (output_variable.opt->type() != coBools)
ctx->throw_exception("Left hand side is a bool vector, while the right hand side is not.", boost::iterator_range<Iterator>{ output_variable.it_range.begin(), src_variable.it_range.end() });
static_cast<ConfigOptionBools*>(output_variable.opt)->values = static_cast<const ConfigOptionBools*>(src_variable.opt)->values;
break;
default:
assert(false);
}
// Continue parsing.
return true;
const_cast<ConfigOption*>(lhs.opt)->set(rhs.opt);
}
template <typename Iterator>
@ -1445,49 +1378,53 @@ namespace client
}
template <typename Iterator>
static bool assign_vector_variable_copy(
static bool vector_variable_new_from_copy(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const OptWithPos<Iterator> &src_variable)
bool global_variable,
NewOldVariable<Iterator> &lhs,
const OptWithPos<Iterator> &rhs)
{
if (! is_vector_variable_reference(src_variable))
// Skip parsing this branch, bactrack.
return false;
check_writable(ctx, lhs);
auto *opt = const_cast<ConfigOption*>(lhs.opt);
switch (lhs.opt->type()) {
case coFloats:
if (lhs.opt->type() != coFloats)
ctx->throw_exception("Left hand side is a float vector, while the right hand side is not.", lhs.it_range);
static_cast<ConfigOptionFloats*>(opt)->values = static_cast<const ConfigOptionFloats*>(src_variable.opt)->values;
break;
case coInts:
if (lhs.opt->type() != coInts)
ctx->throw_exception("Left hand side is an int vector, while the right hand side is not.", lhs.it_range);
static_cast<ConfigOptionInts*>(opt)->values = static_cast<const ConfigOptionInts*>(src_variable.opt)->values;
break;
case coStrings:
if (lhs.opt->type() != coStrings)
ctx->throw_exception("Left hand side is a string vector, while the right hand side is not.", lhs.it_range);
static_cast<ConfigOptionStrings*>(opt)->values = static_cast<const ConfigOptionStrings*>(src_variable.opt)->values;
break;
case coBools:
if (lhs.opt->type() != coBools)
ctx->throw_exception("Left hand side is a bool vector, while the right hand side is not.", lhs.it_range);
static_cast<ConfigOptionBools*>(opt)->values = static_cast<const ConfigOptionBools*>(src_variable.opt)->values;
break;
default:
assert(false);
if (is_vector_variable_reference(rhs)) {
if (lhs.opt) {
OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true };
copy_vector_variable_to_vector_variable(ctx, lhs_opt, rhs);
} else {
// Clone the vector variable.
std::unique_ptr<ConfigOption> opt_new;
if (one_of(rhs.opt->type(), { coFloats, coInts, coStrings, coBools }))
opt_new = std::unique_ptr<ConfigOption>(rhs.opt->clone());
else if (rhs.opt->type() == coPercents)
opt_new = std::make_unique<ConfigOptionFloats>(static_cast<const ConfigOptionPercents*>(rhs.opt)->values);
else
ctx->throw_exception("Duplicating this type of vector variable is not supported", rhs.it_range);
const_cast<MyContext*>(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable);
}
// Continue parsing.
return true;
} else {
// Skip parsing this branch, bactrack.
return false;
}
}
template <typename Iterator>
static void new_vector_variable_initializer_list_append(std::vector<expr<Iterator>> &list, expr<Iterator> &expr)
static bool vector_variable_assign_copy(
const MyContext *ctx,
OptWithPos<Iterator> &lhs,
const OptWithPos<Iterator> &rhs)
{
if (is_vector_variable_reference(rhs)) {
copy_vector_variable_to_vector_variable(ctx, lhs, rhs);
// Continue parsing.
return true;
} else {
// Skip parsing this branch, bactrack.
return false;
}
}
template <typename Iterator>
static void initializer_list_append(std::vector<expr<Iterator>> &list, expr<Iterator> &expr)
{
list.emplace_back(std::move(expr));
}
@ -1570,13 +1507,12 @@ namespace client
template<typename Iterator>
struct InterpolateTableContext {
template<typename Iterator>
struct Item {
double x;
boost::iterator_range<Iterator> it_range_x;
double y;
};
std::vector<Item<Iterator>> table;
std::vector<Item> table;
static void init(const expr<Iterator> &x) {
if (!x.numeric_type())
@ -1785,8 +1721,8 @@ namespace client
// Also the start symbol switches between the "full macro syntax" and a "boolean expression only",
// depending on the context->just_boolean_expression flag. This way a single static expression parser
// could serve both purposes.
start = eps[px::bind(&MyContext::evaluate_full_macro, _r1, _a)] >
( (eps(_a==true) > text_block(_r1) [_val=_1])
start =
( (eps(px::bind(&MyContext::evaluate_full_macro, _r1)) > text_block(_r1) [_val=_1])
| conditional_expression(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean_to_string, _1, _val) ]
) > eoi;
start.name("start");
@ -1911,36 +1847,36 @@ namespace client
variable_reference(_r1)[_a = _1] >> '=' >
( // Consumes also '(' conditional_expression ')', that means enclosing an expression into braces makes it a single value vector initializer.
(lit('(') > new_variable_initializer_list(_r1) > ')')
[px::bind(&MyContext::assign_vector_variable_initializer_list<Iterator>, _r1, _a, _1)]
[px::bind(&MyContext::vector_variable_assign_initializer_list<Iterator>, _r1, _a, _1)]
// Process it before conditional_expression, as conditional_expression requires a vector reference to be augmented with an index.
// Only process such variable references, which return a naked vector variable.
| variable_reference(_r1)
[px::ref(qi::_pass) = px::bind(&MyContext::assign_vector_variable_copy<Iterator>, _r1, _a, _1)]
[px::ref(qi::_pass) = px::bind(&MyContext::vector_variable_assign_copy<Iterator>, _r1, _a, _1)]
// Would NOT consume '(' conditional_expression ')' because such value was consumed with the expression above.
| conditional_expression(_r1)
[px::bind(&MyContext::assign_scalar_variable<Iterator>, _r1, _a, _1)]
[px::bind(&MyContext::scalar_variable_assign_scalar_expression<Iterator>, _r1, _a, _1)]
| (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")")
[px::bind(&MyContext::assign_vector_variable_array<Iterator>, _r1, _a, _1, _2)]
[px::bind(&MyContext::vector_variable_assign_array<Iterator>, _r1, _a, _1, _2)]
);
new_variable_statement =
(kw["local"][_a = false] | kw["global"][_a = true]) > identifier[px::bind(&MyContext::new_old_variable<Iterator>, _r1, _a, _1, _b)] > lit('=') >
( // Consumes also '(' conditional_expression ')', that means enclosing an expression into braces makes it a single value vector initializer.
(lit('(') > new_variable_initializer_list(_r1) > ')')
[px::bind(&MyContext::new_vector_variable_initializer_list<Iterator>, _r1, _a, _b, _1)]
[px::bind(&MyContext::vector_variable_new_from_initializer_list<Iterator>, _r1, _a, _b, _1)]
// Process it before conditional_expression, as conditional_expression requires a vector reference to be augmented with an index.
// Only process such variable references, which return a naked vector variable.
| variable_reference(_r1)
[px::ref(qi::_pass) = px::bind(&MyContext::new_vector_variable_copy<Iterator>, _r1, _a, _b, _1)]
[px::ref(qi::_pass) = px::bind(&MyContext::vector_variable_new_from_copy<Iterator>, _r1, _a, _b, _1)]
// Would NOT consume '(' conditional_expression ')' because such value was consumed with the expression above.
| conditional_expression(_r1)
[px::bind(&MyContext::new_scalar_variable<Iterator>, _r1, _a, _b, _1)]
[px::bind(&MyContext::scalar_variable_new_from_scalar_expression<Iterator>, _r1, _a, _b, _1)]
| (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")")
[px::bind(&MyContext::new_vector_variable_array<Iterator>, _r1, _a, _b, _1, _2)]
[px::bind(&MyContext::vector_variable_new_from_array<Iterator>, _r1, _a, _b, _1, _2)]
);
new_variable_initializer_list =
conditional_expression(_r1)[px::bind(&MyContext::new_vector_variable_initializer_list_append<Iterator>, _val, _1)] >>
*(lit(',') > conditional_expression(_r1)[px::bind(&MyContext::new_vector_variable_initializer_list_append<Iterator>, _val, _1)]);
conditional_expression(_r1)[px::bind(&MyContext::initializer_list_append<Iterator>, _val, _1)] >>
*(lit(',') > conditional_expression(_r1)[px::bind(&MyContext::initializer_list_append<Iterator>, _val, _1)]);
struct FactorActions {
static void set_start_pos(Iterator &start_pos, expr<Iterator> &out)

View file

@ -460,8 +460,8 @@ static std::vector<std::string> s_Preset_print_options {
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits",
"wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
"perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
"wall_distribution_count", "min_feature_size", "min_bead_width"
};

View file

@ -205,7 +205,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wipe_tower"
|| opt_key == "wipe_tower_width"
|| opt_key == "wipe_tower_brim_width"
|| opt_key == "wipe_tower_cone_angle"
|| opt_key == "wipe_tower_bridging"
|| opt_key == "wipe_tower_extra_spacing"
|| opt_key == "wipe_tower_no_sparse_layers"
|| opt_key == "wiping_volumes_matrix"
|| opt_key == "parking_pos_retraction"
@ -1133,23 +1135,34 @@ Polygons Print::first_layer_islands() const
std::vector<Point> Print::first_layer_wipe_tower_corners() const
{
std::vector<Point> corners;
std::vector<Point> pts_scaled;
if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) {
double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width;
double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width;
Vec2d pt0(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width);
for (Vec2d pt : {
pt0,
// First the corners.
std::vector<Vec2d> pts = { pt0,
Vec2d(pt0.x()+width, pt0.y()),
Vec2d(pt0.x()+width, pt0.y()+depth),
Vec2d(pt0.x(),pt0.y()+depth)
}) {
};
// Now the stabilization cone.
Vec2d center = (pts[0] + pts[2])/2.;
const auto [cone_R, cone_x_scale] = WipeTower::get_wipe_tower_cone_base(m_config.wipe_tower_width, m_wipe_tower_data.height, m_wipe_tower_data.depth, m_config.wipe_tower_cone_angle);
double r = cone_R + m_wipe_tower_data.brim_width;
for (double alpha = 0.; alpha<2*M_PI; alpha += M_PI/20.)
pts.emplace_back(center + r*Vec2d(std::cos(alpha)/cone_x_scale, std::sin(alpha)));
for (Vec2d& pt : pts) {
pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt;
pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value);
corners.emplace_back(Point(scale_(pt.x()), scale_(pt.y())));
pts_scaled.emplace_back(Point(scale_(pt.x()), scale_(pt.y())));
}
}
return corners;
return pts_scaled;
}
void Print::finalize_first_layer_convex_hull()
@ -1447,6 +1460,7 @@ void Print::_make_wipe_tower()
wipe_tower.generate(m_wipe_tower_data.tool_changes);
m_wipe_tower_data.depth = wipe_tower.get_depth();
m_wipe_tower_data.brim_width = wipe_tower.get_brim_width();
m_wipe_tower_data.height = wipe_tower.get_wipe_tower_height();
// Unload the current filament over the purge tower.
coordf_t layer_height = m_objects.front()->config().layer_height.value;

View file

@ -434,6 +434,7 @@ struct WipeTowerData
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
float depth;
float brim_width;
float height;
void clear() {
priming.reset(nullptr);

View file

@ -3150,6 +3150,25 @@ void PrintConfigDef::init_fff_params()
def->min = 0.;
def->set_default_value(new ConfigOptionFloat(2.));
def = this->add("wipe_tower_cone_angle", coFloat);
def->label = L("Stabilization cone apex angle");
def->tooltip = L("Angle at the apex of the cone that is used to stabilize the wipe tower. "
"Larger angle means wider base.");
def->sidetext = L("°");
def->mode = comAdvanced;
def->min = 0.;
def->max = 90.;
def->set_default_value(new ConfigOptionFloat(0.));
def = this->add("wipe_tower_extra_spacing", coPercent);
def->label = L("Wipe tower purge lines spacing");
def->tooltip = L("Spacing of purge lines on the wipe tower.");
def->sidetext = L("%");
def->mode = comExpert;
def->min = 100.;
def->max = 300.;
def->set_default_value(new ConfigOptionPercent(100.));
def = this->add("wipe_into_infill", coBool);
def->category = L("Wipe options");
def->label = L("Wipe into this object's infill");

View file

@ -823,6 +823,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionFloat, wipe_tower_per_color_wipe))
((ConfigOptionFloat, wipe_tower_rotation_angle))
((ConfigOptionFloat, wipe_tower_brim_width))
((ConfigOptionFloat, wipe_tower_cone_angle))
((ConfigOptionPercent, wipe_tower_extra_spacing))
((ConfigOptionFloat, wipe_tower_bridging))
((ConfigOptionFloats, wiping_volumes_matrix))
((ConfigOptionFloats, wiping_volumes_extruders))

View file

@ -480,11 +480,11 @@ int GLVolumeCollection::load_object_volume(
#if ENABLE_OPENGL_ES
int GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height,
float pos_x, float pos_y, float width, float depth, float height, float cone_angle,
float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh)
#else
int GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height,
float pos_x, float pos_y, float width, float depth, float height, float cone_angle,
float rotation_angle, bool size_unknown, float brim_width)
#endif // ENABLE_OPENGL_ES
{
@ -544,6 +544,21 @@ int GLVolumeCollection::load_wipe_tower_preview(
brim_mesh.translate(-brim_width, -brim_width, 0.f);
mesh.merge(brim_mesh);
// Now the stabilization cone and its base.
const auto [R, scale_x] = WipeTower::get_wipe_tower_cone_base(width, height, depth, cone_angle);
if (R > 0.) {
TriangleMesh cone_mesh(its_make_cone(R, height));
cone_mesh.scale(Vec3f(1.f/scale_x, 1.f, 1.f));
TriangleMesh disk_mesh(its_make_cylinder(R, brim_height));
disk_mesh.scale(Vec3f(1. / scale_x, 1., 1.)); // Now it matches the base, which may be elliptic.
disk_mesh.scale(Vec3f(1.f + scale_x*brim_width/R, 1.f + brim_width/R, 1.f)); // Scale so the brim is not deformed.
cone_mesh.merge(disk_mesh);
cone_mesh.translate(width / 2., depth / 2., 0.);
mesh.merge(cone_mesh);
}
volumes.emplace_back(new GLVolume(color));
GLVolume& v = *volumes.back();
#if ENABLE_OPENGL_ES

View file

@ -424,10 +424,10 @@ public:
#if ENABLE_OPENGL_ES
int load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr);
float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr);
#else
int load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width);
float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width);
#endif // ENABLE_OPENGL_ES
GLVolume* new_toolpath_volume(const ColorRGBA& rgba);

View file

@ -319,8 +319,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field("standby_temperature_delta", have_ooze_prevention);
bool have_wipe_tower = config->opt_bool("wipe_tower");
for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width",
"wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle",
"wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
toggle_field(el, have_wipe_tower);
toggle_field("avoid_crossing_curled_overhangs", !config->opt_bool("avoid_crossing_perimeters"));

View file

@ -2253,7 +2253,7 @@ void GCodeViewer::load_shells(const Print& print)
const WipeTowerData& wipe_tower_data = print.wipe_tower_data(extruders_count);
const float depth = wipe_tower_data.depth;
const float brim_width = wipe_tower_data.brim_width;
m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_cone_angle, config.wipe_tower_rotation_angle,
!print.is_step_done(psWipeTower), brim_width);
}
}

View file

@ -2035,17 +2035,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
const float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_width"))->value;
const float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
const float bw = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_brim_width"))->value;
const float ca = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_cone_angle"))->value;
const Print *print = m_process->fff_print();
const float depth = print->wipe_tower_data(extruders_count).depth;
#if ENABLE_OPENGL_ES
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower),
x, y, w, depth, (float)height, ca, a, !print->is_step_done(psWipeTower),
bw, &m_wipe_tower_mesh);
#else
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower),
x, y, w, depth, (float)height, ca, a, !print->is_step_done(psWipeTower),
bw);
#endif // ENABLE_OPENGL_ES
if (volume_idx_wipe_tower_old != -1)

View file

@ -2016,7 +2016,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
, config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
"bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
"brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing",
"extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_technology",
// These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
"layer_height", "first_layer_height", "min_layer_height", "max_layer_height",
@ -4779,7 +4779,7 @@ bool Plater::priv::can_increase_instances() const
if (q->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss) return false;
const auto obj_idxs = get_selection().get_object_idxs();
return !obj_idxs.empty() && !sidebar->obj_list()->has_selected_cut_object();
return !obj_idxs.empty() && !get_selection().is_wipe_tower() && !sidebar->obj_list()->has_selected_cut_object();
}
bool Plater::priv::can_decrease_instances(int obj_idx /*= -1*/) const

View file

@ -1602,6 +1602,8 @@ void TabPrint::build()
optgroup->append_single_option_line("wipe_tower_rotation_angle");
optgroup->append_single_option_line("wipe_tower_brim_width");
optgroup->append_single_option_line("wipe_tower_bridging");
optgroup->append_single_option_line("wipe_tower_cone_angle");
optgroup->append_single_option_line("wipe_tower_extra_spacing");
optgroup->append_single_option_line("wipe_tower_no_sparse_layers");
optgroup->append_single_option_line("single_extruder_multi_material_priming");

View file

@ -258,4 +258,16 @@ SCENARIO("Custom G-code", "[CustomGCode]")
REQUIRE(match_count == 2);
}
}
GIVEN("before_layer_gcode increments global variable") {
auto config = Slic3r::DynamicPrintConfig::new_with({
{ "start_gcode", "{global counter=0}" },
{ "before_layer_gcode", ";Counter{counter=counter+1;counter}\n" }
});
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
THEN("The counter is emitted multiple times before layer change.") {
REQUIRE(Slic3r::Test::contains(gcode, ";Counter1\n"));
REQUIRE(Slic3r::Test::contains(gcode, ";Counter2\n"));
REQUIRE(Slic3r::Test::contains(gcode, ";Counter3\n"));
}
}
}