Implement lift before obstacles that take into account already extruder extrusions on the current layer.

Ignore the first intersection of the travel path with the object from which the travel starts.

Created a new class TravelObstacleTracker, for wrapping all data structures related to lift before obstacles.
This commit is contained in:
Lukáš Hejl 2023-12-01 11:31:38 +01:00 committed by Martin Šach
parent 2fc9299c65
commit da57489874
8 changed files with 270 additions and 61 deletions

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

@ -2060,26 +2060,6 @@ 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}));
}
}
}
}
return AABBTreeLines::LinesDistancer{std::move(lines)};
}
}
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>()),
@ -2089,7 +2069,7 @@ std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3
using namespace GCode::Impl::Travels;
ElevatedTravelParams elevation_params{
get_elevated_traval_params(xy_path, this->m_config, extruder_id)};
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();
@ -2226,9 +2206,9 @@ LayerResult GCodeGenerator::process_layer(
}
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()) {
DynamicConfig config;
@ -2566,9 +2546,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);
}
}
}
};
@ -3383,10 +3365,10 @@ std::string GCodeGenerator::travel_to(
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)
)
);

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>
@ -405,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
@ -424,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?

View File

@ -1,6 +1,104 @@
#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 {
@ -114,33 +212,86 @@ Points3 generate_elevated_travel(
return result;
}
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<Linef> &distancer
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 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 std::numeric_limits<double>::max();
}
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{};
@ -201,7 +352,7 @@ ElevatedTravelParams get_elevated_traval_params(
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)) {
@ -221,12 +372,7 @@ ElevatedTravelParams get_elevated_traval_params(
elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad);
}
const double obstacle_adjusted_slope_end{
previous_layer_distancer ?
get_first_crossed_line_distance(xy_path.lines(), *previous_layer_distancer) :
std::numeric_limits<double>::max()
};
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;
@ -263,7 +409,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
) {
const double upper_limit = config.retract_lift_below.get_at(extruder_id);
@ -279,7 +425,7 @@ Points3 generate_travel_to_extrusion(
}
ElevatedTravelParams elevation_params{get_elevated_traval_params(
Polyline{std::move(global_xy_path)}, config, extruder_id, previous_layer_distancer
Polyline{std::move(global_xy_path)}, config, extruder_id, obstacle_tracker
)};
const std::vector<double> ensure_points_at_distances = linspace(

View File

@ -11,19 +11,91 @@
#include <functional>
#include <optional>
#include <boost/functional/hash.hpp>
#include <boost/math/special_functions/pow.hpp>
#include "libslic3r/AABBTreeLines.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.
@ -106,7 +178,7 @@ ElevatedTravelParams get_elevated_traval_params(
const Polyline& xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer = std::nullopt
const GCode::TravelObstacleTracker &obstacle_tracker
);
/**
@ -137,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.**
*/
double get_first_crossed_line_distance(
tcb::span<const Line> xy_path, const AABBTreeLines::LinesDistancer<Linef> &distancer
);
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.
@ -154,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

@ -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

@ -1,6 +1,7 @@
#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;
@ -162,14 +163,14 @@ 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));