Merge branch 'lm_seam_fixes2'

This commit is contained in:
Lukas Matena 2021-10-22 14:52:01 +02:00
commit e07a2434d3
7 changed files with 248 additions and 93 deletions

View File

@ -193,12 +193,8 @@ bool ExtrusionLoop::split_at_vertex(const Point &point)
return false; return false;
} }
// Splitting an extrusion loop, possibly made of multiple segments, some of the segments may be bridging. std::pair<size_t, Point> ExtrusionLoop::get_closest_path_and_point(const Point& point, bool prefer_non_overhang) const
void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
{ {
if (this->paths.empty())
return;
// Find the closest path and closest point belonging to that path. Avoid overhangs, if asked for. // Find the closest path and closest point belonging to that path. Avoid overhangs, if asked for.
size_t path_idx = 0; size_t path_idx = 0;
Point p; Point p;
@ -227,6 +223,16 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
p = p_non_overhang; p = p_non_overhang;
} }
} }
return std::make_pair(path_idx, p);
}
// Splitting an extrusion loop, possibly made of multiple segments, some of the segments may be bridging.
void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
{
if (this->paths.empty())
return;
auto [path_idx, p] = get_closest_path_and_point(point, prefer_non_overhang);
// now split path_idx in two parts // now split path_idx in two parts
const ExtrusionPath &path = this->paths[path_idx]; const ExtrusionPath &path = this->paths[path_idx];

View File

@ -258,6 +258,7 @@ public:
double length() const override; double length() const override;
bool split_at_vertex(const Point &point); bool split_at_vertex(const Point &point);
void split_at(const Point &point, bool prefer_non_overhang); void split_at(const Point &point, bool prefer_non_overhang);
std::pair<size_t, Point> get_closest_path_and_point(const Point& point, bool prefer_non_overhang) const;
void clip_end(double distance, ExtrusionPaths* paths) const; void clip_end(double distance, ExtrusionPaths* paths) const;
// Test, whether the point is extruded by a bridging flow. // Test, whether the point is extruded by a bridging flow.
// This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead. // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead.

View File

@ -2479,18 +2479,14 @@ std::string GCode::change_layer(coordf_t print_z)
std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid) static std::unique_ptr<EdgeGrid::Grid> calculate_layer_edge_grid(const Layer& layer)
{ {
// get a copy; don't modify the orientation of the original loop object otherwise auto out = make_unique<EdgeGrid::Grid>();
// next copies (if any) would not detect the correct orientation
if (m_layer->lower_layer != nullptr && lower_layer_edge_grid != nullptr) {
if (! *lower_layer_edge_grid) {
// Create the distance field for a layer below. // Create the distance field for a layer below.
const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5);
*lower_layer_edge_grid = make_unique<EdgeGrid::Grid>(); out->create(layer.lslices, distance_field_resolution);
(*lower_layer_edge_grid)->create(m_layer->lower_layer->lslices, distance_field_resolution); out->calculate_sdf();
(*lower_layer_edge_grid)->calculate_sdf();
#if 0 #if 0
{ {
static int iRun = 0; static int iRun = 0;
@ -2502,34 +2498,30 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++)); EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++));
} }
#endif #endif
return out;
} }
}
std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
{
// get a copy; don't modify the orientation of the original loop object otherwise
// next copies (if any) would not detect the correct orientation
if (m_layer->lower_layer && lower_layer_edge_grid != nullptr && ! *lower_layer_edge_grid)
*lower_layer_edge_grid = calculate_layer_edge_grid(*m_layer->lower_layer);
// extrude all loops ccw // extrude all loops ccw
bool was_clockwise = loop.make_counter_clockwise(); bool was_clockwise = loop.make_counter_clockwise();
SeamPosition seam_position = m_config.seam_position;
if (loop.loop_role() == elrSkirt)
seam_position = spNearest;
// find the point of the loop that is closest to the current extruder position // find the point of the loop that is closest to the current extruder position
// or randomize if requested // or randomize if requested
Point last_pos = this->last_pos(); Point last_pos = this->last_pos();
if (m_config.spiral_vase) { if (m_config.spiral_vase) {
loop.split_at(last_pos, false); loop.split_at(last_pos, false);
} else {
const EdgeGrid::Grid* edge_grid_ptr = (lower_layer_edge_grid && *lower_layer_edge_grid)
? lower_layer_edge_grid->get()
: nullptr;
Point seam = m_seam_placer.get_seam(*m_layer, seam_position, loop,
last_pos, EXTRUDER_CONFIG(nozzle_diameter),
(m_layer == NULL ? nullptr : m_layer->object()),
was_clockwise, edge_grid_ptr);
// Split the loop at the point with a minium penalty.
if (!loop.split_at_vertex(seam))
// The point is not in the original loop. Insert it.
loop.split_at(seam, true);
} }
else
m_seam_placer.place_seam(loop, this->last_pos(), m_config.external_perimeters_first,
EXTRUDER_CONFIG(nozzle_diameter), lower_layer_edge_grid ? lower_layer_edge_grid->get() : nullptr);
// clip the path to avoid the extruder to get exactly on the first point of the loop; // 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 // if polyline was shorter than the clipping distance we'd get a null polyline, so
@ -2652,6 +2644,16 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector<Obje
for (const ObjectByExtruder::Island::Region &region : by_region) for (const ObjectByExtruder::Island::Region &region : by_region)
if (! region.perimeters.empty()) { if (! region.perimeters.empty()) {
m_config.apply(print.get_print_region(&region - &by_region.front()).config()); m_config.apply(print.get_print_region(&region - &by_region.front()).config());
// plan_perimeters tries to place seams, it needs to have the lower_layer_edge_grid calculated already.
if (m_layer->lower_layer && ! lower_layer_edge_grid)
lower_layer_edge_grid = calculate_layer_edge_grid(*m_layer->lower_layer);
m_seam_placer.plan_perimeters(std::vector<const ExtrusionEntity*>(region.perimeters.begin(), region.perimeters.end()),
*m_layer, m_config.seam_position, this->last_pos(), EXTRUDER_CONFIG(nozzle_diameter),
(m_layer == NULL ? nullptr : m_layer->object()),
(lower_layer_edge_grid ? lower_layer_edge_grid.get() : nullptr));
for (const ExtrusionEntity* ee : region.perimeters) for (const ExtrusionEntity* ee : region.perimeters)
gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid); gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid);
} }

View File

@ -292,11 +292,165 @@ void SeamPlacer::init(const Print& print)
Point SeamPlacer::get_seam(const Layer& layer, const SeamPosition seam_position, void SeamPlacer::plan_perimeters(const std::vector<const ExtrusionEntity*> perimeters,
const ExtrusionLoop& loop, Point last_pos, coordf_t nozzle_dmr, const Layer& layer, SeamPosition seam_position,
const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid) Point last_pos, coordf_t nozzle_dmr, const PrintObject* po,
const EdgeGrid::Grid* lower_layer_edge_grid)
{ {
// When printing the perimeters, we want the seams on external and internal perimeters to match.
// We have a list of perimeters in the order to be printed. Each internal perimeter must inherit
// the seam from the previous external perimeter.
m_plan.clear();
m_plan_idx = 0;
if (perimeters.empty() || ! po)
return;
m_plan.resize(perimeters.size());
for (int i = 0; i < int(perimeters.size()); ++i) {
if (perimeters[i]->role() == erExternalPerimeter && perimeters[i]->is_loop()) {
last_pos = this->calculate_seam(
layer, seam_position, *dynamic_cast<const ExtrusionLoop*>(perimeters[i]), nozzle_dmr,
po, lower_layer_edge_grid, last_pos);
m_plan[i].external = true;
m_plan[i].seam_position = seam_position;
m_plan[i].layer = &layer;
m_plan[i].po = po;
}
m_plan[i].pt = last_pos;
}
}
void SeamPlacer::place_seam(ExtrusionLoop& loop, const Point& last_pos, bool external_first, double nozzle_diameter,
const EdgeGrid::Grid* lower_layer_edge_grid)
{
const double seam_offset = nozzle_diameter;
Point seam = last_pos;
if (! m_plan.empty() && m_plan_idx < m_plan.size()) {
if (m_plan[m_plan_idx].external) {
seam = m_plan[m_plan_idx].pt;
// One more heuristics: if the seam is too far from current nozzle position,
// try to place it again. This can happen in cases where the external perimeter
// does not belong to the preceding ones and they are ordered so they end up
// far from each other.
if ((seam.cast<double>() - last_pos.cast<double>()).squaredNorm() > std::pow(scale_(5.*nozzle_diameter), 2.))
seam = this->calculate_seam(*m_plan[m_plan_idx].layer, m_plan[m_plan_idx].seam_position, loop, nozzle_diameter,
m_plan[m_plan_idx].po, lower_layer_edge_grid, last_pos);
}
else if (! external_first) {
// Internal perimeter printed before the external.
// First get list of external seams.
std::vector<size_t> ext_seams;
for (size_t i = 0; i < m_plan.size(); ++i) {
if (m_plan[i].external)
ext_seams.emplace_back(i);
}
if (! ext_seams.empty()) {
// First find the line segment closest to an external seam:
int path_idx = 0;
int line_idx = 0;
size_t ext_seam_idx = size_t(-1);
double min_dist_sqr = std::numeric_limits<double>::max();
std::vector<Lines> lines_vect;
for (int i = 0; i < int(loop.paths.size()); ++i) {
lines_vect.emplace_back(loop.paths[i].polyline.lines());
const Lines& lines = lines_vect.back();
for (int j = 0; j < int(lines.size()); ++j) {
for (size_t k : ext_seams) {
double d_sqr = lines[j].distance_to_squared(m_plan[k].pt);
if (d_sqr < min_dist_sqr) {
path_idx = i;
line_idx = j;
ext_seam_idx = k;
min_dist_sqr = d_sqr;
}
}
}
}
// Only accept seam that is reasonably close.
double limit_dist_sqr = std::pow(double(scale_((ext_seam_idx - m_plan_idx) * nozzle_diameter * 2.)), 2.);
if (ext_seam_idx != size_t(-1) && min_dist_sqr < limit_dist_sqr) {
// Now find a projection of the external seam
const Lines& lines = lines_vect[path_idx];
Point closest = m_plan[ext_seam_idx].pt.projection_onto(lines[line_idx]);
double dist = (closest.cast<double>() - lines[line_idx].b.cast<double>()).norm();
// And walk along the perimeter until we make enough space for
// seams of all perimeters beforethe external one.
double offset = (ext_seam_idx - m_plan_idx) * scale_(seam_offset);
double last_offset = offset;
offset -= dist;
const Point* a = &closest;
const Point* b = &lines[line_idx].b;
while (++line_idx < int(lines.size()) && offset > 0.) {
last_offset = offset;
offset -= lines[line_idx].length();
a = &lines[line_idx].a;
b = &lines[line_idx].b;
}
// We have walked far enough, too far maybe. Interpolate on the
// last segment to find the end precisely.
offset = std::min(0., offset); // In case that offset is still positive (we may have "wrapped around")
double ratio = last_offset / (last_offset - offset);
seam = (a->cast<double>() + ((b->cast<double>() - a->cast<double>()) * ratio)).cast<coord_t>();
}
}
}
else {
// We should have a candidate ready from before. If not, use last_pos.
if (m_plan_idx > 0 && m_plan[m_plan_idx - 1].precalculated)
seam = m_plan[m_plan_idx - 1].pt;
}
}
// Split the loop at the point with a minium penalty.
if (!loop.split_at_vertex(seam))
// The point is not in the original loop. Insert it.
loop.split_at(seam, true);
if (external_first && m_plan_idx+1<m_plan.size() && ! m_plan[m_plan_idx+1].external) {
// Next perimeter should start near this one.
const double dist_sqr = std::pow(double(scale_(seam_offset)), 2.);
double running_sqr = 0.;
double running_sqr_last = 0.;
if (!loop.paths.empty() && loop.paths.back().polyline.points.size() > 1) {
const ExtrusionPath& last = loop.paths.back();
auto it = last.polyline.points.crbegin() + 1;
for (; it != last.polyline.points.crend(); ++it) {
running_sqr += (it->cast<double>() - (it - 1)->cast<double>()).squaredNorm();
if (running_sqr > dist_sqr)
break;
running_sqr_last = running_sqr;
}
if (running_sqr <= dist_sqr)
it = last.polyline.points.crend() - 1;
// Now interpolate.
double ratio = (std::sqrt(dist_sqr) - std::sqrt(running_sqr_last)) / (std::sqrt(running_sqr) - std::sqrt(running_sqr_last));
m_plan[m_plan_idx + 1].pt = ((it - 1)->cast<double>() + (it->cast<double>() - (it - 1)->cast<double>()) * std::min(ratio, 1.)).cast<coord_t>();
m_plan[m_plan_idx + 1].precalculated = true;
}
}
++m_plan_idx;
}
// Returns a seam for an EXTERNAL perimeter.
Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_position,
const ExtrusionLoop& loop, coordf_t nozzle_dmr, const PrintObject* po,
const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos)
{
assert(loop.role() == erExternalPerimeter);
Polygon polygon = loop.polygon(); Polygon polygon = loop.polygon();
bool was_clockwise = polygon.make_counter_clockwise();
BoundingBox polygon_bb = polygon.bounding_box(); BoundingBox polygon_bb = polygon.bounding_box();
const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
@ -438,7 +592,7 @@ Point SeamPlacer::get_seam(const Layer& layer, const SeamPosition seam_position,
} }
} }
if (seam_position == spAligned && loop.role() == erExternalPerimeter) if (seam_position == spAligned)
m_seam_history.add_seam(po, polygon.points[idx_min], polygon_bb); m_seam_history.add_seam(po, polygon.points[idx_min], polygon_bb);
@ -466,42 +620,8 @@ Point SeamPlacer::get_seam(const Layer& layer, const SeamPosition seam_position,
#endif #endif
return polygon.points[idx_min]; return polygon.points[idx_min];
} else { // spRandom } else
if (po->print()->default_region_config().external_perimeters_first) { return this->get_random_seam(layer_idx, polygon, po_idx);
if (loop.role() == erExternalPerimeter)
last_pos = this->get_random_seam(layer_idx, polygon, po_idx);
else {
// Internal perimeters will just use last_pos.
}
} else {
if (loop.loop_role() == elrContourInternalPerimeter && loop.role() != erExternalPerimeter) {
// This loop does not contain any other loop. Set a random position.
// The other loops will get a seam close to the random point chosen
// on the innermost contour.
last_pos = this->get_random_seam(layer_idx, polygon, po_idx);
m_last_loop_was_external = false;
}
if (loop.role() == erExternalPerimeter) {
if (m_last_loop_was_external) {
// There was no internal perimeter before this one.
last_pos = this->get_random_seam(layer_idx, polygon, po_idx);
} else {
if (is_custom_seam_on_layer(layer_idx, po_idx)) {
// There is a possibility that the loop will be influenced by custom
// seam enforcer/blocker. In this case do not inherit the seam
// from internal loops (which may conflict with the custom selection
// and generate another random one.
bool saw_custom = false;
Point candidate = this->get_random_seam(layer_idx, polygon, po_idx, &saw_custom);
if (saw_custom)
last_pos = candidate;
}
}
m_last_loop_was_external = true;
}
}
return last_pos;
}
} }

View File

@ -2,7 +2,9 @@
#define libslic3r_SeamPlacer_hpp_ #define libslic3r_SeamPlacer_hpp_
#include <optional> #include <optional>
#include <vector>
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/Polygon.hpp" #include "libslic3r/Polygon.hpp"
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
#include "libslic3r/BoundingBox.hpp" #include "libslic3r/BoundingBox.hpp"
@ -41,16 +43,30 @@ class SeamPlacer {
public: public:
void init(const Print& print); void init(const Print& print);
Point get_seam(const Layer& layer, const SeamPosition seam_position, // When perimeters are printed, first call this function with the respective
const ExtrusionLoop& loop, Point last_pos, // external perimeter. SeamPlacer will find a location for its seam and remember it.
coordf_t nozzle_diameter, const PrintObject* po, // Subsequent calls to get_seam will return this position.
bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid);
void plan_perimeters(const std::vector<const ExtrusionEntity*> perimeters,
const Layer& layer, SeamPosition seam_position,
Point last_pos, coordf_t nozzle_dmr, const PrintObject* po,
const EdgeGrid::Grid* lower_layer_edge_grid);
void place_seam(ExtrusionLoop& loop, const Point& last_pos, bool external_first, double nozzle_diameter,
const EdgeGrid::Grid* lower_layer_edge_grid);
using TreeType = AABBTreeIndirect::Tree<2, coord_t>; using TreeType = AABBTreeIndirect::Tree<2, coord_t>;
using AlignedBoxType = Eigen::AlignedBox<TreeType::CoordType, TreeType::NumDimensions>; using AlignedBoxType = Eigen::AlignedBox<TreeType::CoordType, TreeType::NumDimensions>;
private: private:
// When given an external perimeter (!), returns the seam.
Point calculate_seam(const Layer& layer, const SeamPosition seam_position,
const ExtrusionLoop& loop, coordf_t nozzle_dmr, const PrintObject* po,
const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos);
struct CustomTrianglesPerLayer { struct CustomTrianglesPerLayer {
Polygons polys; Polygons polys;
TreeType tree; TreeType tree;
@ -61,7 +77,16 @@ private:
coordf_t m_last_print_z = -1.; coordf_t m_last_print_z = -1.;
const PrintObject* m_last_po = nullptr; const PrintObject* m_last_po = nullptr;
bool m_last_loop_was_external = true; struct SeamPoint {
Point pt;
bool precalculated = false;
bool external = false;
const Layer* layer = nullptr;
SeamPosition seam_position;
const PrintObject* po = nullptr;
};
std::vector<SeamPoint> m_plan;
size_t m_plan_idx;
std::vector<std::vector<CustomTrianglesPerLayer>> m_enforcers; std::vector<std::vector<CustomTrianglesPerLayer>> m_enforcers;
std::vector<std::vector<CustomTrianglesPerLayer>> m_blockers; std::vector<std::vector<CustomTrianglesPerLayer>> m_blockers;

View File

@ -47,9 +47,9 @@ void MultiPoint::rotate(double angle, const Point &center)
double MultiPoint::length() const double MultiPoint::length() const
{ {
Lines lines = this->lines(); const Lines& lines = this->lines();
double len = 0; double len = 0;
for (Lines::iterator it = lines.begin(); it != lines.end(); ++it) { for (auto it = lines.cbegin(); it != lines.cend(); ++it) {
len += it->length(); len += it->length();
} }
return len; return len;

View File

@ -673,6 +673,7 @@ void GUI_App::post_init()
// to popup a modal dialog on start without screwing combo boxes. // to popup a modal dialog on start without screwing combo boxes.
// This is ugly but I honestly found no better way to do it. // This is ugly but I honestly found no better way to do it.
// Neither wxShowEvent nor wxWindowCreateEvent work reliably. // Neither wxShowEvent nor wxWindowCreateEvent work reliably.
assert(this->preset_updater); // FIXME Following condition is probably not neccessary.
if (this->preset_updater) { if (this->preset_updater) {
this->check_updates(false); this->check_updates(false);
CallAfter([this] { CallAfter([this] {