Merge branch 'lm_seam_fixes2'
This commit is contained in:
commit
e07a2434d3
@ -193,12 +193,8 @@ bool ExtrusionLoop::split_at_vertex(const Point &point)
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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)
|
||||
std::pair<size_t, Point> ExtrusionLoop::get_closest_path_and_point(const Point& point, bool prefer_non_overhang) const
|
||||
{
|
||||
if (this->paths.empty())
|
||||
return;
|
||||
|
||||
// Find the closest path and closest point belonging to that path. Avoid overhangs, if asked for.
|
||||
size_t path_idx = 0;
|
||||
Point p;
|
||||
@ -227,6 +223,16 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_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
|
||||
const ExtrusionPath &path = this->paths[path_idx];
|
||||
|
@ -258,6 +258,7 @@ public:
|
||||
double length() const override;
|
||||
bool split_at_vertex(const Point &point);
|
||||
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;
|
||||
// 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.
|
||||
|
@ -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
|
||||
// next copies (if any) would not detect the correct orientation
|
||||
auto out = make_unique<EdgeGrid::Grid>();
|
||||
|
||||
if (m_layer->lower_layer != nullptr && lower_layer_edge_grid != nullptr) {
|
||||
if (! *lower_layer_edge_grid) {
|
||||
// Create the distance field for a layer below.
|
||||
const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5);
|
||||
*lower_layer_edge_grid = make_unique<EdgeGrid::Grid>();
|
||||
(*lower_layer_edge_grid)->create(m_layer->lower_layer->lslices, distance_field_resolution);
|
||||
(*lower_layer_edge_grid)->calculate_sdf();
|
||||
out->create(layer.lslices, distance_field_resolution);
|
||||
out->calculate_sdf();
|
||||
#if 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++));
|
||||
}
|
||||
#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
|
||||
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
|
||||
// or randomize if requested
|
||||
Point last_pos = this->last_pos();
|
||||
if (m_config.spiral_vase) {
|
||||
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;
|
||||
// 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 ®ion : by_region)
|
||||
if (! region.perimeters.empty()) {
|
||||
m_config.apply(print.get_print_region(®ion - &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)
|
||||
gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid);
|
||||
}
|
||||
|
@ -292,11 +292,165 @@ void SeamPlacer::init(const Print& print)
|
||||
|
||||
|
||||
|
||||
Point SeamPlacer::get_seam(const Layer& layer, const SeamPosition seam_position,
|
||||
const ExtrusionLoop& loop, Point last_pos, coordf_t nozzle_dmr,
|
||||
const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid)
|
||||
void SeamPlacer::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)
|
||||
{
|
||||
// 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();
|
||||
bool was_clockwise = polygon.make_counter_clockwise();
|
||||
BoundingBox polygon_bb = polygon.bounding_box();
|
||||
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);
|
||||
|
||||
|
||||
@ -466,42 +620,8 @@ Point SeamPlacer::get_seam(const Layer& layer, const SeamPosition seam_position,
|
||||
#endif
|
||||
return polygon.points[idx_min];
|
||||
|
||||
} else { // spRandom
|
||||
if (po->print()->default_region_config().external_perimeters_first) {
|
||||
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;
|
||||
}
|
||||
} else
|
||||
return this->get_random_seam(layer_idx, polygon, po_idx);
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,7 +2,9 @@
|
||||
#define libslic3r_SeamPlacer_hpp_
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
@ -41,16 +43,30 @@ class SeamPlacer {
|
||||
public:
|
||||
void init(const Print& print);
|
||||
|
||||
Point get_seam(const Layer& layer, const SeamPosition seam_position,
|
||||
const ExtrusionLoop& loop, Point last_pos,
|
||||
coordf_t nozzle_diameter, const PrintObject* po,
|
||||
bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid);
|
||||
// When perimeters are printed, first call this function with the respective
|
||||
// external perimeter. SeamPlacer will find a location for its seam and remember it.
|
||||
// Subsequent calls to get_seam will return this position.
|
||||
|
||||
|
||||
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 AlignedBoxType = Eigen::AlignedBox<TreeType::CoordType, TreeType::NumDimensions>;
|
||||
|
||||
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 {
|
||||
Polygons polys;
|
||||
TreeType tree;
|
||||
@ -61,7 +77,16 @@ private:
|
||||
coordf_t m_last_print_z = -1.;
|
||||
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_blockers;
|
||||
|
@ -47,9 +47,9 @@ void MultiPoint::rotate(double angle, const Point ¢er)
|
||||
|
||||
double MultiPoint::length() const
|
||||
{
|
||||
Lines lines = this->lines();
|
||||
const Lines& lines = this->lines();
|
||||
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();
|
||||
}
|
||||
return len;
|
||||
|
@ -673,6 +673,7 @@ void GUI_App::post_init()
|
||||
// to popup a modal dialog on start without screwing combo boxes.
|
||||
// This is ugly but I honestly found no better way to do it.
|
||||
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
|
||||
assert(this->preset_updater); // FIXME Following condition is probably not neccessary.
|
||||
if (this->preset_updater) {
|
||||
this->check_updates(false);
|
||||
CallAfter([this] {
|
||||
|
Loading…
Reference in New Issue
Block a user