mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 17:05:53 +08:00
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:
commit
2770b977da
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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",
|
||||
|
@ -192,6 +192,7 @@ namespace Slic3r {
|
||||
Height,
|
||||
Width,
|
||||
Layer_Change,
|
||||
Layer_Change_Travel,
|
||||
Color_Change,
|
||||
Pause_Print,
|
||||
Custom_Code,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -1,53 +0,0 @@
|
||||
#include "LayerChanges.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r::GCode::Impl::LayerChanges {
|
||||
|
||||
Polygon generate_regular_polygon(
|
||||
const Point ¢roid, 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
|
@ -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 ¢roid, 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_
|
@ -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 ¶ms)
|
||||
: 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,
|
||||
|
@ -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> ¤t_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 ¶ms);
|
||||
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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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())];
|
||||
|
@ -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);
|
||||
|
@ -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}));
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user