mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-15 02:15:55 +08:00
Replace GCode.cpp travel_to with more general z-hop strategy.
The new travel has an initial flat part, sloped part and once the travel height reaches maxima a flat part again. Also, the notion of extruder lift is removed. It is used no more. Consequently the retract_lift parameter lost its original meaning.
This commit is contained in:
parent
09466df1d4
commit
49455cf427
@ -141,11 +141,6 @@ double Extruder::retract_length() const
|
||||
return m_config->retract_length.get_at(m_id);
|
||||
}
|
||||
|
||||
double Extruder::retract_lift() const
|
||||
{
|
||||
return m_config->retract_lift.get_at(m_id);
|
||||
}
|
||||
|
||||
int Extruder::retract_speed() const
|
||||
{
|
||||
return int(floor(m_config->retract_speed.get_at(m_id)+0.5));
|
||||
|
@ -182,7 +182,7 @@ void GCodeGenerator::PlaceholderParserIntegration::reset()
|
||||
this->failed_templates.clear();
|
||||
this->output_config.clear();
|
||||
this->opt_position = nullptr;
|
||||
this->opt_zhop = nullptr;
|
||||
this->opt_zhop = nullptr;
|
||||
this->opt_e_position = nullptr;
|
||||
this->opt_e_retracted = nullptr;
|
||||
this->opt_e_restart_extra = nullptr;
|
||||
@ -228,6 +228,7 @@ void GCodeGenerator::PlaceholderParserIntegration::init(const GCodeWriter &write
|
||||
this->position.assign(3, 0);
|
||||
this->opt_position = new ConfigOptionFloats(this->position);
|
||||
this->output_config.set_key_value("position", this->opt_position);
|
||||
|
||||
// Store zhop variable into the parser itself, it is a read-only variable to the script.
|
||||
this->opt_zhop = new ConfigOptionFloat(writer.get_zhop());
|
||||
this->parser.set("zhop", this->opt_zhop);
|
||||
@ -237,7 +238,6 @@ void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const
|
||||
{
|
||||
memcpy(this->position.data(), writer.get_position().data(), sizeof(double) * 3);
|
||||
this->opt_position->values = this->position;
|
||||
this->opt_zhop->value = writer.get_zhop();
|
||||
|
||||
if (this->num_extruders > 0) {
|
||||
const std::vector<Extruder> &extruders = writer.extruders();
|
||||
@ -1257,7 +1257,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
|
||||
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
|
||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
file.write(this->retract());
|
||||
file.write(this->retract_and_wipe());
|
||||
file.write(this->travel_to(Point(0, 0), ExtrusionRole::None, "move to origin position for next object"));
|
||||
m_enable_cooling_markers = true;
|
||||
// Disable motion planner when traveling to first object point.
|
||||
@ -1308,7 +1308,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
bool overlap = bbox_prime.overlap(bbox_print);
|
||||
|
||||
if (print.config().gcode_flavor == gcfMarlinLegacy || print.config().gcode_flavor == gcfMarlinFirmware) {
|
||||
file.write(this->retract());
|
||||
file.write(this->retract_and_wipe());
|
||||
file.write("M300 S800 P500\n"); // Beep for 500ms, tone 800Hz.
|
||||
if (overlap) {
|
||||
// Wait for the user to remove the priming extrusions.
|
||||
@ -1344,7 +1344,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
}
|
||||
|
||||
// Write end commands to file.
|
||||
file.write(this->retract());
|
||||
file.write(this->retract_and_wipe());
|
||||
file.write(m_writer.set_fan(0));
|
||||
|
||||
// adds tag for processor
|
||||
@ -2616,8 +2616,8 @@ std::string GCodeGenerator::change_layer(coordf_t print_z)
|
||||
// Increment a progress bar indicator.
|
||||
gcode += m_writer.update_progress(++ m_layer_index, m_layer_count);
|
||||
coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates
|
||||
if (EXTRUDER_CONFIG(retract_layer_change) && m_writer.will_move_z(z))
|
||||
gcode += this->retract();
|
||||
if (EXTRUDER_CONFIG(retract_layer_change))
|
||||
gcode += this->retract_and_wipe();
|
||||
|
||||
{
|
||||
std::ostringstream comment;
|
||||
@ -2688,9 +2688,11 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
|
||||
} else if (loop_src.paths.back().role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) {
|
||||
// Only wipe inside if the wipe along the perimeter is disabled.
|
||||
// Make a little move inwards before leaving loop.
|
||||
if (std::optional<Point> pt = wipe_hide_seam(smooth_path, is_hole, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt)
|
||||
if (std::optional<Point> pt = wipe_hide_seam(smooth_path, is_hole, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) {
|
||||
// Generate the seam hiding travel move.
|
||||
gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel");
|
||||
this->set_last_pos(*pt);
|
||||
}
|
||||
}
|
||||
|
||||
return gcode;
|
||||
@ -2891,7 +2893,13 @@ std::string GCodeGenerator::_extrude(
|
||||
const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv;
|
||||
|
||||
// go to first point of extrusion path
|
||||
if (!m_last_pos_defined || m_last_pos != path.front().point) {
|
||||
if (!m_last_pos_defined) {
|
||||
const double z = this->m_last_layer_z + this->m_config.z_offset.value;
|
||||
const std::string comment{"move to print after unknown position"};
|
||||
gcode += this->retract_and_wipe();
|
||||
gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment);
|
||||
gcode += this->m_writer.get_travel_to_z_gcode(z, comment);
|
||||
} else if ( m_last_pos != path.front().point) {
|
||||
std::string comment = "move to first ";
|
||||
comment += description;
|
||||
comment += description_bridge;
|
||||
@ -3127,86 +3135,217 @@ std::string GCodeGenerator::_extrude(
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// This method accepts &point in print coordinates.
|
||||
std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, std::string comment)
|
||||
{
|
||||
/* Define the travel move as a line between current position and the taget point.
|
||||
This is expressed in print coordinates, so it will need to be translated by
|
||||
this->origin in order to get G-code coordinates. */
|
||||
Polyline travel { this->last_pos(), point };
|
||||
Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation) {
|
||||
Points3 result;
|
||||
result.reserve(xy_path.size() - 1);
|
||||
for (const Point& point : xy_path.subspan(1)) {
|
||||
result.emplace_back(point.x(), point.y(), scaled(elevation));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (this->config().avoid_crossing_curled_overhangs) {
|
||||
if (m_config.avoid_crossing_perimeters) {
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Option >avoid crossing curled overhangs< is not compatible with avoid crossing perimeters and it will be ignored!";
|
||||
Vec2d place_at_segment(const Vec2d& current_point, const Vec2d& previous_point, const double distance) {
|
||||
Vec2d direction = (current_point - previous_point).normalized();
|
||||
return previous_point + direction * distance;
|
||||
}
|
||||
|
||||
namespace GCode::Impl {
|
||||
std::vector<DistancedPoint> slice_xy_path(tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances) {
|
||||
assert(xy_path.size() >= 2);
|
||||
std::vector<DistancedPoint> result;
|
||||
result.reserve(xy_path.size() + sorted_distances.size());
|
||||
double total_distance{0};
|
||||
result.emplace_back(DistancedPoint{xy_path.front(), 0});
|
||||
Point previous_point = result.front().point;
|
||||
std::size_t offset{0};
|
||||
for (const Point& point : xy_path.subspan(1)) {
|
||||
Vec2d unscaled_point{unscaled(point)};
|
||||
Vec2d unscaled_previous_point{unscaled(previous_point)};
|
||||
const double current_segment_length = (unscaled_point - unscaled_previous_point).norm();
|
||||
for (const double distance_to_add : sorted_distances.subspan(offset)) {
|
||||
if (distance_to_add <= total_distance + current_segment_length) {
|
||||
Point to_place = scaled(place_at_segment(
|
||||
unscaled_point,
|
||||
unscaled_previous_point,
|
||||
distance_to_add - total_distance
|
||||
));
|
||||
if (to_place != previous_point && to_place != point) {
|
||||
result.emplace_back(DistancedPoint{to_place, distance_to_add});
|
||||
}
|
||||
++offset;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
total_distance += current_segment_length;
|
||||
result.emplace_back(DistancedPoint{point, total_distance});
|
||||
previous_point = point;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct ElevatedTravelParams {
|
||||
double lift_height{};
|
||||
double slope_end{};
|
||||
};
|
||||
|
||||
struct ElevatedTravelFormula {
|
||||
double operator()(double distance_from_start) const {
|
||||
if (distance_from_start < this->params.slope_end) {
|
||||
const double lift_percent = distance_from_start / this->params.slope_end;
|
||||
return lift_percent * this->params.lift_height;
|
||||
} else {
|
||||
Point scaled_origin = Point(scaled(this->origin()));
|
||||
travel = m_avoid_crossing_curled_overhangs.find_path(this->last_pos() + scaled_origin, point + scaled_origin);
|
||||
travel.translate(-scaled_origin);
|
||||
return this->params.lift_height;
|
||||
}
|
||||
}
|
||||
|
||||
// check whether a straight travel move would need retraction
|
||||
bool needs_retraction = this->needs_retraction(travel, role);
|
||||
// check whether wipe could be disabled without causing visible stringing
|
||||
bool could_be_wipe_disabled = false;
|
||||
// Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to.
|
||||
const bool used_external_mp_once = m_avoid_crossing_perimeters.used_external_mp_once();
|
||||
ElevatedTravelParams params{};
|
||||
};
|
||||
|
||||
// if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
|
||||
// multi-hop travel path inside the configuration space
|
||||
if (needs_retraction
|
||||
&& m_config.avoid_crossing_perimeters
|
||||
&& ! m_avoid_crossing_perimeters.disabled_once()) {
|
||||
travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled);
|
||||
// check again whether the new travel path still needs a retraction
|
||||
needs_retraction = this->needs_retraction(travel, role);
|
||||
//if (needs_retraction && m_layer_index > 1) exit(0);
|
||||
Points3 generate_elevated_travel(
|
||||
const tcb::span<const Point> xy_path,
|
||||
const std::vector<double>& ensure_points_at_distances,
|
||||
const double initial_elevation,
|
||||
const std::function<double(double)>& elevation
|
||||
) {
|
||||
Points3 result{};
|
||||
|
||||
std::vector<DistancedPoint> extended_xy_path = slice_xy_path(xy_path, ensure_points_at_distances);
|
||||
result.reserve(extended_xy_path.size());
|
||||
|
||||
for (const DistancedPoint& point : extended_xy_path) {
|
||||
result.emplace_back(point.point.x(), point.point.y(), scaled(initial_elevation + elevation(point.distance_from_start)));
|
||||
}
|
||||
|
||||
// Re-allow avoid_crossing_perimeters for the next travel moves
|
||||
m_avoid_crossing_perimeters.reset_once_modifiers();
|
||||
return result;
|
||||
}
|
||||
|
||||
AABBTreeLines::LinesDistancer<Linef> get_expolygons_distancer(const ExPolygons& polygons) {
|
||||
std::vector<Linef> lines;
|
||||
for (const ExPolygon& polygon : polygons) {
|
||||
for (const Line& line : polygon.lines()) {
|
||||
lines.emplace_back(unscaled(line.a), unscaled(line.b));
|
||||
}
|
||||
}
|
||||
|
||||
return AABBTreeLines::LinesDistancer{std::move(lines)};
|
||||
}
|
||||
|
||||
std::optional<double> get_first_crossed_line_distance(
|
||||
tcb::span<const Line> xy_path,
|
||||
const AABBTreeLines::LinesDistancer<Linef>& distancer
|
||||
) {
|
||||
assert(!xy_path.empty());
|
||||
if (xy_path.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
double traversed_distance = 0;
|
||||
for (const Line& line : xy_path) {
|
||||
const Linef unscaled_line = {unscaled(line.a), unscaled(line.b)};
|
||||
auto intersections = distancer.intersections_with_line<true>(unscaled_line);
|
||||
if (!intersections.empty()) {
|
||||
const Vec2d intersection = intersections.front().first;
|
||||
const double distance = traversed_distance + (unscaled_line.a - intersection).norm();
|
||||
if (distance > EPSILON) {
|
||||
return distance;
|
||||
} else if (intersections.size() >= 2) { // Edge case
|
||||
const Vec2d second_intersection = intersections[1].first;
|
||||
return traversed_distance + (unscaled_line.a - second_intersection).norm();
|
||||
}
|
||||
}
|
||||
traversed_distance += (unscaled_line.a - unscaled_line.b).norm();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ElevatedTravelParams get_elevated_traval_params(
|
||||
const FullPrintConfig& config,
|
||||
const unsigned extruder_id
|
||||
)
|
||||
{
|
||||
ElevatedTravelParams elevation_params{};
|
||||
if (!config.travel_ramping_lift.get_at(extruder_id)) {
|
||||
elevation_params.slope_end = 0;
|
||||
elevation_params.lift_height = config.retract_lift.get_at(extruder_id);
|
||||
return elevation_params;
|
||||
}
|
||||
elevation_params.lift_height = config.travel_max_lift.get_at(extruder_id);
|
||||
|
||||
const double slope_deg = config.travel_slope.get_at(extruder_id);
|
||||
|
||||
if (slope_deg >= 90 || slope_deg <= 0) {
|
||||
elevation_params.slope_end = 0;
|
||||
} else {
|
||||
const double slope_rad = slope_deg * (M_PI / 180); // rad
|
||||
elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad);
|
||||
}
|
||||
|
||||
return elevation_params;
|
||||
}
|
||||
|
||||
Points3 generate_travel_to_extrusion(
|
||||
const Polyline& xy_path,
|
||||
const FullPrintConfig& config,
|
||||
const unsigned extruder_id,
|
||||
const double initial_elevation
|
||||
) {
|
||||
const double upper_limit = config.retract_lift_below.get_at(extruder_id);
|
||||
const double lower_limit = config.retract_lift_above.get_at(extruder_id);
|
||||
if (
|
||||
(lower_limit > 0 && initial_elevation < lower_limit)
|
||||
|| (upper_limit > 0 && initial_elevation > upper_limit)
|
||||
) {
|
||||
return generate_flat_travel(xy_path.points, initial_elevation);
|
||||
}
|
||||
|
||||
ElevatedTravelParams elevation_params{get_elevated_traval_params(
|
||||
config,
|
||||
extruder_id
|
||||
)};
|
||||
|
||||
const std::vector<double> ensure_points_at_distances{elevation_params.slope_end};
|
||||
|
||||
Points3 result{generate_elevated_travel(
|
||||
xy_path.points,
|
||||
ensure_points_at_distances,
|
||||
initial_elevation,
|
||||
ElevatedTravelFormula{elevation_params}
|
||||
)};
|
||||
|
||||
result.emplace_back(xy_path.back().x(), xy_path.back().y(), scaled(initial_elevation));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
std::string GCodeGenerator::generate_travel_gcode(
|
||||
const Points3& travel,
|
||||
const std::string& comment
|
||||
) {
|
||||
std::string gcode;
|
||||
|
||||
const unsigned acceleration =(unsigned)(m_config.travel_acceleration.value + 0.5);
|
||||
|
||||
if (travel.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// generate G-code for the travel move
|
||||
std::string gcode;
|
||||
if (needs_retraction) {
|
||||
if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled)
|
||||
m_wipe.reset_path();
|
||||
|
||||
Point last_post_before_retract = this->last_pos();
|
||||
gcode += this->retract();
|
||||
// When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters.
|
||||
// Because of it, it is necessary to call avoid crossing perimeters again with new starting point after calling retraction()
|
||||
// FIXME Lukas H.: Try to predict if this second calling of avoid crossing perimeters will be needed or not. It could save computations.
|
||||
if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters) {
|
||||
// If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call.
|
||||
if (used_external_mp_once)
|
||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
travel = m_avoid_crossing_perimeters.travel_to(*this, point);
|
||||
// If state of use_external_mp_once was changed reset it to right value.
|
||||
if (used_external_mp_once)
|
||||
m_avoid_crossing_perimeters.reset_once_modifiers();
|
||||
}
|
||||
} else
|
||||
// Reset the wipe path when traveling, so one would not wipe along an old path.
|
||||
m_wipe.reset_path();
|
||||
|
||||
// use G1 because we rely on paths being straight (G0 may make round paths)
|
||||
if (travel.size() >= 2) {
|
||||
gcode += this->m_writer.set_travel_acceleration(acceleration);
|
||||
|
||||
gcode += m_writer.set_travel_acceleration((unsigned int)(m_config.travel_acceleration.value + 0.5));
|
||||
|
||||
for (size_t i = 1; i < travel.size(); ++ i)
|
||||
gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment);
|
||||
|
||||
if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) {
|
||||
// In case that this flavor does not support separate print and travel acceleration,
|
||||
// reset acceleration to default.
|
||||
gcode += m_writer.set_travel_acceleration((unsigned int)(m_config.travel_acceleration.value + 0.5));
|
||||
}
|
||||
|
||||
this->set_last_pos(travel.points.back());
|
||||
for (const Vec3crd& point : travel) {
|
||||
gcode += this->m_writer.travel_to_xyz(to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())), comment);
|
||||
this->set_last_pos(point.head<2>());
|
||||
}
|
||||
|
||||
if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) {
|
||||
// In case that this flavor does not support separate print and travel acceleration,
|
||||
// reset acceleration to default.
|
||||
gcode += this->m_writer.set_travel_acceleration(acceleration);
|
||||
}
|
||||
|
||||
return gcode;
|
||||
}
|
||||
|
||||
@ -3249,7 +3388,104 @@ bool GCodeGenerator::needs_retraction(const Polyline &travel, ExtrusionRole role
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GCodeGenerator::retract(bool toolchange)
|
||||
Polyline GCodeGenerator::generate_travel_xy_path(
|
||||
const Point& start_point,
|
||||
const Point& end_point,
|
||||
const bool needs_retraction,
|
||||
bool& could_be_wipe_disabled
|
||||
) {
|
||||
|
||||
const Point scaled_origin{scaled(this->origin())};
|
||||
const bool avoid_crossing_perimeters = (
|
||||
this->m_config.avoid_crossing_perimeters
|
||||
&& !this->m_avoid_crossing_perimeters.disabled_once()
|
||||
);
|
||||
|
||||
Polyline xy_path{start_point, end_point};
|
||||
if (m_config.avoid_crossing_curled_overhangs) {
|
||||
if (avoid_crossing_perimeters) {
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Option >avoid crossing curled overhangs< is not compatible with avoid crossing perimeters and it will be ignored!";
|
||||
} else {
|
||||
xy_path = this->m_avoid_crossing_curled_overhangs.find_path(
|
||||
start_point + scaled_origin,
|
||||
end_point + scaled_origin
|
||||
);
|
||||
xy_path.translate(-scaled_origin);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
|
||||
// multi-hop travel path inside the configuration space
|
||||
if (
|
||||
needs_retraction
|
||||
&& avoid_crossing_perimeters
|
||||
) {
|
||||
xy_path = this->m_avoid_crossing_perimeters.travel_to(*this, end_point, &could_be_wipe_disabled);
|
||||
}
|
||||
|
||||
return xy_path;
|
||||
}
|
||||
|
||||
// This method accepts &point in print coordinates.
|
||||
std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, std::string comment)
|
||||
{
|
||||
|
||||
const Point start_point = this->last_pos();
|
||||
|
||||
using namespace GCode::Impl;
|
||||
|
||||
// check whether a straight travel move would need retraction
|
||||
|
||||
bool could_be_wipe_disabled {false};
|
||||
bool needs_retraction = this->needs_retraction(Polyline{start_point, point}, role);
|
||||
|
||||
Polyline xy_path{generate_travel_xy_path(
|
||||
start_point, point, needs_retraction, could_be_wipe_disabled
|
||||
)};
|
||||
|
||||
needs_retraction = this->needs_retraction(xy_path, role);
|
||||
|
||||
std::string wipe_retract_gcode{};
|
||||
if (needs_retraction) {
|
||||
if (could_be_wipe_disabled) {
|
||||
m_wipe.reset_path();
|
||||
}
|
||||
|
||||
Point position_before_wipe{this->last_pos()};
|
||||
wipe_retract_gcode = this->retract_and_wipe();
|
||||
|
||||
if (this->last_pos() != position_before_wipe) {
|
||||
xy_path = generate_travel_xy_path(
|
||||
this->last_pos(), point, needs_retraction, could_be_wipe_disabled
|
||||
);
|
||||
}
|
||||
} else {
|
||||
m_wipe.reset_path();
|
||||
}
|
||||
|
||||
this->m_avoid_crossing_perimeters.reset_once_modifiers();
|
||||
|
||||
const unsigned extruder_id = this->m_writer.extruder()->id();
|
||||
const double retract_length = this->m_config.retract_length.get_at(extruder_id);
|
||||
bool can_be_flat{!needs_retraction || retract_length == 0};
|
||||
const double initial_elevation = this->m_last_layer_z + this->m_config.z_offset.value;
|
||||
const Points3 travel = (
|
||||
can_be_flat ?
|
||||
generate_flat_travel(xy_path.points, initial_elevation) :
|
||||
GCode::Impl::generate_travel_to_extrusion(
|
||||
xy_path,
|
||||
this->m_config,
|
||||
extruder_id,
|
||||
initial_elevation
|
||||
)
|
||||
);
|
||||
|
||||
return wipe_retract_gcode + generate_travel_gcode(travel, comment);
|
||||
}
|
||||
|
||||
std::string GCodeGenerator::retract_and_wipe(bool toolchange)
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
@ -3267,10 +3503,7 @@ std::string GCodeGenerator::retract(bool toolchange)
|
||||
methods even if we performed wipe, since this will ensure the entire retraction
|
||||
length is honored in case wipe path was too short. */
|
||||
gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract();
|
||||
|
||||
gcode += m_writer.reset_e();
|
||||
if (m_writer.extruder()->retract_length() > 0 || m_config.use_firmware_retraction)
|
||||
gcode += m_writer.lift();
|
||||
|
||||
return gcode;
|
||||
}
|
||||
@ -3302,7 +3535,7 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_
|
||||
}
|
||||
|
||||
// prepend retraction on the current extruder
|
||||
std::string gcode = this->retract(true);
|
||||
std::string gcode = this->retract_and_wipe(true);
|
||||
|
||||
// Always reset the extrusion path, even if the tool change retract is set to zero.
|
||||
m_wipe.reset_path();
|
||||
@ -3378,6 +3611,9 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_
|
||||
if (m_ooze_prevention.enable)
|
||||
gcode += m_ooze_prevention.post_toolchange(*this);
|
||||
|
||||
// The position is now known after the tool change.
|
||||
this->m_last_pos_defined = false;
|
||||
|
||||
return gcode;
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "GCode/GCodeProcessor.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#include "tcbspan/span.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
@ -88,6 +89,53 @@ struct LayerResult {
|
||||
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
|
||||
};
|
||||
|
||||
namespace GCode::Impl {
|
||||
struct DistancedPoint {
|
||||
Point point;
|
||||
double distance_from_start;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Takes a path described as a list of points and adds points to it.
|
||||
*
|
||||
* @param xy_path A list of points describing a path in xy.
|
||||
* @param sorted_distances A sorted list of distances along the path.
|
||||
* @return Sliced path.
|
||||
*
|
||||
* The algorithm travels along the path segments and adds points to
|
||||
* the segments in such a way that the points have specified distances
|
||||
* from the xy_path start. **Any distances over the xy_path end will
|
||||
* be simply ignored.**
|
||||
*
|
||||
* Example usage - simplified for clarity:
|
||||
* @code
|
||||
* std::vector<double> distances{0.5, 1.5};
|
||||
* std::vector<Points> xy_path{{0, 0}, {1, 0}};
|
||||
* // produces
|
||||
* {{0, 0}, {0, 0.5}, {1, 0}}
|
||||
* // notice that 1.5 is omitted
|
||||
* @endcode
|
||||
*/
|
||||
std::vector<DistancedPoint> slice_xy_path(tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances);
|
||||
|
||||
/**
|
||||
* @brief Take xy_path and genrate a travel acording to elevation.
|
||||
*
|
||||
* @param xy_path A list of points describing a path in xy.
|
||||
* @param ensure_points_at_distances See slice_xy_path sorted_distances.
|
||||
* @param elevation A function taking current distance in mm as input and returning elevation in mm as output.
|
||||
*
|
||||
* **Be aweare** that the elevation function operates in mm, while xy_path and returned travel are in
|
||||
* scaled coordinates.
|
||||
*/
|
||||
Points3 generate_elevated_travel(
|
||||
const tcb::span<const Point> xy_path,
|
||||
const std::vector<double>& ensure_points_at_distances,
|
||||
const double initial_elevation,
|
||||
const std::function<double(double)>& elevation
|
||||
);
|
||||
|
||||
}
|
||||
class GCodeGenerator {
|
||||
|
||||
public:
|
||||
@ -303,11 +351,21 @@ private:
|
||||
const bool print_wipe_extrusions);
|
||||
|
||||
std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache);
|
||||
|
||||
std::string generate_travel_gcode(
|
||||
const Points3& travel,
|
||||
const std::string& comment
|
||||
);
|
||||
Polyline generate_travel_xy_path(
|
||||
const Point& start,
|
||||
const Point& end,
|
||||
const bool needs_retraction,
|
||||
bool& could_be_wipe_disabled
|
||||
);
|
||||
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
|
||||
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
|
||||
std::string retract(bool toolchange = false);
|
||||
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); }
|
||||
|
||||
std::string retract_and_wipe(bool toolchange = false);
|
||||
std::string unretract() { return m_writer.unretract(); }
|
||||
std::string set_extruder(unsigned int extruder_id, double print_z);
|
||||
|
||||
// Cache for custom seam enforcers/blockers for each layer.
|
||||
@ -336,8 +394,8 @@ private:
|
||||
// Input/output from/to custom G-code block, for returning position, retraction etc.
|
||||
DynamicConfig output_config;
|
||||
ConfigOptionFloats *opt_position { nullptr };
|
||||
ConfigOptionFloat *opt_zhop { nullptr };
|
||||
ConfigOptionFloats *opt_e_position { nullptr };
|
||||
ConfigOptionFloat *opt_zhop { nullptr };
|
||||
ConfigOptionFloats *opt_e_retracted { nullptr };
|
||||
ConfigOptionFloats *opt_e_restart_extra { nullptr };
|
||||
ConfigOptionFloats *opt_extruded_volume { nullptr };
|
||||
|
@ -742,7 +742,7 @@ static bool need_wipe(const GCodeGenerator &gcodegen,
|
||||
const Polyline &result_travel,
|
||||
const size_t intersection_count)
|
||||
{
|
||||
bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.;
|
||||
bool z_lift_enabled = gcodegen.config().travel_max_lift.get_at(gcodegen.writer().extruder()->id()) > 0.;
|
||||
bool wipe_needed = false;
|
||||
|
||||
// If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <map>
|
||||
#include <assert.h>
|
||||
#include <string_view>
|
||||
#include <boost/math/special_functions/pow.hpp>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <boost/spirit/include/karma.hpp>
|
||||
@ -277,7 +278,7 @@ std::string GCodeWriter::set_speed(double F, const std::string_view comment, con
|
||||
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment)
|
||||
{
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xy(point);
|
||||
w.emit_f(this->config.travel_speed.value * 60.0);
|
||||
@ -304,64 +305,37 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij
|
||||
|
||||
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment)
|
||||
{
|
||||
// FIXME: This function was not being used when travel_speed_z was separated (bd6badf).
|
||||
// Calculation of feedrate was not updated accordingly. If you want to use
|
||||
// this function, fix it first.
|
||||
std::terminate();
|
||||
if (std::abs(point.x() - m_pos.x()) < EPSILON && std::abs(point.y() - m_pos.y()) < EPSILON) {
|
||||
return this->travel_to_z(point.z(), comment);
|
||||
} else if (std::abs(point.z() - m_pos.z()) < EPSILON) {
|
||||
return this->travel_to_xy(point.head<2>(), comment);
|
||||
} else {
|
||||
m_pos = point;
|
||||
|
||||
/* If target Z is lower than current Z but higher than nominal Z we
|
||||
don't perform the Z move but we only move in the XY plane and
|
||||
adjust the nominal Z by reducing the lift amount that will be
|
||||
used for unlift. */
|
||||
if (!this->will_move_z(point.z())) {
|
||||
double nominal_z = m_pos.z() - m_lifted;
|
||||
m_lifted -= (point.z() - nominal_z);
|
||||
// In case that retract_lift == layer_height we could end up with almost zero in_m_lifted
|
||||
// and a retract could be skipped (https://github.com/prusa3d/PrusaSlicer/issues/2154
|
||||
if (std::abs(m_lifted) < EPSILON)
|
||||
m_lifted = 0.;
|
||||
return this->travel_to_xy(to_2d(point));
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point);
|
||||
|
||||
Vec2f speed {this->config.travel_speed_z.value, this->config.travel_speed.value};
|
||||
w.emit_f(speed.norm() * 60.0);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
/* In all the other cases, we perform an actual XYZ move and cancel
|
||||
the lift. */
|
||||
m_lifted = 0;
|
||||
m_pos = point;
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point);
|
||||
w.emit_f(this->config.travel_speed.value * 60.0);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
|
||||
std::string GCodeWriter::travel_to_z(double z, const std::string_view comment)
|
||||
{
|
||||
/* If target Z is lower than current Z but higher than nominal Z
|
||||
we don't perform the move but we only adjust the nominal Z by
|
||||
reducing the lift amount that will be used for unlift. */
|
||||
if (!this->will_move_z(z)) {
|
||||
double nominal_z = m_pos.z() - m_lifted;
|
||||
m_lifted -= (z - nominal_z);
|
||||
if (std::abs(m_lifted) < EPSILON)
|
||||
m_lifted = 0.;
|
||||
return {};
|
||||
}
|
||||
|
||||
/* In all the other cases, we perform an actual Z move and cancel
|
||||
the lift. */
|
||||
m_lifted = 0;
|
||||
return this->_travel_to_z(z, comment);
|
||||
return std::abs(m_pos.z() - z) < EPSILON ? "" : this->get_travel_to_z_gcode(z, comment);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::_travel_to_z(double z, const std::string_view comment)
|
||||
std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment)
|
||||
{
|
||||
m_pos.z() = z;
|
||||
|
||||
double speed = this->config.travel_speed_z.value;
|
||||
if (speed == 0.)
|
||||
speed = this->config.travel_speed.value;
|
||||
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_z(z);
|
||||
w.emit_f(speed * 60.0);
|
||||
@ -369,18 +343,6 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string_view comment)
|
||||
return w.string();
|
||||
}
|
||||
|
||||
bool GCodeWriter::will_move_z(double z) const
|
||||
{
|
||||
/* If target Z is lower than current Z but higher than nominal Z
|
||||
we don't perform an actual Z move. */
|
||||
if (m_lifted > 0) {
|
||||
double nominal_z = m_pos.z() - m_lifted;
|
||||
if (z >= nominal_z && z <= m_pos.z())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment)
|
||||
{
|
||||
assert(dE != 0);
|
||||
@ -514,47 +476,8 @@ std::string GCodeWriter::unretract()
|
||||
return gcode;
|
||||
}
|
||||
|
||||
/* If this method is called more than once before calling unlift(),
|
||||
it will not perform subsequent lifts, even if Z was raised manually
|
||||
(i.e. with travel_to_z()) and thus _lifted was reduced. */
|
||||
std::string GCodeWriter::lift()
|
||||
{
|
||||
// check whether the above/below conditions are met
|
||||
double target_lift = 0;
|
||||
{
|
||||
double above = this->config.retract_lift_above.get_at(m_extruder->id());
|
||||
double below = this->config.retract_lift_below.get_at(m_extruder->id());
|
||||
if (m_pos.z() >= above && (below == 0 || m_pos.z() <= below))
|
||||
target_lift = this->config.retract_lift.get_at(m_extruder->id());
|
||||
}
|
||||
if (m_lifted == 0 && target_lift > 0) {
|
||||
m_lifted = target_lift;
|
||||
return this->_travel_to_z(m_pos.z() + target_lift, "lift Z");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string GCodeWriter::unlift()
|
||||
{
|
||||
std::string gcode;
|
||||
if (m_lifted > 0) {
|
||||
gcode += this->_travel_to_z(m_pos.z() - m_lifted, "restore layer Z");
|
||||
m_lifted = 0;
|
||||
}
|
||||
return gcode;
|
||||
}
|
||||
|
||||
void GCodeWriter::update_position(const Vec3d &new_pos)
|
||||
{
|
||||
assert(this->m_lifted >= 0);
|
||||
const double nominal_z = m_pos.z() - m_lifted;
|
||||
m_lifted = new_pos.z() - nominal_z;
|
||||
if (m_lifted < - EPSILON)
|
||||
throw Slic3r::RuntimeError("Custom G-code reports negative Z-hop. Final Z position is below the print_z height.");
|
||||
// In case that retract_lift == layer_height we could end up with almost zero in_m_lifted
|
||||
// and a retract could be skipped (https://github.com/prusa3d/PrusaSlicer/issues/2154
|
||||
if (m_lifted < EPSILON)
|
||||
m_lifted = 0.;
|
||||
m_pos = new_pos;
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,7 @@ public:
|
||||
multiple_extruders(false), m_extrusion_axis("E"), m_extruder(nullptr),
|
||||
m_single_extruder_multi_material(false),
|
||||
m_last_acceleration(0), m_max_acceleration(0),
|
||||
m_last_bed_temperature(0), m_last_bed_temperature_reached(true),
|
||||
m_lifted(0)
|
||||
m_last_bed_temperature(0), m_last_bed_temperature_reached(true)
|
||||
{}
|
||||
Extruder* extruder() { return m_extruder; }
|
||||
const Extruder* extruder() const { return m_extruder; }
|
||||
@ -70,23 +69,21 @@ public:
|
||||
std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {});
|
||||
std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {});
|
||||
std::string travel_to_xyz(const Vec3d &point, const std::string_view comment = {});
|
||||
std::string get_travel_to_z_gcode(double z, const std::string_view comment);
|
||||
std::string travel_to_z(double z, const std::string_view comment = {});
|
||||
bool will_move_z(double z) const;
|
||||
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {});
|
||||
std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment);
|
||||
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {});
|
||||
std::string retract(bool before_wipe = false);
|
||||
std::string retract_for_toolchange(bool before_wipe = false);
|
||||
std::string unretract();
|
||||
std::string lift();
|
||||
std::string unlift();
|
||||
|
||||
// Current position of the printer, in G-code coordinates.
|
||||
// Z coordinate of current position contains zhop. If zhop is applied (this->zhop() > 0),
|
||||
// then the print_z = this->get_position().z() - this->zhop().
|
||||
Vec3d get_position() const { return m_pos; }
|
||||
// Current Z hop value.
|
||||
double get_zhop() const { return m_lifted; }
|
||||
// Zhop value is obsolete. This is for backwards compability.
|
||||
double get_zhop() const { return 0; }
|
||||
// Update position of the print head based on the final position returned by a custom G-code block.
|
||||
// The new position Z coordinate contains the Z-hop.
|
||||
// GCodeWriter expects the custom script to NOT change print_z, only Z-hop, thus the print_z is maintained
|
||||
@ -117,7 +114,6 @@ private:
|
||||
|
||||
unsigned int m_last_bed_temperature;
|
||||
bool m_last_bed_temperature_reached;
|
||||
double m_lifted;
|
||||
Vec3d m_pos = Vec3d::Zero();
|
||||
|
||||
enum class Acceleration {
|
||||
@ -125,7 +121,6 @@ private:
|
||||
Print
|
||||
};
|
||||
|
||||
std::string _travel_to_z(double z, const std::string_view comment);
|
||||
std::string _retract(double length, double restart_extra, const std::string_view comment);
|
||||
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);
|
||||
};
|
||||
|
@ -537,6 +537,7 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
|
||||
m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_travel_speed(config.travel_speed),
|
||||
m_travel_speed_z(config.travel_speed_z),
|
||||
m_infill_speed(default_region_config.infill_speed),
|
||||
m_perimeter_speed(default_region_config.perimeter_speed),
|
||||
m_current_tool(initial_tool),
|
||||
@ -1018,7 +1019,11 @@ void WipeTower::toolchange_Change(
|
||||
writer.feedrate(m_travel_speed * 60.f) // see https://github.com/prusa3d/PrusaSlicer/issues/5483
|
||||
.append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x())
|
||||
+ " Y" + Slic3r::float_to_string_decimal_point(current_pos.y())
|
||||
+ never_skip_tag() + "\n");
|
||||
+ never_skip_tag() + "\n"
|
||||
);
|
||||
writer.feedrate(m_travel_speed_z * 60.f)
|
||||
.append("G1 Z" + Slic3r::float_to_string_decimal_point(this->m_z_pos) + "\n");
|
||||
|
||||
writer.append("[deretraction_from_wipe_tower_generator]");
|
||||
|
||||
// The toolchange Tn command will be inserted later, only in case that the user does
|
||||
|
@ -279,6 +279,7 @@ private:
|
||||
size_t m_max_color_changes = 0; // Maximum number of color changes per layer.
|
||||
int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary)
|
||||
float m_travel_speed = 0.f;
|
||||
float m_travel_speed_z = 0.f;
|
||||
float m_infill_speed = 0.f;
|
||||
float m_perimeter_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
|
@ -41,9 +41,9 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
|
||||
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
|
||||
|
||||
gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't).
|
||||
|
||||
double current_z = gcodegen.writer().get_position().z();
|
||||
gcode += gcodegen.writer().travel_to_z(current_z);
|
||||
|
||||
if (z == -1.) // in case no specific z was provided, print at current_z pos
|
||||
z = current_z;
|
||||
|
||||
@ -57,7 +57,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
|| is_ramming
|
||||
|| will_go_down); // don't dig into the print
|
||||
if (should_travel_to_tower) {
|
||||
gcode += gcodegen.retract();
|
||||
gcode += gcodegen.retract_and_wipe();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcode += gcodegen.travel_to(
|
||||
wipe_tower_point_to_object_point(gcodegen, start_pos),
|
||||
@ -150,12 +150,15 @@ std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower:
|
||||
}
|
||||
std::ostringstream line_out;
|
||||
std::istringstream line_str(line);
|
||||
std::optional<float> z{};
|
||||
line_str >> std::noskipws; // don't skip whitespace
|
||||
char ch = 0;
|
||||
line_str >> ch >> ch; // read the "G1"
|
||||
while (line_str >> ch) {
|
||||
if (ch == 'X' || ch == 'Y')
|
||||
line_str >> (ch == 'X' ? pos.x() : pos.y());
|
||||
else if (ch == 'Z')
|
||||
line_str >> *z;
|
||||
else
|
||||
line_out << ch;
|
||||
}
|
||||
@ -171,6 +174,8 @@ std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower:
|
||||
oss << " X" << transformed_pos.x() - extruder_offset.x();
|
||||
if (transformed_pos.y() != old_pos.y() || never_skip)
|
||||
oss << " Y" << transformed_pos.y() - extruder_offset.y();
|
||||
if (z)
|
||||
oss << " Z" << *z;
|
||||
if (! line.empty())
|
||||
oss << " ";
|
||||
line = oss.str() + line;
|
||||
@ -243,7 +248,10 @@ std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
|
||||
gcode += gcodegen.change_layer(m_final_purge.print_z);
|
||||
gcode += gcodegen.generate_travel_gcode(
|
||||
{{gcodegen.last_pos().x(), gcodegen.last_pos().y(), scaled(m_final_purge.print_z)}},
|
||||
"move to safe place for purging"
|
||||
);
|
||||
gcode += append_tcr(gcodegen, m_final_purge, -1);
|
||||
return gcode;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ use Slic3r::Test qw(_eq);
|
||||
|
||||
my $tool = 0;
|
||||
my @toolchange_count = (); # track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time
|
||||
my @retracted = (1); # ignore the first travel move from home to first point
|
||||
my @retracted = (0);
|
||||
my @retracted_length = (0);
|
||||
my $lifted = 0;
|
||||
my $lift_dist = 0; # track lifted distance for toolchanges and extruders with different retract_lift values
|
||||
@ -91,7 +91,7 @@ use Slic3r::Test qw(_eq);
|
||||
fail 'retracted before long travel move' if !$retracted[$tool];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
1;
|
||||
};
|
||||
|
||||
@ -155,7 +155,7 @@ use Slic3r::Test qw(_eq);
|
||||
if ($info->{dist_Z} && $retracted) {
|
||||
$layer_changes_with_retraction++;
|
||||
}
|
||||
if ($info->{dist_Z} && $args->{Z} < $self->Z) {
|
||||
if ($info->{dist_Z} && $args->{Z} < $self->{Z}) {
|
||||
$z_restores++;
|
||||
}
|
||||
});
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "libslic3r/GCode.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GCode::Impl;
|
||||
|
||||
SCENARIO("Origin manipulation", "[GCode]") {
|
||||
Slic3r::GCodeGenerator gcodegen;
|
||||
@ -20,3 +21,121 @@ SCENARIO("Origin manipulation", "[GCode]") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ApproxEqualsPoints : public Catch::MatcherBase<Points> {
|
||||
ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {}
|
||||
bool match(const Points& points) const override {
|
||||
if (points.size() != expected.size()) {
|
||||
return false;
|
||||
}
|
||||
for (auto i = 0u; i < points.size(); ++i) {
|
||||
const Point& point = points[i];
|
||||
const Point& expected_point = this->expected[i];
|
||||
if (
|
||||
std::abs(point.x() - expected_point.x()) > this->tolerance
|
||||
|| std::abs(point.y() - expected_point.y()) > this->tolerance
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
std::string describe() const override {
|
||||
std::stringstream ss;
|
||||
ss << std::endl;
|
||||
for (const Point& point : expected) {
|
||||
ss << "(" << point.x() << ", " << point.y() << ")" << std::endl;
|
||||
}
|
||||
ss << "With tolerance: " << this->tolerance;
|
||||
|
||||
return "Equals " + ss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
Points expected;
|
||||
unsigned tolerance;
|
||||
};
|
||||
|
||||
Points get_points(const std::vector<DistancedPoint>& result) {
|
||||
Points result_points;
|
||||
std::transform(
|
||||
result.begin(),
|
||||
result.end(),
|
||||
std::back_inserter(result_points),
|
||||
[](const DistancedPoint& point){
|
||||
return point.point;
|
||||
}
|
||||
);
|
||||
return result_points;
|
||||
}
|
||||
|
||||
std::vector<double> get_distances(const std::vector<DistancedPoint>& result) {
|
||||
std::vector<double> result_distances;
|
||||
std::transform(
|
||||
result.begin(),
|
||||
result.end(),
|
||||
std::back_inserter(result_distances),
|
||||
[](const DistancedPoint& point){
|
||||
return point.distance_from_start;
|
||||
}
|
||||
);
|
||||
return result_distances;
|
||||
}
|
||||
|
||||
TEST_CASE("Place points at distances - expected use", "[GCode]") {
|
||||
std::vector<Point> line{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{2, 1}),
|
||||
scaled(Vec2f{2, 2})
|
||||
};
|
||||
std::vector<double> distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0};
|
||||
std::vector<DistancedPoint> result = slice_xy_path(line, distances);
|
||||
|
||||
REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{0.2, 0}),
|
||||
scaled(Vec2f{0.5, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{1.5, 0.5}),
|
||||
scaled(Vec2f{2, 1}),
|
||||
scaled(Vec2f{2, 1.5}),
|
||||
scaled(Vec2f{2, 2})
|
||||
}, 5));
|
||||
|
||||
REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector<double>{
|
||||
distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2)
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_CASE("Place points at distances - edge case", "[GCode]") {
|
||||
std::vector<Point> line{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{2, 0})
|
||||
};
|
||||
std::vector<double> distances{0, 1, 1.5, 2};
|
||||
Points result{get_points(slice_xy_path(line, distances))};
|
||||
CHECK(result == Points{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{1.5, 0}),
|
||||
scaled(Vec2f{2, 0})
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("Generate elevated travel", "[GCode]") {
|
||||
std::vector<Point> xy_path{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
};
|
||||
std::vector<double> ensure_points_at_distances{0.2, 0.5};
|
||||
Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})};
|
||||
|
||||
CHECK(result == Points3{
|
||||
scaled(Vec3f{0, 0, 3.0}),
|
||||
scaled(Vec3f{0.2, 0, 3.2}),
|
||||
scaled(Vec3f{0.5, 0, 3.5}),
|
||||
scaled(Vec3f{1, 0, 4.0})
|
||||
});
|
||||
}
|
||||
|
@ -6,68 +6,6 @@
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWriter]") {
|
||||
GIVEN("A config from a file and a single extruder.") {
|
||||
GCodeWriter writer;
|
||||
GCodeConfig &config = writer.config;
|
||||
config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini", ForwardCompatibilitySubstitutionRule::Disable);
|
||||
|
||||
std::vector<unsigned int> extruder_ids {0};
|
||||
writer.set_extruders(extruder_ids);
|
||||
writer.set_extruder(0);
|
||||
|
||||
WHEN("Z is set to 203") {
|
||||
double trouble_Z = 203;
|
||||
writer.travel_to_z(trouble_Z);
|
||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WHEN("Z is set to 500003") {
|
||||
double trouble_Z = 500003;
|
||||
writer.travel_to_z(trouble_Z);
|
||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WHEN("Z is set to 10.3") {
|
||||
double trouble_Z = 10.3;
|
||||
writer.travel_to_z(trouble_Z);
|
||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The test above will fail for trouble_Z == 9007199254740992, where trouble_Z + 1.5 will be rounded to trouble_Z + 2.0 due to double mantisa overflow.
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") {
|
||||
|
||||
GIVEN("GCodeWriter instance") {
|
||||
|
Loading…
x
Reference in New Issue
Block a user