Merge ms_lh_zhop_improvements into master.

* Replace the ramping travel with a smooth ramping travel.
* Replace helical layer changes with ramping layer changes.
* Implement the "lift before obstacle" feature.
This commit is contained in:
Martin Šach 2024-01-17 12:24:00 +01:00
commit 2770b977da
23 changed files with 816 additions and 474 deletions

View File

@ -193,8 +193,6 @@ set(SLIC3R_SOURCES
GCode/AvoidCrossingPerimeters.hpp
GCode/Travels.cpp
GCode/Travels.hpp
GCode/LayerChanges.cpp
GCode/LayerChanges.hpp
GCode.cpp
GCode.hpp
GCodeReader.cpp

View File

@ -446,7 +446,7 @@ inline void expolygons_rotate(ExPolygons &expolys, double angle)
expoly.rotate(angle);
}
inline bool expolygons_contain(ExPolygons &expolys, const Point &pt, bool border_result = true)
inline bool expolygons_contain(const ExPolygons &expolys, const Point &pt, bool border_result = true)
{
for (const ExPolygon &expoly : expolys)
if (expoly.contains(pt, border_result))

View File

@ -35,7 +35,6 @@
#include "GCode/WipeTower.hpp"
#include "GCode/WipeTowerIntegration.hpp"
#include "GCode/Travels.hpp"
#include "GCode/LayerChanges.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "PrintConfig.hpp"
@ -1214,7 +1213,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
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_and_wipe());
file.write(this->travel_to(Point(0, 0), ExtrusionRole::None, "move to origin position for next object"));
file.write(this->travel_to(*this->last_position, 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.
m_avoid_crossing_perimeters.disable_once();
@ -1250,7 +1249,12 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
// Prusa Multi-Material wipe tower.
if (has_wipe_tower && ! layers_to_print.empty()) {
m_wipe_tower = std::make_unique<GCode::WipeTowerIntegration>(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get());
file.write(m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
// Set position for wipe tower generation.
Vec3d new_position = this->writer().get_position();
new_position.z() = first_layer_height + m_config.z_offset.value;
this->writer().update_position(new_position);
if (print.config().single_extruder_multi_material_priming) {
file.write(m_wipe_tower->prime(*this));
// Verify, whether the print overaps the priming extrusions.
@ -1637,7 +1641,7 @@ std::string GCodeGenerator::placeholder_parser_process(
if (const std::vector<double> &pos = ppi.opt_position->values; ppi.position != pos) {
// Update G-code writer.
m_writer.update_position({ pos[0], pos[1], pos[2] });
this->set_last_pos(this->gcode_to_point({ pos[0], pos[1] }));
this->last_position = this->gcode_to_point({ pos[0], pos[1] });
}
for (const Extruder &e : m_writer.extruders()) {
@ -2061,24 +2065,48 @@ bool GCodeGenerator::line_distancer_is_required(const std::vector<unsigned int>&
return false;
}
namespace GCode::Impl {
AABBTreeLines::LinesDistancer<Linef> get_previous_layer_distancer(
const GCodeGenerator::ObjectsLayerToPrint& objects_to_print,
const ExPolygons& slices
) {
std::vector<Linef> lines;
for (const GCodeGenerator::ObjectLayerToPrint &object_to_print : objects_to_print) {
for (const PrintInstance& instance: object_to_print.object()->instances()) {
for (const ExPolygon& polygon : slices) {
for (const Line& line : polygon.lines()) {
lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift}));
}
}
std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id) {
const Polyline xy_path{
this->gcode_to_point(from.head<2>()),
this->gcode_to_point(to.head<2>())
};
}
using namespace GCode::Impl::Travels;
ElevatedTravelParams elevation_params{
get_elevated_traval_params(xy_path, this->m_config, extruder_id, this->m_travel_obstacle_tracker)};
const double initial_elevation = from.z();
const double z_change = to.z() - from.z();
elevation_params.lift_height = std::max(z_change, elevation_params.lift_height);
const double path_length = unscaled(xy_path.length());
const double lift_at_travel_end =
(elevation_params.lift_height / elevation_params.slope_end * path_length);
if (lift_at_travel_end < z_change) {
elevation_params.lift_height = z_change;
elevation_params.slope_end = path_length;
}
return AABBTreeLines::LinesDistancer{std::move(lines)};
}
const std::vector<double> ensure_points_at_distances = linspace(
elevation_params.slope_end - elevation_params.blend_width / 2.0,
elevation_params.slope_end + elevation_params.blend_width / 2.0,
elevation_params.parabola_points_count
);
Points3 travel{generate_elevated_travel(
xy_path.points, ensure_points_at_distances, initial_elevation,
ElevatedTravelFormula{elevation_params}
)};
std::string travel_gcode;
Vec3d previous_point{this->point_to_gcode(travel.front())};
for (const Vec3crd& point : tcb::span{travel}.subspan(1)) {
const Vec3d gcode_point{this->point_to_gcode(point)};
travel_gcode += this->m_writer.get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change");
previous_point = gcode_point;
}
return travel_gcode;
}
// In sequential mode, process_layer is called once per each object and its copy,
@ -2150,6 +2178,7 @@ LayerResult GCodeGenerator::process_layer(
m_enable_loop_clipping = !enable;
}
std::string gcode;
assert(is_decimal_separator_point()); // for the sprintfs
@ -2168,9 +2197,10 @@ LayerResult GCodeGenerator::process_layer(
m_last_layer_z = static_cast<float>(print_z);
m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z);
m_last_height = height;
m_current_layer_first_position = std::nullopt;
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
if (! print.config().before_layer_gcode.value.empty()) {
if (!first_layer && ! print.config().before_layer_gcode.value.empty()) {
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
@ -2179,13 +2209,13 @@ LayerResult GCodeGenerator::process_layer(
print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config)
+ "\n";
}
gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable); // this will increase m_layer_index
gcode += this->change_layer(previous_layer_z, print_z); // this will increase m_layer_index
m_layer = &layer;
if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) {
this->m_previous_layer_distancer = GCode::Impl::get_previous_layer_distancer(layers, layer.lower_layer->lslices);
}
if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr)
m_travel_obstacle_tracker.init_layer(layer, layers);
m_object_layer_over_raft = false;
if (! print.config().layer_gcode.value.empty()) {
if (!first_layer && ! print.config().layer_gcode.value.empty()) {
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
@ -2305,6 +2335,32 @@ LayerResult GCodeGenerator::process_layer(
is_anything_overridden, false /* print_wipe_extrusions */);
}
// During layer change the starting position of next layer is now known.
// The solution is thus to emplace a temporary tag to the gcode, cache the postion and
// replace the tag later. The tag is Layer_Change_Travel, the cached position is
// m_current_layer_first_position and it is replaced here.
const std::string tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel);
std::string layer_change_gcode;
const bool do_ramping_layer_change = (
m_previous_layer_last_position
&& m_current_layer_first_position
&& m_layer_change_extruder_id
&& !result.spiral_vase_enable
&& print_z > previous_layer_z
&& EXTRUDER_CONFIG(travel_ramping_lift)
&& EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90
);
if (first_layer) {
layer_change_gcode = ""; // Explicit for readability.
} else if (do_ramping_layer_change) {
layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position, *m_current_layer_first_position, *m_layer_change_extruder_id);
} else {
layer_change_gcode = this->writer().get_travel_to_z_gcode(m_config.z_offset.value + print_z, "simple layer change");
}
boost::algorithm::replace_first(gcode, tag, layer_change_gcode);
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
log_memory_info();
@ -2350,10 +2406,10 @@ void GCodeGenerator::process_layer_single_object(
m_avoid_crossing_perimeters.init_layer(*m_layer);
// When starting a new object, use the external motion planner for the first travel move.
const Point &offset = print_object.instances()[print_instance.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&print_object, offset);
if (m_last_obj_copy != this_object_copy)
GCode::PrintObjectInstance next_instance = {&print_object, int(print_instance.instance_id)};
if (m_current_instance != next_instance)
m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy;
m_current_instance = next_instance;
this->set_origin(unscale(offset));
gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No);
}
@ -2462,9 +2518,10 @@ void GCodeGenerator::process_layer_single_object(
init_layer_delayed();
m_config.apply(region.config());
const auto extrusion_name = ironing ? "ironing"sv : "infill"sv;
for (const ExtrusionEntityReference &fill : chain_extrusion_references(temp_fill_extrusions, &m_last_pos))
const Point* start_near = this->last_position ? &(*(this->last_position)) : nullptr;
for (const ExtrusionEntityReference &fill : chain_extrusion_references(temp_fill_extrusions, start_near))
if (auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(&fill.extrusion_entity()); eec) {
for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, &m_last_pos, fill.flipped()))
for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, start_near, fill.flipped()))
gcode += this->extrude_entity(ee, smooth_path_cache, extrusion_name);
} else
gcode += this->extrude_entity(fill, smooth_path_cache, extrusion_name);
@ -2493,9 +2550,11 @@ void GCodeGenerator::process_layer_single_object(
init_layer_delayed();
m_config.apply(region.config());
}
for (const ExtrusionEntity *ee : *eec)
for (const ExtrusionEntity *ee : *eec) {
// Don't reorder, don't flip.
gcode += this->extrude_entity({ *ee, false }, smooth_path_cache, comment_perimeter, -1.);
gcode += this->extrude_entity({*ee, false}, smooth_path_cache, comment_perimeter, -1.);
m_travel_obstacle_tracker.mark_extruded(ee, print_instance.object_layer_to_print_id, print_instance.instance_id);
}
}
}
};
@ -2586,7 +2645,9 @@ void GCodeGenerator::set_origin(const Vec2d &pointf)
{
// if origin increases (goes towards right), last_pos decreases because it goes towards left
const auto offset = Point::new_scale(m_origin - pointf);
m_last_pos += offset;
if (last_position.has_value())
*(this->last_position) += offset;
m_wipe.offset_path(offset);
m_origin = pointf;
}
@ -2604,63 +2665,10 @@ std::string GCodeGenerator::preamble()
return gcode;
}
std::optional<std::string> GCodeGenerator::get_helical_layer_change_gcode(
const coordf_t previous_layer_z,
const coordf_t print_z,
const std::string& comment
) {
if (!this->last_pos_defined()) {
return std::nullopt;
}
const double circle_radius{2};
const unsigned n_gon_points_count{16};
const Point n_gon_start_point{this->last_pos()};
GCode::Impl::LayerChanges::Bed bed{
this->m_config.bed_shape.values,
circle_radius * 2
};
if (!bed.contains_within_padding(this->point_to_gcode(n_gon_start_point))) {
return std::nullopt;
}
const Vec2crd n_gon_vector{scaled(Vec2d{
(bed.centroid - this->point_to_gcode(n_gon_start_point)).normalized() * circle_radius
})};
const Point n_gon_centeroid{n_gon_start_point + n_gon_vector};
const Polygon n_gon{GCode::Impl::LayerChanges::generate_regular_polygon(
n_gon_centeroid,
n_gon_start_point,
n_gon_points_count
)};
const double n_gon_circumference = unscaled(n_gon.length());
const double z_change{print_z - previous_layer_z};
Points3 helix{GCode::Impl::Travels::generate_elevated_travel(
n_gon.points,
{},
previous_layer_z,
[&](const double distance){
return distance / n_gon_circumference * z_change;
}
)};
helix.emplace_back(to_3d(this->last_pos(), scaled(print_z)));
return this->generate_travel_gcode(helix, comment);
}
// called by GCodeGenerator::process_layer()
std::string GCodeGenerator::change_layer(
coordf_t previous_layer_z,
coordf_t print_z,
const bool spiral_vase_enabled
coordf_t print_z
) {
std::string gcode;
if (m_layer_count > 0)
@ -2670,31 +2678,16 @@ std::string GCodeGenerator::change_layer(
if (EXTRUDER_CONFIG(retract_layer_change))
gcode += this->retract_and_wipe();
const std::string comment{"move to next layer (" + std::to_string(m_layer_index) + ")"};
Vec3d new_position = this->writer().get_position();
new_position.z() = print_z + m_config.z_offset.value;
this->writer().update_position(new_position);
bool do_helical_layer_change{
!spiral_vase_enabled
&& print_z > previous_layer_z
&& EXTRUDER_CONFIG(retract_layer_change)
&& EXTRUDER_CONFIG(retract_length) > 0
&& EXTRUDER_CONFIG(travel_ramping_lift)
&& EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90
};
m_previous_layer_last_position = this->last_position ?
std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} :
std::nullopt;
const std::optional<std::string> helix_gcode{
do_helical_layer_change ?
this->get_helical_layer_change_gcode(
m_config.z_offset.value + previous_layer_z,
m_config.z_offset.value + print_z,
comment
) :
std::nullopt
};
gcode += (
helix_gcode ?
*helix_gcode :
m_writer.travel_to_z(m_config.z_offset.value + print_z, comment)
);
gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel);
this->m_layer_change_extruder_id = m_writer.extruder()->id();
// forget last wiping path as wiping after raising Z is pointless
m_wipe.reset_path();
@ -2720,10 +2713,10 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
{
// Extrude all loops CCW.
bool is_hole = loop_src.is_clockwise();
Point seam_point = this->last_pos();
Point seam_point = *this->last_position;
if (! m_config.spiral_vase && comment_is_perimeter(description)) {
assert(m_layer != nullptr);
seam_point = m_seam_placer.place_seam(m_layer, loop_src, m_config.external_perimeters_first, this->last_pos());
seam_point = m_seam_placer.place_seam(m_layer, loop_src, m_config.external_perimeters_first, *this->last_position);
}
// Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns,
// thus empty path segments will not be produced by G-code export.
@ -2762,7 +2755,7 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
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);
this->last_position = *pt;
}
}
@ -2775,7 +2768,7 @@ std::string GCodeGenerator::extrude_skirt(
{
assert(loop_src.is_counter_clockwise());
GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam(
loop_src, false, m_scaled_resolution, this->last_pos(), scaled<double>(0.0015));
loop_src, false, m_scaled_resolution, *this->last_position, scaled<double>(0.0015));
// Clip the path to avoid the extruder to get exactly on the first point of the loop;
// if polyline was shorter than the clipping distance we'd get a null polyline, so
@ -2963,19 +2956,31 @@ std::string GCodeGenerator::_extrude(
std::string gcode;
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) {
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;
comment += " point";
gcode += this->travel_to(path.front().point, path_attr.role, comment);
if (!m_current_layer_first_position) {
// Make the first travel just one G1.
const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z + this->m_config.z_offset.value));
const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z()));
this->last_position = path.front().point;
this->writer().update_position(gcode_point);
gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), "move to first layer point");
gcode += this->writer().get_travel_to_z_gcode(gcode_point.z(), "move to first layer point");
m_current_layer_first_position = gcode_point;
} else {
// go to first point of extrusion path
if (!this->last_position) {
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 ( this->last_position != path.front().point) {
std::string comment = "move to first ";
comment += description;
comment += description_bridge;
comment += " point";
const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment)};
gcode += travel_gcode;
}
}
// compensate retraction
@ -3197,7 +3202,7 @@ std::string GCodeGenerator::_extrude(
if (dynamic_speed_and_fan_speed.second >= 0)
gcode += ";_RESET_FAN_SPEED\n";
this->set_last_pos(path.back().point);
this->last_position = path.back().point;
return gcode;
}
@ -3217,9 +3222,13 @@ std::string GCodeGenerator::generate_travel_gcode(
// use G1 because we rely on paths being straight (G0 may make round paths)
gcode += this->m_writer.set_travel_acceleration(acceleration);
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>());
Vec3d previous_point{this->point_to_gcode(travel.front())};
for (const Vec3crd& point : tcb::span{travel}.subspan(1)) {
const Vec3d gcode_point{this->point_to_gcode(point)};
gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment);
this->last_position = point.head<2>();
previous_point = gcode_point;
}
if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) {
@ -3311,18 +3320,16 @@ Polyline GCodeGenerator::generate_travel_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();
std::string GCodeGenerator::travel_to(
const Point &start_point, const Point &end_point, ExtrusionRole role, const std::string &comment
) {
// 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);
bool needs_retraction = this->needs_retraction(Polyline{start_point, end_point}, role);
Polyline xy_path{generate_travel_xy_path(
start_point, point, needs_retraction, could_be_wipe_disabled
start_point, end_point, needs_retraction, could_be_wipe_disabled
)};
needs_retraction = this->needs_retraction(xy_path, role);
@ -3333,12 +3340,12 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st
m_wipe.reset_path();
}
Point position_before_wipe{this->last_pos()};
Point position_before_wipe{*this->last_position};
wipe_retract_gcode = this->retract_and_wipe();
if (this->last_pos() != position_before_wipe) {
if (*this->last_position != position_before_wipe) {
xy_path = generate_travel_xy_path(
this->last_pos(), point, needs_retraction, could_be_wipe_disabled
*this->last_position, end_point, needs_retraction, could_be_wipe_disabled
);
}
} else {
@ -3351,15 +3358,23 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st
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 double upper_limit = this->m_config.retract_lift_below.get_at(extruder_id);
const double lower_limit = this->m_config.retract_lift_above.get_at(extruder_id);
if ((lower_limit > 0 && initial_elevation < lower_limit) ||
(upper_limit > 0 && initial_elevation > upper_limit)) {
can_be_flat = true;
}
const Points3 travel = (
can_be_flat ?
GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) :
GCode::Impl::Travels::generate_travel_to_extrusion(
xy_path,
this->m_config,
m_config,
extruder_id,
initial_elevation,
this->m_previous_layer_distancer,
m_travel_obstacle_tracker,
scaled(m_origin)
)
);
@ -3494,7 +3509,7 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_
gcode += m_ooze_prevention.post_toolchange(*this);
// The position is now known after the tool change.
this->m_last_pos_defined = false;
this->last_position = std::nullopt;
return gcode;
}

View File

@ -38,8 +38,9 @@
#include "GCode/WipeTowerIntegration.hpp"
#include "GCode/SeamPlacer.hpp"
#include "GCode/GCodeProcessor.hpp"
#include "EdgeGrid.hpp"
#include "GCode/ThumbnailData.hpp"
#include "GCode/Travels.hpp"
#include "EdgeGrid.hpp"
#include "tcbspan/span.hpp"
#include <memory>
@ -89,6 +90,30 @@ struct LayerResult {
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
};
namespace GCode {
// Object and support extrusions of the same PrintObject at the same print_z.
// public, so that it could be accessed by free helper functions from GCode.cpp
struct ObjectLayerToPrint
{
ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
const Layer* object_layer;
const SupportLayer* support_layer;
const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; }
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
};
struct PrintObjectInstance
{
const PrintObject *print_object = nullptr;
int instance_idx = -1;
bool operator==(const PrintObjectInstance &other) const {return print_object == other.print_object && instance_idx == other.instance_idx; }
bool operator!=(const PrintObjectInstance &other) const { return *this == other; }
};
} // namespace GCode
class GCodeGenerator {
public:
@ -103,7 +128,6 @@ public:
m_layer(nullptr),
m_object_layer_over_raft(false),
m_volumetric_speed(0),
m_last_pos_defined(false),
m_last_extrusion_role(GCodeExtrusionRole::None),
m_last_width(0.0f),
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
@ -112,7 +136,7 @@ public:
m_brim_done(false),
m_second_layer_things_done(false),
m_silent_time_estimator_enabled(false),
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max()))
m_current_instance({nullptr, -1})
{}
~GCodeGenerator() = default;
@ -124,14 +148,27 @@ public:
const Vec2d& origin() const { return m_origin; }
void set_origin(const Vec2d &pointf);
void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); }
const Point& last_pos() const { return m_last_pos; }
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset.
template<typename Derived>
Vec2d point_to_gcode(const Eigen::MatrixBase<Derived> &point) const {
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode(): first parameter is not a 2D vector");
return Vec2d(unscaled<double>(point.x()), unscaled<double>(point.y())) + m_origin
- m_config.extruder_offset.get_at(m_writer.extruder()->id());
Eigen::Matrix<double, Derived::SizeAtCompileTime, 1, Eigen::DontAlign> point_to_gcode(const Eigen::MatrixBase<Derived> &point) const {
static_assert(
Derived::IsVectorAtCompileTime,
"GCodeGenerator::point_to_gcode(): first parameter is not a vector"
);
static_assert(
int(Derived::SizeAtCompileTime) == 2 || int(Derived::SizeAtCompileTime) == 3,
"GCodeGenerator::point_to_gcode(): first parameter is not a 2D or 3D vector"
);
if constexpr (Derived::SizeAtCompileTime == 2) {
return Vec2d(unscaled<double>(point.x()), unscaled<double>(point.y())) + m_origin
- m_config.extruder_offset.get_at(m_writer.extruder()->id());
} else {
const Vec2d gcode_point_xy{this->point_to_gcode(point.template head<2>())};
return to_3d(gcode_point_xy, unscaled(point.z()));
}
}
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution.
template<typename Derived>
Vec2d point_to_gcode_quantized(const Eigen::MatrixBase<Derived> &point) const {
@ -159,18 +196,10 @@ public:
// translate full config into a list of <key, value> items
static void encode_full_config(const Print& print, std::vector<std::pair<std::string, std::string>>& config);
// Object and support extrusions of the same PrintObject at the same print_z.
// public, so that it could be accessed by free helper functions from GCode.cpp
struct ObjectLayerToPrint
{
ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
const Layer* object_layer;
const SupportLayer* support_layer;
const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; }
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
};
using ObjectsLayerToPrint = std::vector<ObjectLayerToPrint>;
using ObjectLayerToPrint = GCode::ObjectLayerToPrint;
using ObjectsLayerToPrint = std::vector<GCode::ObjectLayerToPrint>;
std::optional<Point> last_position;
private:
class GCodeOutputStream {
@ -216,6 +245,9 @@ private:
static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> collect_layers_to_print(const Print &print);
/** @brief Generates ramping travel gcode for layer change. */
std::string get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id);
LayerResult process_layer(
const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z.
@ -249,19 +281,11 @@ private:
const GCode::SmoothPathCache &smooth_path_cache_global,
GCodeOutputStream &output_stream);
void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; }
bool last_pos_defined() const { return m_last_pos_defined; }
void set_extruders(const std::vector<unsigned int> &extruder_ids);
std::string preamble();
std::optional<std::string> get_helical_layer_change_gcode(
const coordf_t previous_layer_z,
const coordf_t print_z,
const std::string& comment
);
std::string change_layer(
coordf_t previous_layer_z,
coordf_t print_z,
const bool spiral_vase_enabled
coordf_t print_z
);
std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
@ -321,7 +345,12 @@ private:
const bool needs_retraction,
bool& could_be_wipe_disabled
);
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
std::string travel_to(
const Point &start_point,
const Point &end_point,
ExtrusionRole role,
const std::string &comment
);
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
std::string retract_and_wipe(bool toolchange = false);
@ -377,6 +406,7 @@ private:
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
JPSPathFinder m_avoid_crossing_curled_overhangs;
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
GCode::TravelObstacleTracker m_travel_obstacle_tracker;
bool m_enable_loop_clipping;
// If enabled, the G-code generator will put following comments at the ends
// of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END
@ -396,7 +426,6 @@ private:
// In non-sequential mode, all its copies will be printed.
const Layer* m_layer;
// m_layer is an object layer and it is being printed over raft surface.
std::optional<AABBTreeLines::LinesDistancer<Linef>> m_previous_layer_distancer;
bool m_object_layer_over_raft;
double m_volumetric_speed;
// Support for the extrusion role markers. Which marker is active?
@ -410,9 +439,10 @@ private:
double m_last_mm3_per_mm;
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
Point m_last_pos;
bool m_last_pos_defined;
std::optional<Vec3d> m_previous_layer_last_position;
// This needs to be populated during the layer processing!
std::optional<Vec3d> m_current_layer_first_position;
std::optional<unsigned> m_layer_change_extruder_id;
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
std::unique_ptr<SpiralVase> m_spiral_vase;
std::unique_ptr<GCodeFindReplace> m_find_replace;
@ -425,8 +455,8 @@ private:
bool m_brim_done;
// Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
bool m_second_layer_things_done;
// Index of a last object copy extruded.
std::pair<const PrintObject*, Point> m_last_obj_copy;
// Pointer to currently exporting PrintObject and instance index.
GCode::PrintObjectInstance m_current_instance;
bool m_silent_time_estimator_enabled;

View File

@ -1177,7 +1177,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, cons
// Otherwise perform the path planning in the coordinate system of the active object.
bool use_external = m_use_external_mp || m_use_external_mp_once;
Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
const Point start = gcodegen.last_pos() + scaled_origin;
const Point start = *gcodegen.last_position + scaled_origin;
const Point end = point + scaled_origin;
const Line travel(start, end);

View File

@ -57,6 +57,7 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
"HEIGHT:",
"WIDTH:",
"LAYER_CHANGE",
"LAYER_CHANGE_TRAVEL",
"COLOR_CHANGE",
"PAUSE_PRINT",
"CUSTOM_GCODE",

View File

@ -192,6 +192,7 @@ namespace Slic3r {
Height,
Width,
Layer_Change,
Layer_Change_Travel,
Color_Change,
Pause_Print,
Custom_Code,

View File

@ -275,10 +275,8 @@ std::string GCodeWriter::set_speed(double F, const std::string_view comment, con
return w.string();
}
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment)
std::string GCodeWriter::get_travel_to_xy_gcode(const Vec2d &point, const std::string_view comment) const
{
m_pos.head<2>() = point.head<2>();
GCodeG1Formatter w;
w.emit_xy(point);
w.emit_f(this->config.travel_speed.value * 60.0);
@ -286,6 +284,12 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view
return w.string();
}
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment)
{
m_pos.head<2>() = point.head<2>();
return this->get_travel_to_xy_gcode(point, comment);
}
std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment)
{
assert(std::abs(point.x()) < 1200.);
@ -303,35 +307,49 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij
return w.string();
}
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment)
std::string GCodeWriter::travel_to_xyz(const Vec3d& from, const Vec3d &to, const std::string_view comment)
{
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);
if (std::abs(to.x() - m_pos.x()) < EPSILON && std::abs(to.y() - m_pos.y()) < EPSILON) {
return this->travel_to_z(to.z(), comment);
} else if (std::abs(to.z() - m_pos.z()) < EPSILON) {
return this->travel_to_xy(to.head<2>(), comment);
} else {
m_pos = 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();
m_pos = to;
return this->get_travel_to_xyz_gcode(from, to, comment);
}
}
std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d &to, const std::string_view comment) const {
GCodeG1Formatter w;
w.emit_xyz(to);
const double distance_xy{(to.head<2>() - from.head<2>()).norm()};
const double distnace_z{std::abs(to.z() - from.z())};
const double time_z = distnace_z / this->config.travel_speed_z.value;
const double time_xy = distance_xy / this->config.travel_speed.value;
const double factor = time_z > 0 ? time_xy / time_z : 1;
if (factor < 1) {
w.emit_f((this->config.travel_speed.value * factor + (1 - factor) * this->config.travel_speed_z.value) * 60.0);
} else {
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)
{
return std::abs(m_pos.z() - z) < EPSILON ? "" : this->get_travel_to_z_gcode(z, comment);
if (std::abs(m_pos.z() - z) < EPSILON) {
return "";
} else {
m_pos.z() = z;
return this->get_travel_to_z_gcode(z, comment);
}
}
std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment)
std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment) const
{
m_pos.z() = z;
double speed = this->config.travel_speed_z.value;
if (speed == 0.)
speed = this->config.travel_speed.value;

View File

@ -66,10 +66,23 @@ public:
std::string toolchange_prefix() const;
std::string toolchange(unsigned int extruder_id);
std::string set_speed(double F, const std::string_view comment = {}, const std::string_view cooling_marker = {}) const;
std::string get_travel_to_xy_gcode(const Vec2d &point, const std::string_view comment) const;
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);
/**
* @brief Return gcode with all three axis defined. Optionally adds feedrate.
*
* Feedrate is added the starting point "from" is specified.
*
* @param from Optional starting point of the travel.
* @param to Where to travel to.
* @param comment Description of the travel purpose.
*/
std::string get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d &to, const std::string_view comment) const;
std::string travel_to_xyz(const Vec3d &from, const Vec3d &to, const std::string_view comment = {});
std::string get_travel_to_z_gcode(double z, const std::string_view comment) const;
std::string travel_to_z(double z, const std::string_view comment = {});
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);

View File

@ -1,53 +0,0 @@
#include "LayerChanges.hpp"
#include "libslic3r/ClipperUtils.hpp"
namespace Slic3r::GCode::Impl::LayerChanges {
Polygon generate_regular_polygon(
const Point &centroid, const Point &start_point, const unsigned points_count
) {
Points points;
points.reserve(points_count);
const double part_angle{2 * M_PI / points_count};
for (unsigned i = 0; i < points_count; ++i) {
const double current_angle{i * part_angle};
points.emplace_back(scaled(std::cos(current_angle)), scaled(std::sin(current_angle)));
}
Polygon regular_polygon{points};
const Vec2d current_vector{unscaled(regular_polygon.points.front())};
const Vec2d expected_vector{unscaled(start_point) - unscaled(centroid)};
const double current_scale = current_vector.norm();
const double expected_scale = expected_vector.norm();
regular_polygon.scale(expected_scale / current_scale);
regular_polygon.rotate(angle(current_vector, expected_vector));
regular_polygon.translate(centroid);
return regular_polygon;
}
Bed::Bed(const std::vector<Vec2d> &shape, const double padding)
: inner_offset(get_inner_offset(shape, padding)), centroid(unscaled(inner_offset.centroid())) {}
bool Bed::contains_within_padding(const Vec2d &point) const {
return inner_offset.contains(scaled(point));
}
Polygon Bed::get_inner_offset(const std::vector<Vec2d> &shape, const double padding) {
Points shape_scaled;
shape_scaled.reserve(shape.size());
using std::begin, std::end, std::back_inserter, std::transform;
transform(begin(shape), end(shape), back_inserter(shape_scaled), [](const Vec2d &point) {
return scaled(point);
});
const Polygons inner_offset{shrink({Polygon{shape_scaled}}, scaled(padding))};
if (inner_offset.empty()) {
return Polygon{};
}
return inner_offset.front();
}
} // namespace Slic3r::GCode::Impl::LayerChanges

View File

@ -1,52 +0,0 @@
/**
* @file
* @brief Utility functions for layer change gcode generation.
*/
#ifndef slic3r_GCode_LayerChanges_hpp_
#define slic3r_GCode_LayerChanges_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
namespace Slic3r::GCode::Impl::LayerChanges {
/**
* Generates a regular polygon - all angles are the same (e.g. typical hexagon).
*
* @param centroid Central point.
* @param start_point The polygon point are ordered. This is the first point.
* @param points_count Amount of nodes of the polygon (e.g. 6 for haxagon).
*
* Distance between centroid and start point sets the scale of the polygon.
*/
Polygon generate_regular_polygon(
const Point &centroid, const Point &start_point, const unsigned points_count
);
/**
* @brief A representation of the bed shape with inner padding.
*
* Its purpose is to facilitate the bed boundary checking.
*/
class Bed
{
private:
Polygon inner_offset;
static Polygon get_inner_offset(const std::vector<Vec2d> &shape, const double padding);
public:
/**
* Bed shape with inner padding.
*/
Bed(const std::vector<Vec2d> &shape, const double padding);
Vec2d centroid;
/**
* Returns true if the point is within the bed shape including inner padding.
*/
bool contains_within_padding(const Vec2d &point) const;
};
} // namespace Slic3r::GCode::Impl::LayerChanges
#endif // slic3r_GCode_LayerChanges_hpp_

View File

@ -1,11 +1,150 @@
#include "Travels.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Layer.hpp"
#include "libslic3r/Print.hpp"
#include "../GCode.hpp"
namespace Slic3r::GCode {
static Lines extrusion_entity_to_lines(const ExtrusionEntity &e_entity)
{
if (const auto *path = dynamic_cast<const ExtrusionPath *>(&e_entity)) {
return to_lines(path->as_polyline());
} else if (const auto *multipath = dynamic_cast<const ExtrusionMultiPath *>(&e_entity)) {
return to_lines(multipath->as_polyline());
} else if (const auto *loop = dynamic_cast<const ExtrusionLoop *>(&e_entity)) {
return to_lines(loop->polygon());
} else {
throw Slic3r::InvalidArgument("Invalid argument supplied to TODO()");
}
return {};
}
AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> get_previous_layer_distancer(
const GCodeGenerator::ObjectsLayerToPrint &objects_to_print, const ExPolygons &slices
) {
std::vector<ObjectOrExtrusionLinef> lines;
for (const GCodeGenerator::ObjectLayerToPrint &object_to_print : objects_to_print) {
if (const PrintObject *object = object_to_print.object(); object) {
const size_t object_layer_idx = &object_to_print - &objects_to_print.front();
for (const PrintInstance &instance : object->instances()) {
const size_t instance_idx = &instance - &object->instances().front();
for (const ExPolygon &polygon : slices)
for (const Line &line : polygon.lines())
lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift}), object_layer_idx, instance_idx);
}
}
}
return AABBTreeLines::LinesDistancer{std::move(lines)};
}
std::pair<AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef>, size_t> get_current_layer_distancer(const ObjectsLayerToPrint &objects_to_print)
{
std::vector<ObjectOrExtrusionLinef> lines;
size_t extrusion_entity_cnt = 0;
for (const ObjectLayerToPrint &object_to_print : objects_to_print) {
const size_t object_layer_idx = &object_to_print - &objects_to_print.front();
if (const Layer *layer = object_to_print.object_layer; layer) {
for (const PrintInstance &instance : layer->object()->instances()) {
const size_t instance_idx = &instance - &layer->object()->instances().front();
for (const LayerSlice &lslice : layer->lslices_ex) {
for (const LayerIsland &island : lslice.islands) {
const LayerRegion &layerm = *layer->get_region(island.perimeters.region());
for (uint32_t perimeter_id : island.perimeters) {
assert(dynamic_cast<const ExtrusionEntityCollection *>(layerm.perimeters().entities[perimeter_id]));
const auto *eec = static_cast<const ExtrusionEntityCollection *>(layerm.perimeters().entities[perimeter_id]);
for (const ExtrusionEntity *ee : *eec) {
if (ee->role().is_external_perimeter()) {
for (const Line &line : extrusion_entity_to_lines(*ee))
lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift}), object_layer_idx, instance_idx, ee);
}
++extrusion_entity_cnt;
}
}
}
}
}
}
}
return {AABBTreeLines::LinesDistancer{std::move(lines)}, extrusion_entity_cnt};
}
void TravelObstacleTracker::init_layer(const Layer &layer, const ObjectsLayerToPrint &objects_to_print)
{
size_t extrusion_entity_cnt = 0;
m_extruded_extrusion.clear();
m_objects_to_print = objects_to_print;
m_previous_layer_distancer = get_previous_layer_distancer(m_objects_to_print, layer.lower_layer->lslices);
std::tie(m_current_layer_distancer, extrusion_entity_cnt) = get_current_layer_distancer(m_objects_to_print);
m_extruded_extrusion.reserve(extrusion_entity_cnt);
}
void TravelObstacleTracker::mark_extruded(const ExtrusionEntity *extrusion_entity, size_t object_layer_idx, size_t instance_idx)
{
if (extrusion_entity->role().is_external_perimeter())
this->m_extruded_extrusion.insert({int(object_layer_idx), int(instance_idx), extrusion_entity});
}
bool TravelObstacleTracker::is_extruded(const ObjectOrExtrusionLinef &line) const
{
return m_extruded_extrusion.find({line.object_layer_idx, line.instance_idx, line.extrusion_entity}) != m_extruded_extrusion.end();
}
} // namespace Slic3r::GCode
namespace Slic3r::GCode::Impl::Travels {
ElevatedTravelFormula::ElevatedTravelFormula(const ElevatedTravelParams &params)
: smoothing_from(params.slope_end - params.blend_width / 2.0)
, smoothing_to(params.slope_end + params.blend_width / 2.0)
, blend_width(params.blend_width)
, lift_height(params.lift_height)
, slope_end(params.slope_end) {
if (smoothing_from < 0) {
smoothing_from = params.slope_end;
smoothing_to = params.slope_end;
}
}
double parabola(const double x, const double a, const double b, const double c) {
return a * x * x + b * x + c;
}
double ElevatedTravelFormula::slope_function(double distance_from_start) const {
if (distance_from_start < this->slope_end) {
const double lift_percent = distance_from_start / this->slope_end;
return lift_percent * this->lift_height;
} else {
return this->lift_height;
}
}
double ElevatedTravelFormula::operator()(const double distance_from_start) const {
if (distance_from_start > this->smoothing_from && distance_from_start < this->smoothing_to) {
const double slope = this->lift_height / this->slope_end;
// This is a part of a parabola going over a specific
// range and with specific end slopes.
const double a = -slope / 2.0 / this->blend_width;
const double b = slope * this->smoothing_to / this->blend_width;
const double c = this->lift_height + a * boost::math::pow<2>(this->smoothing_to);
return parabola(distance_from_start, a, b, c);
}
return slope_function(distance_from_start);
}
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.reserve(xy_path.size());
for (const Point &point : xy_path) {
result.emplace_back(point.x(), point.y(), scaled(elevation));
}
return result;
@ -52,26 +191,6 @@ std::vector<DistancedPoint> slice_xy_path(
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 {
return this->params.lift_height;
}
}
ElevatedTravelParams params{};
};
Points3 generate_elevated_travel(
const tcb::span<const Point> xy_path,
const std::vector<double> &ensure_points_at_distances,
@ -93,59 +212,153 @@ Points3 generate_elevated_travel(
return result;
}
std::optional<double> get_first_crossed_line_distance(
tcb::span<const Line> xy_path, const AABBTreeLines::LinesDistancer<Linef> &distancer
struct Intersection
{
int object_layer_idx = -1;
int instance_idx = -1;
bool is_inside = false;
bool is_print_instance_equal(const ObjectOrExtrusionLinef &print_istance) {
return this->object_layer_idx == print_istance.object_layer_idx && this->instance_idx == print_istance.instance_idx;
}
};
double get_first_crossed_line_distance(
tcb::span<const Line> xy_path,
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> &distancer,
const ObjectsLayerToPrint &objects_to_print,
const std::function<bool(const ObjectOrExtrusionLinef &)> &predicate,
const bool ignore_starting_object_intersection
) {
assert(!xy_path.empty());
if (xy_path.empty()) {
return {};
}
if (xy_path.empty())
return std::numeric_limits<double>::max();
const Point path_first_point = xy_path.front().a;
double traversed_distance = 0;
bool skip_intersection = ignore_starting_object_intersection;
Intersection first_intersection;
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();
}
const ObjectOrExtrusionLinef unscaled_line = {unscaled(line.a), unscaled(line.b)};
const std::vector<std::pair<Vec2d, size_t>> intersections = distancer.intersections_with_line<true>(unscaled_line);
if (intersections.empty())
continue;
if (!objects_to_print.empty() && ignore_starting_object_intersection && first_intersection.object_layer_idx == -1) {
const ObjectOrExtrusionLinef &intersection_line = distancer.get_line(intersections.front().second);
const Point shift = objects_to_print[intersection_line.object_layer_idx].layer()->object()->instances()[intersection_line.instance_idx].shift;
const Point shifted_first_point = path_first_point - shift;
const bool contain_first_point = expolygons_contain(objects_to_print[intersection_line.object_layer_idx].layer()->lslices, shifted_first_point);
first_intersection = {intersection_line.object_layer_idx, intersection_line.instance_idx, contain_first_point};
}
for (const auto &intersection : intersections) {
const ObjectOrExtrusionLinef &intersection_line = distancer.get_line(intersection.second);
const double distance = traversed_distance + (unscaled_line.a - intersection.first).norm();
if (distance <= EPSILON)
continue;
// There is only one external border for each object, so when we cross this border,
// we definitely know that we are outside the object.
if (skip_intersection && first_intersection.is_print_instance_equal(intersection_line) && first_intersection.is_inside) {
skip_intersection = false;
continue;
}
if (!predicate(intersection_line))
continue;
return distance;
}
traversed_distance += (unscaled_line.a - unscaled_line.b).norm();
}
return {};
return std::numeric_limits<double>::max();
}
std::optional<double> get_obstacle_adjusted_slope_end(
const Lines &xy_path,
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer
double get_obstacle_adjusted_slope_end(const Lines &xy_path, const GCode::TravelObstacleTracker &obstacle_tracker) {
const double previous_layer_crossed_line = get_first_crossed_line_distance(
xy_path, obstacle_tracker.previous_layer_distancer(), obstacle_tracker.objects_to_print()
);
const double current_layer_crossed_line = get_first_crossed_line_distance(
xy_path, obstacle_tracker.current_layer_distancer(), obstacle_tracker.objects_to_print(),
[&obstacle_tracker](const ObjectOrExtrusionLinef &line) { return obstacle_tracker.is_extruded(line); }
);
return std::min(previous_layer_crossed_line, current_layer_crossed_line);
}
struct SmoothingParams
{
double blend_width{};
unsigned points_count{1};
};
SmoothingParams get_smoothing_params(
const double lift_height,
const double slope_end,
unsigned extruder_id,
const double travel_length,
const FullPrintConfig &config
) {
if (!previous_layer_distancer) {
return std::nullopt;
if (config.gcode_flavor != gcfMarlinFirmware)
// Smoothing is supported only on Marlin.
return {0, 1};
const double slope = lift_height / slope_end;
const double max_machine_z_velocity = config.machine_max_feedrate_z.get_at(extruder_id);
const double max_xy_velocity =
Vec2d{
config.machine_max_feedrate_x.get_at(extruder_id),
config.machine_max_feedrate_y.get_at(extruder_id)}
.norm();
const double xy_acceleration = config.machine_max_acceleration_travel.get_at(extruder_id);
const double xy_acceleration_time = max_xy_velocity / xy_acceleration;
const double xy_acceleration_distance = 1.0 / 2.0 * xy_acceleration *
boost::math::pow<2>(xy_acceleration_time);
if (travel_length < xy_acceleration_distance) {
return {0, 1};
}
std::optional<double> first_obstacle_distance =
get_first_crossed_line_distance(xy_path, *previous_layer_distancer);
if (!first_obstacle_distance) {
return std::nullopt;
}
return *first_obstacle_distance;
const double max_z_velocity = std::min(max_xy_velocity * slope, max_machine_z_velocity);
const double deceleration_time = max_z_velocity /
config.machine_max_acceleration_z.get_at(extruder_id);
const double deceleration_xy_distance = deceleration_time * max_xy_velocity;
const double blend_width = slope_end > deceleration_xy_distance / 2.0 ? deceleration_xy_distance :
slope_end * 2.0;
const unsigned points_count = blend_width > 0 ?
std::ceil(max_z_velocity / config.machine_max_jerk_z.get_at(extruder_id)) :
1;
if (blend_width <= 0 // When there is no blend with, there is no need for smoothing.
|| points_count > 6 // That would be way to many points. Do not do it at all.
|| points_count <= 0 // Always return at least one point.
)
return {0, 1};
return {blend_width, points_count};
}
ElevatedTravelParams get_elevated_traval_params(
const Lines &xy_path,
const Polyline& xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer
const GCode::TravelObstacleTracker &obstacle_tracker
) {
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);
elevation_params.blend_width = 0;
return elevation_params;
}
elevation_params.lift_height = config.travel_max_lift.get_at(extruder_id);
@ -159,22 +372,44 @@ ElevatedTravelParams get_elevated_traval_params(
elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad);
}
std::optional<double> obstacle_adjusted_slope_end{
get_obstacle_adjusted_slope_end(xy_path, previous_layer_distancer)};
const double obstacle_adjusted_slope_end = get_obstacle_adjusted_slope_end(xy_path.lines(), obstacle_tracker);
if (obstacle_adjusted_slope_end < elevation_params.slope_end)
elevation_params.slope_end = obstacle_adjusted_slope_end;
if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) {
elevation_params.slope_end = *obstacle_adjusted_slope_end;
}
SmoothingParams smoothing_params{get_smoothing_params(
elevation_params.lift_height, elevation_params.slope_end, extruder_id,
unscaled(xy_path.length()), config
)};
elevation_params.blend_width = smoothing_params.blend_width;
elevation_params.parabola_points_count = smoothing_params.points_count;
return elevation_params;
}
std::vector<double> linspace(const double from, const double to, const unsigned count) {
if (count == 0) {
return {};
}
std::vector<double> result;
result.reserve(count);
if (count == 1) {
result.emplace_back((from + to) / 2.0);
return result;
}
const double step = (to - from) / count;
for (unsigned i = 0; i < count - 1; ++i) {
result.emplace_back(from + i * step);
}
result.emplace_back(to); // Make sure the last value is exactly equal to the value of "to".
return result;
}
Points3 generate_travel_to_extrusion(
const Polyline &xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const double initial_elevation,
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer,
const GCode::TravelObstacleTracker &obstacle_tracker,
const Point &xy_path_coord_origin
) {
const double upper_limit = config.retract_lift_below.get_at(extruder_id);
@ -184,15 +419,20 @@ Points3 generate_travel_to_extrusion(
return generate_flat_travel(xy_path.points, initial_elevation);
}
Lines global_xy_path;
for (const Line &line : xy_path.lines()) {
global_xy_path.emplace_back(line.a + xy_path_coord_origin, line.b + xy_path_coord_origin);
Points global_xy_path;
for (const Point &point : xy_path.points) {
global_xy_path.emplace_back(point + xy_path_coord_origin);
}
ElevatedTravelParams elevation_params{
get_elevated_traval_params(global_xy_path, config, extruder_id, previous_layer_distancer)};
ElevatedTravelParams elevation_params{get_elevated_traval_params(
Polyline{std::move(global_xy_path)}, config, extruder_id, obstacle_tracker
)};
const std::vector<double> ensure_points_at_distances{elevation_params.slope_end};
const std::vector<double> ensure_points_at_distances = linspace(
elevation_params.slope_end - elevation_params.blend_width / 2.0,
elevation_params.slope_end + elevation_params.blend_width / 2.0,
elevation_params.parabola_points_count
);
Points3 result{generate_elevated_travel(
xy_path.points, ensure_points_at_distances, initial_elevation,

View File

@ -11,18 +11,137 @@
#include <functional>
#include <optional>
#include "libslic3r/Line.hpp"
#include "libslic3r/Point.hpp"
#include <boost/functional/hash.hpp>
#include <boost/math/special_functions/pow.hpp>
#include "libslic3r/AABBTreeLines.hpp"
#include "libslic3r/PrintConfig.hpp"
// Forward declarations.
namespace Slic3r {
class Layer;
class Point;
class Linef;
class Polyline;
class FullPrintConfig;
class ExtrusionEntity;
} // namespace Slic3r
namespace Slic3r::GCode {
struct ObjectLayerToPrint;
using ObjectsLayerToPrint = std::vector<ObjectLayerToPrint>;
class ObjectOrExtrusionLinef : public Linef
{
public:
ObjectOrExtrusionLinef() = delete;
ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b) : Linef(a, b) {}
explicit ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b, size_t object_layer_idx, size_t instance_idx)
: Linef(a, b), object_layer_idx(int(object_layer_idx)), instance_idx(int(instance_idx)) {}
ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b, size_t object_layer_idx, size_t instance_idx, const ExtrusionEntity *extrusion_entity)
: Linef(a, b), object_layer_idx(int(object_layer_idx)), instance_idx(int(instance_idx)), extrusion_entity(extrusion_entity) {}
virtual ~ObjectOrExtrusionLinef() = default;
const int object_layer_idx = -1;
const int instance_idx = -1;
const ExtrusionEntity *extrusion_entity = nullptr;
};
struct ExtrudedExtrusionEntity
{
const int object_layer_idx = -1;
const int instance_idx = -1;
const ExtrusionEntity *extrusion_entity = nullptr;
bool operator==(const ExtrudedExtrusionEntity &other) const
{
return extrusion_entity == other.extrusion_entity && object_layer_idx == other.object_layer_idx &&
instance_idx == other.instance_idx;
}
};
struct ExtrudedExtrusionEntityHash
{
size_t operator()(const ExtrudedExtrusionEntity &eee) const noexcept
{
std::size_t seed = std::hash<const ExtrusionEntity *>{}(eee.extrusion_entity);
boost::hash_combine(seed, std::hash<int>{}(eee.object_layer_idx));
boost::hash_combine(seed, std::hash<int>{}(eee.instance_idx));
return seed;
}
};
class TravelObstacleTracker
{
public:
void init_layer(const Layer &layer, const ObjectsLayerToPrint &objects_to_print);
void mark_extruded(const ExtrusionEntity *extrusion_entity, size_t object_layer_idx, size_t instance_idx);
bool is_extruded(const ObjectOrExtrusionLinef &line) const;
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> &previous_layer_distancer() const { return m_previous_layer_distancer; }
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> &current_layer_distancer() const { return m_current_layer_distancer; }
const ObjectsLayerToPrint &objects_to_print() const { return m_objects_to_print; }
private:
ObjectsLayerToPrint m_objects_to_print;
AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> m_previous_layer_distancer;
AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> m_current_layer_distancer;
std::unordered_set<ExtrudedExtrusionEntity, ExtrudedExtrusionEntityHash> m_extruded_extrusion;
};
} // namespace Slic3r::GCode
namespace Slic3r::GCode::Impl::Travels {
/**
* @brief A point on a curve with a distance from start.
*/
struct DistancedPoint
{
Point point;
double distance_from_start;
};
struct ElevatedTravelParams
{
/** Maximal value of nozzle lift. */
double lift_height{};
/** Distance from travel to the middle of the smoothing parabola. */
double slope_end{};
/** Width of the smoothing parabola */
double blend_width{};
/** How many points should be used to approximate the parabola */
unsigned parabola_points_count{};
};
/**
* @brief A mathematical formula for a smooth function.
*
* It starts lineary increasing than there is a parabola part and
* at the end it is flat.
*/
struct ElevatedTravelFormula
{
ElevatedTravelFormula(const ElevatedTravelParams &params);
double operator()(const double distance_from_start) const;
private:
double slope_function(double distance_from_start) const;
double smoothing_from;
double smoothing_to;
double blend_width;
double lift_height;
double slope_end;
};
/**
* @brief Takes a path described as a list of points and adds points to it.
*
@ -48,6 +167,20 @@ std::vector<DistancedPoint> slice_xy_path(
tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances
);
/**
* @brief Generate regulary spaced points on 1 axis. Includes both from and to.
*
* If count is 1, the point is in the middle of the range.
*/
std::vector<double> linspace(const double from, const double to, const unsigned count);
ElevatedTravelParams get_elevated_traval_params(
const Polyline& xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const GCode::TravelObstacleTracker &obstacle_tracker
);
/**
* @brief Simply return the xy_path with z coord set to elevation.
*/
@ -76,13 +209,19 @@ Points3 generate_elevated_travel(
*
* @param xy_path A path in 2D.
* @param distancer AABB Tree over lines.
* @param objects_to_print Objects to print are used to determine in which object xy_path starts.
* @param ignore_starting_object_intersection When it is true, then the first intersection during traveling from the object out is ignored.
* @return Distance to the first intersection if there is one.
*
* **Ignores intersection with xy_path starting point.**
*/
std::optional<double> get_first_crossed_line_distance(
tcb::span<const Line> xy_path, const AABBTreeLines::LinesDistancer<Linef> &distancer
);
double get_first_crossed_line_distance(
tcb::span<const Line> xy_path,
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> &distancer,
const ObjectsLayerToPrint &objects_to_print = {},
const std::function<bool(const ObjectOrExtrusionLinef &)> &predicate = [](const ObjectOrExtrusionLinef &) { return true; },
bool ignore_starting_object_intersection = true);
/**
* @brief Extract parameters and decide wheather the travel can be elevated.
@ -93,7 +232,7 @@ Points3 generate_travel_to_extrusion(
const FullPrintConfig &config,
const unsigned extruder_id,
const double initial_elevation,
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer,
const GCode::TravelObstacleTracker &obstacle_tracker,
const Point &xy_path_coord_origin
);
} // namespace Slic3r::GCode::Impl::Travels

View File

@ -164,7 +164,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange)
return done;
};
// Start with the current position, which may be different from the wipe path start in case of loop clipping.
Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos());
Vec2d prev = gcodegen.point_to_gcode_quantized(*gcodegen.last_position);
auto it = this->path().begin();
Vec2d p = gcodegen.point_to_gcode(it->point + m_offset);
++ it;
@ -192,7 +192,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange)
// add tag for processor
assert(p == GCodeFormatter::quantize(p));
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n";
gcodegen.set_last_pos(gcodegen.gcode_to_point(p));
gcodegen.last_position = gcodegen.gcode_to_point(p);
}
}

View File

@ -57,12 +57,30 @@ 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) {
const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos);
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),
ExtrusionRole::Mixed,
"Travel to a Wipe Tower");
const std::string comment{"Travel to a Wipe Tower"};
if (gcodegen.m_current_layer_first_position) {
if (gcodegen.last_position) {
gcode += gcodegen.travel_to(
*gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment
);
} else {
gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment);
gcode += gcodegen.writer().get_travel_to_z_gcode(z, comment);
}
} else {
const Vec3crd point = to_3d(xy_point, scaled(z));
const Vec3d gcode_point = gcodegen.point_to_gcode(point);
gcodegen.last_position = point.head<2>();
gcodegen.writer().update_position(gcode_point);
gcode += gcodegen.writer()
.get_travel_to_xy_gcode(gcode_point.head<2>(), comment);
gcode += gcodegen.writer()
.get_travel_to_z_gcode(gcode_point.z(), comment);
gcodegen.m_current_layer_first_position = gcode_point;
}
gcode += gcodegen.unretract();
} else {
// When this is multiextruder printer without any ramming, we can just change
@ -98,7 +116,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
gcodegen.last_position = wipe_tower_point_to_object_point(gcodegen, end_pos);
if (!is_approx(z, current_z)) {
gcode += gcodegen.writer().retract();
gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
@ -247,7 +265,7 @@ 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.generate_travel_gcode(
{{gcodegen.last_pos().x(), gcodegen.last_pos().y(), scaled(m_final_purge.print_z)}},
{{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(m_final_purge.print_z)}},
"move to safe place for purging"
);
gcode += append_tcr(gcodegen, m_final_purge, -1);

View File

@ -26,7 +26,8 @@ public:
m_tool_changes(tool_changes),
m_final_purge(final_purge),
m_layer_idx(-1),
m_tool_change_idx(0)
m_tool_change_idx(0),
m_last_wipe_tower_print_z(print_config.z_offset.value)
{}
std::string prime(GCodeGenerator &gcodegen);
@ -56,7 +57,7 @@ private:
// Current layer index.
int m_layer_idx;
int m_tool_change_idx;
double m_last_wipe_tower_print_z = 0.f;
double m_last_wipe_tower_print_z;
};
} // namespace GCode

View File

@ -280,6 +280,7 @@ class Linef
public:
Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {}
Linef(const Vec2d& _a, const Vec2d& _b) : a(_a), b(_b) {}
virtual ~Linef() = default;
Vec2d a;
Vec2d b;

View File

@ -1966,7 +1966,7 @@ std::vector<std::pair<std::string, std::vector<std::string>>> option_keys {
"filament_travel_ramping_lift",
"filament_travel_max_lift",
"filament_travel_slope",
//"filament_travel_lift_before_obstacle",
"filament_travel_lift_before_obstacle",
"filament_retract_lift_above",
"filament_retract_lift_below"
}},
@ -3283,7 +3283,7 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders)
optgroup->append_single_option_line("travel_ramping_lift", "", extruder_idx);
optgroup->append_single_option_line("travel_max_lift", "", extruder_idx);
optgroup->append_single_option_line("travel_slope", "", extruder_idx);
//optgroup->append_single_option_line("travel_lift_before_obstacle", "", extruder_idx);
optgroup->append_single_option_line("travel_lift_before_obstacle", "", extruder_idx);
line = { L("Only lift"), "" };
line.append_option(optgroup->get_option("retract_lift_above", extruder_idx));
@ -3557,7 +3557,7 @@ void TabPrinter::toggle_options()
load_config(new_conf);
}
//toggle_option("travel_lift_before_obstacle", ramping_lift, i);
toggle_option("travel_lift_before_obstacle", ramping_lift, i);
toggle_option("retract_length_toolchange", have_multiple_extruders, i);

View File

@ -14,7 +14,6 @@ add_executable(${_TEST_NAME}_tests
test_gaps.cpp
test_gcode.cpp
test_gcode_travels.cpp
test_gcode_layer_changes.cpp
test_gcodefindreplace.cpp
test_gcodewriter.cpp
test_layers.cpp

View File

@ -250,9 +250,9 @@ SCENARIO("Cooling integration tests", "[Cooling]") {
if (l == 0)
l = line.dist_Z(self);
if (l > 0.) {
if (layer_times.empty())
layer_times.emplace_back(0.);
layer_times.back() += 60. * std::abs(l) / line.new_F(self);
if (!layer_times.empty()) { // Ignore anything before first z move.
layer_times.back() += 60. * std::abs(l) / line.new_F(self);
}
}
if (line.has('F') && line.f() == external_perimeter_speed)
++ layer_external[scaled<coord_t>(self.z())];

View File

@ -45,13 +45,21 @@ SCENARIO("Custom G-code", "[CustomGCode]")
});
GCodeReader parser;
bool last_move_was_z_change = false;
bool first_z_move = true; // First z move is not a layer change.
int num_layer_changes_not_applied = 0;
parser.parse_buffer(Slic3r::Test::slice({ Test::TestMesh::cube_2x20x10 }, config),
[&last_move_was_z_change, &num_layer_changes_not_applied](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
[&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
{
if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_"))
if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_")) {
++ num_layer_changes_not_applied;
last_move_was_z_change = line.dist_Z(self) > 0;
}
if (line.dist_Z(self) > 0 && first_z_move) {
first_z_move = false;
} else if (line.dist_Z(self) > 0){
last_move_was_z_change = true;
} else {
last_move_was_z_change = false;
}
});
THEN("custom layer G-code is applied after Z move and before other moves") {
REQUIRE(num_layer_changes_not_applied == 0);

View File

@ -1,55 +0,0 @@
#include <catch2/catch.hpp>
#include <libslic3r/GCode/LayerChanges.hpp>
using namespace Slic3r;
using namespace Slic3r::GCode::Impl::LayerChanges;
TEST_CASE("Generate regular polygon", "[GCode]") {
const unsigned points_count{32};
const Point centroid{scaled(Vec2d{5, -2})};
const Polygon result{generate_regular_polygon(centroid, scaled(Vec2d{0, 0}), points_count)};
const Point oposite_point{centroid * 2};
REQUIRE(result.size() == 32);
CHECK(result[16].x() == Approx(oposite_point.x()));
CHECK(result[16].y() == Approx(oposite_point.y()));
std::vector<double> angles;
angles.reserve(points_count);
for (unsigned index = 0; index < points_count; index++) {
const unsigned previous_index{index == 0 ? points_count - 1 : index - 1};
const unsigned next_index{index == points_count - 1 ? 0 : index + 1};
const Point previous_point = result.points[previous_index];
const Point current_point = result.points[index];
const Point next_point = result.points[next_index];
angles.emplace_back(angle(Vec2crd{previous_point - current_point}, Vec2crd{next_point - current_point}));
}
std::vector<double> expected;
angles.reserve(points_count);
std::generate_n(std::back_inserter(expected), points_count, [&](){
return angles.front();
});
CHECK_THAT(angles, Catch::Matchers::Approx(expected));
}
TEST_CASE("Square bed with padding", "[GCode]") {
const Bed bed{
{
Vec2d{0, 0},
Vec2d{100, 0},
Vec2d{100, 100},
Vec2d{0, 100}
},
10.0
};
CHECK(bed.centroid.x() == 50);
CHECK(bed.centroid.y() == 50);
CHECK(bed.contains_within_padding(Vec2d{10, 10}));
CHECK_FALSE(bed.contains_within_padding(Vec2d{9, 10}));
}

View File

@ -1,6 +1,8 @@
#include <catch2/catch.hpp>
#include <libslic3r/GCode/Travels.hpp>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/GCode.hpp>
#include <boost/math/special_functions/pow.hpp>
using namespace Slic3r;
using namespace Slic3r::GCode::Impl::Travels;
@ -15,8 +17,8 @@ struct ApproxEqualsPoints : public Catch::MatcherBase<Points> {
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
std::abs(point.x() - expected_point.x()) > int(this->tolerance)
|| std::abs(point.y() - expected_point.y()) > int(this->tolerance)
) {
return false;
}
@ -116,10 +118,10 @@ TEST_CASE("Generate elevated travel", "[GCode]") {
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})
scaled(Vec3f{ 0.f, 0.f, 3.f}),
scaled(Vec3f{0.2f, 0.f, 3.2f}),
scaled(Vec3f{0.5f, 0.f, 3.5f}),
scaled(Vec3f{ 1.f, 0.f, 4.f})
});
}
@ -161,21 +163,39 @@ TEST_CASE("Get first crossed line distance", "[GCode]") {
scaled(Vec2f{0, 5}),
}.lines()};
std::vector<Linef> lines;
std::vector<GCode::ObjectOrExtrusionLinef> lines;
for (const ExPolygon& polygon : {square_with_hole, square_above}) {
for (const Line& line : polygon.lines()) {
lines.emplace_back(unscale(line.a), unscale(line.b));
}
}
// Try different cases by skipping lines in the travel.
AABBTreeLines::LinesDistancer<Linef> distancer{std::move(lines)};
AABBTreeLines::LinesDistancer<GCode::ObjectOrExtrusionLinef> distancer{std::move(lines)};
CHECK(*get_first_crossed_line_distance(travel, distancer) == Approx(1));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6));
CHECK_FALSE(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer));
CHECK(get_first_crossed_line_distance(travel, distancer) == Approx(1));
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5));
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7));
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6));
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer) == std::numeric_limits<double>::max());
}
TEST_CASE("Elevated travel formula", "[GCode]") {
const double lift_height{10};
const double slope_end{10};
const double blend_width{10};
const ElevatedTravelParams params{lift_height, slope_end, blend_width};
ElevatedTravelFormula f{params};
const double distance = slope_end - blend_width / 2;
const double slope = (f(distance) - f(0)) / distance;
// At the begining it has given slope.
CHECK(slope == lift_height / slope_end);
// At the end it is flat.
CHECK(f(slope_end + blend_width / 2) == f(slope_end + blend_width));
// Should be smoothed.
CHECK(f(slope_end) < lift_height);
}