Fix of the previous Adaptive Cubic infill refactoring

plus couple of fixes of the old logic.
This commit is contained in:
Vojtech Bubnik 2020-11-06 16:24:15 +01:00
parent 139b58a6f2
commit 517477f0dd
4 changed files with 115 additions and 61 deletions

View file

@ -47,6 +47,7 @@ public:
void translate(coordf_t x, coordf_t y) { assert(this->defined); PointClass v(x, y); this->min += v; this->max += v; }
void translate(const Vec2d &v) { this->min += v; this->max += v; }
void offset(coordf_t delta);
BoundingBoxBase<PointClass> inflated(coordf_t delta) const throw() { BoundingBoxBase<PointClass> out(*this); out.offset(delta); return out; }
PointClass center() const;
bool contains(const PointClass &point) const {
return point(0) >= this->min(0) && point(0) <= this->max(0)
@ -91,6 +92,7 @@ public:
void translate(coordf_t x, coordf_t y, coordf_t z) { assert(this->defined); PointClass v(x, y, z); this->min += v; this->max += v; }
void translate(const Vec3d &v) { this->min += v; this->max += v; }
void offset(coordf_t delta);
BoundingBoxBase<PointClass> inflated(coordf_t delta) const throw() { BoundingBoxBase<PointClass> out(*this); out.offset(delta); return out; }
PointClass center() const;
coordf_t max_size() const;
@ -159,6 +161,8 @@ public:
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {}
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {}
BoundingBox inflated(coordf_t delta) const throw() { BoundingBox out(*this); out.offset(delta); return out; }
friend BoundingBox get_extents_rotated(const Points &points, double angle);
};

View file

@ -231,6 +231,10 @@ public:
std::pair<std::vector<std::pair<size_t, size_t>>::const_iterator, std::vector<std::pair<size_t, size_t>>::const_iterator> cell_data_range(coord_t row, coord_t col) const
{
assert(row >= 0);
assert(row < m_rows);
assert(col >= 0);
assert(col < m_cols);
const EdgeGrid::Grid::Cell &cell = m_cells[row * m_cols + col];
return std::make_pair(m_cell_data.begin() + cell.begin, m_cell_data.begin() + cell.end);
}

View file

@ -508,7 +508,7 @@ static void generate_infill_lines_recursive(
#endif
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines &polylines, const std::string &path)
static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines &polylines, const std::string &path, const Points &pts = Points())
{
BoundingBox bbox = get_extents(expoly);
bbox.offset(scale_(3.));
@ -518,7 +518,8 @@ static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines
svg.draw_outline(expoly, "green");
svg.draw(polylines, "red");
static constexpr double trim_length = scale_(0.4);
for (Polyline polyline : polylines) {
for (Polyline polyline : polylines)
if (! polyline.empty()) {
Vec2d a = polyline.points.front().cast<double>();
Vec2d d = polyline.points.back().cast<double>();
if (polyline.size() == 2) {
@ -550,6 +551,7 @@ static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines
}
svg.draw(polyline, "black");
}
svg.draw(pts, "magenta");
}
#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
@ -601,11 +603,11 @@ static inline Intersection *get_nearest_intersection(std::vector<std::pair<Inter
// Create a line representing the anchor aka hook extrusion based on line_to_offset
// translated in the direction of the intersection line (intersection.intersect_line).
static Line create_offset_line(const Line &line_to_offset, const Intersection &intersection, const double scaled_spacing)
static Line create_offset_line(const Line &line_to_offset, const Intersection &intersection, const double scaled_offset)
{
Vec2d dir = line_to_offset.vector().cast<double>().normalized();
// 50% overlap of the extrusion lines to achieve strong bonding.
Vec2d offset_vector = Vec2d(- dir.y(), dir.x()) * (scaled_spacing / 2.);
Vec2d offset_vector = Vec2d(- dir.y(), dir.x()) * scaled_offset;
const Point &furthest_point = (intersection.intersect_point == intersection.intersect_line.a ? intersection.intersect_line.b : intersection.intersect_line.a);
// Move inside.
@ -615,9 +617,7 @@ static Line create_offset_line(const Line &line_to_offset, const Intersection &i
Line offset_line = line_to_offset;
offset_line.translate(offset_vector.x(), offset_vector.y());
// Extend the line by a small value to guarantee a collision with adjacent lines
offset_line.extend(coord_t(scale_(1.)));
//FIXME scaled_spacing * tan(PI/6)
// offset_line.extend(coord_t(scaled_spacing * 0.577));
offset_line.extend(coord_t(scaled_offset * 1.16)); // / cos(PI/6)
return offset_line;
};
@ -641,12 +641,12 @@ static inline rtree_segment_t mk_rtree_seg(const Line &l) {
}
// Create a hook based on hook_line and append it to the begin or end of the polyline in the intersection
static void add_hook(const Intersection &intersection, const double scaled_spacing, const int hook_length, const rtree_t &rtree)
static void add_hook(const Intersection &intersection, const double scaled_offset, const int hook_length, const rtree_t &rtree)
{
// Trim the hook start by the infill line it will connect to.
Point hook_start;
bool intersection_found = intersection.intersect_line.intersection(
create_offset_line(intersection.closest_line, intersection, scaled_spacing),
create_offset_line(intersection.closest_line, intersection, scaled_offset),
&hook_start);
assert(intersection_found);
@ -729,17 +729,24 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
rtree.insert(std::make_pair(mk_rtree_seg(poly.points.front(), poly.points.back()), poly_idx++));
}
const float scaled_offset = float(scale_(spacing) * 0.7); // 30% overlap
std::vector<Intersection> intersections;
{
const coord_t scaled_spacing = coord_t(scale_(spacing));
// Keeping the vector of closest points outside the loop, so the vector does not need to be reallocated.
std::vector<std::pair<rtree_segment_t, size_t>> closest;
// Minimum lenght of an infill line to anchor. Very short lines cannot be trimmed from both sides,
// it does not help to anchor extremely short infill lines, it consumes too much plastic while not adding
// to the object rigidity.
const double line_len_threshold = scaled_offset * 4.;
// Minimum length of an infill line to be trimmed from both sides.
assert(line_len_threshold > scaled_offset * (2. / cos(PI / 6.)) + SCALED_EPSILON);
for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) {
Polyline &line = lines[line_idx];
// Lines shorter than spacing are skipped because it is needed to shrink a line by the value of spacing.
// A shorter line than spacing could produce a degenerate polyline.
//FIXME we should rather remove such short infill lines earlier!
if (line.length() <= (scaled_spacing + SCALED_EPSILON))
if (line.length() < line_len_threshold)
continue;
const Point &front_point = line.points.front();
@ -763,6 +770,17 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
}
}
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
static int iRun = 0;
int iStep = 0;
{
Points pts;
for (const Intersection &i : intersections)
pts.emplace_back(i.intersect_point);
export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-Tjoints-%d.svg", iRun++), pts);
}
#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
std::sort(intersections.begin(), intersections.end(),
[](const Intersection &i1, const Intersection &i2) { return i1.closest_line_idx < i2.closest_line_idx; });
@ -800,8 +818,11 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
intersection.intersect_pl = &lines[intersect_pl_idx];
// After polylines are merged, it is necessary to update "forward" based on if intersect_point is the first or the last point of intersect_pl.
if (intersection.fresh())
if (intersection.fresh()) {
assert(intersection.intersect_pl->points.front() == intersection.intersect_point ||
intersection.intersect_pl->points.back() == intersection.intersect_point);
intersection.front = intersection.intersect_pl->points.front() == intersection.intersect_point;
}
};
// Keep intersect_line outside the loop, so it does not get reallocated.
@ -811,19 +832,25 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
intersect_line.clear();
// All the nearest points (T-joints) ending at the same line are projected onto this line. Because of it, it can easily find the nearest point.
{
const Point &p0 = intersections[min_idx].intersect_point;
size_t max_idx = min_idx + 1;
intersect_line.emplace_back(&intersections[min_idx], 0.);
for (; max_idx < intersections.size() && intersections[min_idx].closest_line_idx == intersections[max_idx].closest_line_idx; ++max_idx)
intersect_line.emplace_back(&intersections[max_idx], line_dir.dot((intersections[max_idx].intersect_point - p0).cast<double>()));
size_t max_idx = min_idx;
for (; max_idx < intersections.size() && intersections[min_idx].closest_line_idx == intersections[max_idx].closest_line_idx; ++ max_idx)
intersect_line.emplace_back(&intersections[max_idx], line_dir.dot(intersections[max_idx].intersect_point.cast<double>()));
min_idx = max_idx;
}
if (intersect_line.size() == 1) {
// Simple case: The current intersection is the only one touching its adjacent line.
Intersection &first_i = *intersect_line.front().first;
update_merged_polyline(first_i);
if (first_i.fresh()) {
// Try to connect left or right. If not enough space for hook_length, take the longer side.
add_hook(first_i, scale_(spacing), hook_length, rtree);
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-add_hook0-pre-%d-%d.svg", iRun, iStep), { first_i.intersect_point });
#endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
add_hook(first_i, scaled_offset, hook_length, rtree);
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-add_hook0-pre-%d-%d.svg", iRun, iStep), { first_i.intersect_point });
++ iStep;
#endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
first_i.used = true;
}
continue;
@ -832,19 +859,23 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
assert(intersect_line.size() > 1);
// Sort the intersections along line_dir.
std::sort(intersect_line.begin(), intersect_line.end(), [](const auto &i1, const auto &i2) { return i1.second < i2.second; });
for (size_t first_idx = 0; first_idx < intersect_line.size(); ++ first_idx) {
Intersection &first_i = *intersect_line[first_idx].first;
update_merged_polyline(first_i);
if (! first_i.fresh())
// The intersection has been processed, or the polyline has been merged to another polyline.
continue;
// Get the previous or next intersection on the same line, pick the closer one.
if (first_idx > 0)
update_merged_polyline(*intersect_line[first_idx - 1].first);
if (first_idx + 1 < intersect_line.size())
update_merged_polyline(*intersect_line[first_idx + 1].first);
Intersection &nearest_i = *get_nearest_intersection(intersect_line, first_idx);
update_merged_polyline(first_i);
update_merged_polyline(nearest_i);
// A line between two intersections points
Line offset_line = create_offset_line(Line(first_i.intersect_point, nearest_i.intersect_point), first_i, scale_(spacing));
Line offset_line = create_offset_line(Line(first_i.intersect_point, nearest_i.intersect_point), first_i, scaled_offset);
// Check if both intersections lie on the offset_line and simultaneously get their points of intersecting.
// These points are used as start and end of the hook
Point first_i_point, nearest_i_point;
@ -860,9 +891,13 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
bgi::satisfies([&first_i, &nearest_i](const auto &item) { return item.second != first_i.intersect_line_idx && item.second != nearest_i.intersect_line_idx; }),
std::back_inserter(hook_intersections));
if (hook_intersections.empty()) {
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-connecting-pre-%d-%d.svg", iRun, iStep), { first_i.intersect_point, nearest_i.intersect_point });
#endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
// No other infill line intersects this anchor line. Extrude it as a whole.
if (first_i.intersect_pl == nearest_i.intersect_pl) {
// Both intersections are on the same polyline, that means a loop is being closed.
assert(first_i.front != nearest_i.front);
if (! first_i.front)
std::swap(first_i_point, nearest_i_point);
first_i.intersect_pl->points.front() = first_i_point;
@ -885,20 +920,33 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
// Keep the polyline at the lower index slot.
if (first_i.intersect_pl < nearest_i.intersect_pl) {
second_points.clear();
merged_with[nearest_i.intersect_pl - lines.data()] = merged_with[first_i.intersect_pl - lines.data()];
merged_with[nearest_i.intersect_pl - lines.data()] = first_i.intersect_pl - lines.data();
} else {
second_points = std::move(first_points);
first_points.clear();
merged_with[first_i.intersect_pl - lines.data()] = merged_with[nearest_i.intersect_pl - lines.data()];
merged_with[first_i.intersect_pl - lines.data()] = nearest_i.intersect_pl - lines.data();
}
}
nearest_i.used = true;
connected = true;
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-connecting-post-%d-%d.svg", iRun, iStep), { first_i.intersect_point, nearest_i.intersect_point });
#endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
}
}
if (! connected)
if (! connected) {
// Try to connect left or right. If not enough space for hook_length, take the longer side.
add_hook(first_i, scale_(spacing), hook_length, rtree);
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-add_hook-pre-%d-%d.svg", iRun, iStep), { first_i.intersect_point });
#endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
add_hook(first_i, scaled_offset, hook_length, rtree);
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-add_hook-pre-%d-%d.svg", iRun, iStep), { first_i.intersect_point });
#endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
}
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
++iStep;
#endif ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
first_i.used = true;
} else {
// The first & last point should always be found.

View file

@ -1056,7 +1056,8 @@ void mark_boundary_segments_touching_infill(
#endif
EdgeGrid::Grid grid;
grid.set_bbox(boundary_bbox);
// Make sure that the the grid is big enough for queries against the thick segment.
grid.set_bbox(boundary_bbox.inflated(distance_colliding + SCALED_EPSILON));
// Inflate the bounding box by a thick line width.
grid.create(boundary, std::max(clip_distance, distance_colliding) + scale_(10.));
@ -1213,15 +1214,12 @@ void mark_boundary_segments_touching_infill(
void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const double spacing, const FillParams &params, const int hook_length)
{
assert(! boundary_src.contour.points.empty());
BoundingBox bbox = get_extents(boundary_src.contour);
bbox.offset(SCALED_EPSILON);
auto polygons_src = reserve_vector<const Polygon*>(boundary_src.holes.size() + 1);
polygons_src.emplace_back(&boundary_src.contour);
for (const Polygon &polygon : boundary_src.holes)
polygons_src.emplace_back(&polygon);
connect_infill(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params, hook_length);
connect_infill(std::move(infill_ordered), polygons_src, get_extents(boundary_src.contour), polylines_out, spacing, params, hook_length);
}
void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams &params, const int hook_length)
@ -1255,7 +1253,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
std::vector<std::pair<EdgeGrid::Grid::ClosestPointResult, size_t>> intersection_points;
{
EdgeGrid::Grid grid;
grid.set_bbox(bbox);
grid.set_bbox(bbox.inflated(SCALED_EPSILON));
grid.create(boundary_src, scale_(10.));
intersection_points.reserve(infill_ordered.size() * 2);
for (const Polyline &pl : infill_ordered)