BambuStudio/libslic3r/Fill/FillBase.cpp

2649 lines
131 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <stdio.h>
#include <numeric>
#include "../ClipperUtils.hpp"
#include "../EdgeGrid.hpp"
#include "../Geometry.hpp"
#include "../Geometry/Circle.hpp"
#include "../Point.hpp"
#include "../PrintConfig.hpp"
#include "../Surface.hpp"
#include "../libslic3r.h"
#include "../VariableWidth.hpp"
#include "FillBase.hpp"
#include "FillConcentric.hpp"
#include "FillHoneycomb.hpp"
#include "Fill3DHoneycomb.hpp"
#include "FillGyroid.hpp"
#include "FillPlanePath.hpp"
#include "FillLine.hpp"
#include "FillRectilinear.hpp"
#include "FillAdaptive.hpp"
#include "FillLightning.hpp"
// BBS: new infill pattern header
#include "FillConcentricInternal.hpp"
#include "FillCrossHatch.hpp"
#include "Bridge.hpp"
// #define INFILL_DEBUG_OUTPUT
namespace Slic3r {
Fill* Fill::new_from_type(const InfillPattern type)
{
switch (type) {
case ipConcentric: return new FillConcentric();
case ipHoneycomb: return new FillHoneycomb();
case ip3DHoneycomb: return new Fill3DHoneycomb();
case ipGyroid: return new FillGyroid();
case ipRectilinear: return new FillRectilinear();
case ipAlignedRectilinear: return new FillAlignedRectilinear();
case ipCrossHatch: return new FillCrossHatch();
case ipMonotonic: return new FillMonotonic();
case ipLine: return new FillLine();
case ipGrid: return new FillGrid();
case ipTriangles: return new FillTriangles();
case ipStars: return new FillStars();
case ipCubic: return new FillCubic();
case ipArchimedeanChords: return new FillArchimedeanChords();
case ipHilbertCurve: return new FillHilbertCurve();
case ipOctagramSpiral: return new FillOctagramSpiral();
case ipAdaptiveCubic: return new FillAdaptive::Filler();
case ipSupportCubic: return new FillAdaptive::Filler();
case ipSupportBase: return new FillSupportBase();
case ipLightning: return new FillLightning::Filler();
// BBS: for internal solid infill only
case ipConcentricInternal: return new FillConcentricInternal();
// BBS: for bottom and top surface only
case ipMonotonicLine: return new FillMonotonicLineWGapFill();
//xiamian+
case ipFiberSpiral: return new Bridge();
default: throw Slic3r::InvalidArgument("unknown type");
}
}
Fill* Fill::new_from_type(const std::string &type)
{
const t_config_enum_values &enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values();
t_config_enum_values::const_iterator it = enum_keys_map.find(type);
return (it == enum_keys_map.end()) ? nullptr : new_from_type(InfillPattern(it->second));
}
// Force initialization of the Fill::use_bridge_flow() internal static map in a thread safe fashion even on compilers
// not supporting thread safe non-static data member initializers.
static bool use_bridge_flow_initializer = Fill::use_bridge_flow(ipGrid);
bool Fill::use_bridge_flow(const InfillPattern type)
{
static std::vector<unsigned char> cached;
if (cached.empty()) {
cached.assign(size_t(ipCount), 0);
for (size_t i = 0; i < cached.size(); ++ i) {
auto *fill = Fill::new_from_type((InfillPattern)i);
cached[i] = fill->use_bridge_flow();
delete fill;
}
}
return cached[type] != 0;
}
Polylines Fill::fill_surface(const Surface *surface, const FillParams &params)
{
// Perform offset.
//执行偏移。
Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing)));
// Create the infills for each of the regions.
//为每个区域创建填充。
Polylines polylines_out;
for (size_t i = 0; i < expp.size(); ++ i)
_fill_surface_single(
params,
surface->thickness_layers,
_infill_direction(surface),
std::move(expp[i]),
polylines_out);
return polylines_out;
}
ThickPolylines Fill::fill_surface_arachne(const Surface* surface, const FillParams& params)
{
// Perform offset.
Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing)));
// Create the infills for each of the regions.
ThickPolylines thick_polylines_out;
for (ExPolygon& expoly : expp)
_fill_surface_single(params, surface->thickness_layers, _infill_direction(surface), std::move(expoly), thick_polylines_out);
return thick_polylines_out;
}
// BBS: this method is used to fill the ExtrusionEntityCollection. It call fill_surface by default
//BBS此方法用于填充ExtrusionEntity集合。默认情况下它调用fill_surface
void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out)
{
Polylines polylines;
ThickPolylines thick_polylines;
try {
if (params.use_arachne)
thick_polylines = this->fill_surface_arachne(surface, params);
else
//调用各fill下的方法
polylines = this->fill_surface(surface, params);
}
catch (InfillFailedException&) {}
if (!polylines.empty() || !thick_polylines.empty()) {
//int key = 1;
//tbb::concurrent_hash_map<int, Point>::const_accessor accessor;
//if (!polylines.empty()) {
// for (auto& one : polylines) {
// //if (!pointMap.empty()) {
// tbb::concurrent_hash_map<int, Point>::const_accessor accessor1;
// if (pointMap.find(accessor1, key)) {
// //Point tempOne = pointMap.at(1);
// Point tempOne = accessor1 -> second;
// Point firstOne = one.first_point();
// double a1 = (firstOne - tempOne).norm();
// double a2 = (tempOne - firstOne).norm();
// if (a1 > 0 || a2 > 0) {
// one.append_before(tempOne);
// }
// }
// Point lastOne = one.last_point();
// pointMap.emplace(key, lastOne);
// }
//}
//if (!thick_polylines.empty()) {
// for (auto& two : thick_polylines) {
// //if (!pointMap.empty()) {
// tbb::concurrent_hash_map<int, Point>::const_accessor accessor2;
// if (pointMap.find(accessor2,key)){
// //if (it != pointMap.end()) {
// //Point tempTwo = pointMap.at(1);
// Point tempTwo = accessor2 -> second;
// Point firstTwo = two.first_point();
// double b1 = (firstTwo - tempTwo).norm();
// double b2 = (tempTwo - firstTwo).norm();
// if (b1 > 100 || b2 > 100) {
// two.append_before(tempTwo);
// }
// }
// Point lastTwo = two.last_point();
// pointMap.emplace(key, lastTwo);
// }
//}
// calculate actual flow from spacing (which might have been adjusted by the infill
// pattern generator)
//根据间距计算实际流量(可能已由填充生成器调整)
double flow_mm3_per_mm = params.flow.mm3_per_mm();
double flow_width = params.flow.width();
if (params.using_internal_flow) {
// if we used the internal flow we're not doing a solid infill
// so we can safely ignore the slight variation that might have
// been applied to f->spacing
//如果我们使用内部流我们就不会进行实心填充因此我们可以安全地忽略可能应用于f->间距的微小变化
}
else {
Flow new_flow = params.flow.with_spacing(this->spacing);
flow_mm3_per_mm = new_flow.mm3_per_mm();
flow_width = new_flow.width();
}
// Save into layer.
ExtrusionEntityCollection* eec = nullptr;
out.push_back(eec = new ExtrusionEntityCollection());
// Only concentric fills are not sorted.
//只有同心填充没有排序。
eec->no_sort = this->no_sort();
size_t idx = eec->entities.size();
if (params.use_arachne) {
Flow new_flow = params.flow.with_spacing(float(this->spacing));
variable_width(thick_polylines, params.extrusion_role, new_flow, eec->entities);
thick_polylines.clear();
}
else {
extrusion_entities_append_paths(
eec->entities, std::move(polylines),
params.extrusion_role,
flow_mm3_per_mm, float(flow_width), params.flow.height());
}
if (!params.can_reverse) {
for (size_t i = idx; i < eec->entities.size(); i++)
eec->entities[i]->set_reverse();
}
}
}
// Calculate a new spacing to fill width with possibly integer number of lines,
// the first and last line being centered at the interval ends.
// This function possibly increases the spacing, never decreases,
// and for a narrow width the increase in spacing may become severe,
// therefore the adjustment is limited to 20% increase.
//计算一个新的间距,用可能的整数行填充宽度,第一行和最后一行以间隔结束为中心。
//此功能可能会增加间距但不会减少对于窄宽度间距的增加可能会变得严重因此调整仅限于20%的增加。
coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance)
{
assert(width >= 0);
assert(distance > 0);
// floor(width / distance)
const auto number_of_intervals = coord_t((width - EPSILON) / distance);
coord_t distance_new = (number_of_intervals == 0) ?
distance :
coord_t((width - EPSILON) / number_of_intervals);
const coordf_t factor = coordf_t(distance_new) / coordf_t(distance);
assert(factor > 1. - 1e-5);
// How much could the extrusion width be increased? By 20%.
//挤压宽度可以增加多少20%。
const coordf_t factor_max = 1.2;
if (factor > factor_max)
distance_new = coord_t(floor((coordf_t(distance) * factor_max + 0.5)));
return distance_new;
}
// Returns orientation of the infill and the reference point of the infill pattern.
// For a normal print, the reference point is the center of a bounding box of the STL.
std::pair<float, Point> Fill::_infill_direction(const Surface *surface) const
{
// set infill angle
float out_angle = this->angle;
if (out_angle == FLT_MAX) {
//FIXME Vojtech: Add a warning?
printf("Using undefined infill angle\n");
out_angle = 0.f;
}
// Bounding box is the bounding box of a perl object Slic3r::Print::Object (c++ object Slic3r::PrintObject)
// The bounding box is only undefined in unit tests.
Point out_shift = empty(this->bounding_box) ?
surface->expolygon.contour.bounding_box().center() :
this->bounding_box.center();
#if 0
if (empty(this->bounding_box)) {
printf("Fill::_infill_direction: empty bounding box!");
} else {
printf("Fill::_infill_direction: reference point %d, %d\n", out_shift.x, out_shift.y);
}
#endif
if (surface->bridge_angle >= 0) {
// use bridge angle
//FIXME Vojtech: Add a debugf?
// Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle);
#ifdef SLIC3R_DEBUG
printf("Filling bridge with angle %f\n", surface->bridge_angle);
#endif /* SLIC3R_DEBUG */
out_angle = float(surface->bridge_angle);
} else if (this->layer_id != size_t(-1)) {
// alternate fill direction
out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers);
} else {
// printf("Layer_ID undefined!\n");
}
out_angle += float(M_PI/2.);
return std::pair<float, Point>(out_angle, out_shift);
}
// A single T joint of an infill line to a closed contour or one of its holes.
struct ContourIntersectionPoint {
// Contour and point on a contour where an infill line is connected to.
size_t contour_idx;
size_t point_idx;
// Eucleidean parameter of point_idx along its contour.
double param;
// Other intersection points along the same contour. If there is only a single T-joint on a contour
// with an intersection line, then the prev_on_contour and next_on_contour remain nulls.
ContourIntersectionPoint* prev_on_contour { nullptr };
ContourIntersectionPoint* next_on_contour { nullptr };
// Length of the contour not yet allocated to some extrusion path going back (clockwise), or masked out by some overlapping infill line.
double contour_not_taken_length_prev { std::numeric_limits<double>::max() };
// Length of the contour not yet allocated to some extrusion path going forward (counter-clockwise), or masked out by some overlapping infill line.
double contour_not_taken_length_next { std::numeric_limits<double>::max() };
// End point is consumed if an infill line connected to this T-joint was already connected left or right along the contour,
// or if the infill line was processed, but it was not possible to connect it left or right along the contour.
bool consumed { false };
// Whether the contour was trimmed by an overlapping infill line, or whether part of this contour was connected to some infill line.
bool prev_trimmed { false };
bool next_trimmed { false };
void consume_prev() { this->contour_not_taken_length_prev = 0.; this->prev_trimmed = true; this->consumed = true; }
void consume_next() { this->contour_not_taken_length_next = 0.; this->next_trimmed = true; this->consumed = true; }
void trim_prev(const double new_len) {
if (new_len < this->contour_not_taken_length_prev) {
this->contour_not_taken_length_prev = new_len;
this->prev_trimmed = true;
}
}
void trim_next(const double new_len) {
if (new_len < this->contour_not_taken_length_next) {
this->contour_not_taken_length_next = new_len;
this->next_trimmed = true;
}
}
// The end point of an infill line connected to this T-joint was not processed yet and a piece of the contour could be extruded going backwards.
bool could_take_prev() const throw() { return ! this->consumed && this->contour_not_taken_length_prev > SCALED_EPSILON; }
// The end point of an infill line connected to this T-joint was not processed yet and a piece of the contour could be extruded going forward.
bool could_take_next() const throw() { return ! this->consumed && this->contour_not_taken_length_next > SCALED_EPSILON; }
// Could extrude a complete segment from this to this->prev_on_contour.
bool could_connect_prev() const throw()
{ return ! this->consumed && this->prev_on_contour != this && ! this->prev_on_contour->consumed && ! this->prev_trimmed && ! this->prev_on_contour->next_trimmed; }
// Could extrude a complete segment from this to this->next_on_contour.
bool could_connect_next() const throw()
{ return ! this->consumed && this->next_on_contour != this && ! this->next_on_contour->consumed && ! this->next_trimmed && ! this->next_on_contour->prev_trimmed; }
};
// Distance from param1 to param2 when going counter-clockwise.
static inline double closed_contour_distance_ccw(double param1, double param2, double contour_length)
{
assert(param1 >= 0. && param1 <= contour_length);
assert(param2 >= 0. && param2 <= contour_length);
double d = param2 - param1;
if (d < 0.)
d += contour_length;
return d;
}
// Distance from param1 to param2 when going clockwise.
static inline double closed_contour_distance_cw(double param1, double param2, double contour_length)
{
return closed_contour_distance_ccw(param2, param1, contour_length);
}
// Length along the contour from cp1 to cp2 going counter-clockwise.
double path_length_along_contour_ccw(const ContourIntersectionPoint *cp1, const ContourIntersectionPoint *cp2, double contour_length)
{
assert(cp1 != nullptr);
assert(cp2 != nullptr);
assert(cp1->contour_idx == cp2->contour_idx);
assert(cp1 != cp2);
return closed_contour_distance_ccw(cp1->param, cp2->param, contour_length);
}
// Lengths along the contour from cp1 to cp2 going CCW and going CW.
std::pair<double, double> path_lengths_along_contour(const ContourIntersectionPoint *cp1, const ContourIntersectionPoint *cp2, double contour_length)
{
// Zero'th param is the length of the contour.
double param_lo = cp1->param;
double param_hi = cp2->param;
assert(param_lo >= 0. && param_lo <= contour_length);
assert(param_hi >= 0. && param_hi <= contour_length);
bool reversed = false;
if (param_lo > param_hi) {
std::swap(param_lo, param_hi);
reversed = true;
}
auto out = std::make_pair(param_hi - param_lo, param_lo + contour_length - param_hi);
if (reversed)
std::swap(out.first, out.second);
return out;
}
// Add contour points from interval (idx_start, idx_end> to polyline.
static inline void take_cw_full(Polyline &pl, const Points &contour, size_t idx_start, size_t idx_end)
{
assert(! pl.empty() && pl.points.back() == contour[idx_start]);
size_t i = (idx_start == 0) ? contour.size() - 1 : idx_start - 1;
while (i != idx_end) {
pl.points.emplace_back(contour[i]);
if (i == 0)
i = contour.size();
-- i;
}
pl.points.emplace_back(contour[i]);
}
// Add contour points from interval (idx_start, idx_end> to polyline, limited by the Eucleidean length taken.
static inline double take_cw_limited(Polyline &pl, const Points &contour, const std::vector<double> &params, size_t idx_start, size_t idx_end, double length_to_take)
{
// If appending to an infill line, then the start point of a perimeter line shall match the end point of an infill line.
assert(pl.empty() || pl.points.back() == contour[idx_start]);
assert(contour.size() + 1 == params.size());
assert(length_to_take > SCALED_EPSILON);
// Length of the contour.
double length = params.back();
// Parameter (length from contour.front()) for the first point.
double p0 = params[idx_start];
// Current (2nd) point of the contour.
size_t i = (idx_start == 0) ? contour.size() - 1 : idx_start - 1;
// Previous point of the contour.
size_t iprev = idx_start;
// Length of the contour curve taken for iprev.
double lprev = 0.;
for (;;) {
double l = closed_contour_distance_cw(p0, params[i], length);
if (l >= length_to_take) {
// Trim the last segment.
double t = double(length_to_take - lprev) / (l - lprev);
pl.points.emplace_back(lerp(contour[iprev], contour[i], t));
return length_to_take;
}
// Continue with the other segments.
pl.points.emplace_back(contour[i]);
if (i == idx_end)
return l;
iprev = i;
lprev = l;
if (i == 0)
i = contour.size();
-- i;
}
assert(false);
return 0;
}
// Add contour points from interval (idx_start, idx_end> to polyline.
static inline void take_ccw_full(Polyline &pl, const Points &contour, size_t idx_start, size_t idx_end)
{
assert(! pl.empty() && pl.points.back() == contour[idx_start]);
size_t i = idx_start;
if (++ i == contour.size())
i = 0;
while (i != idx_end) {
pl.points.emplace_back(contour[i]);
if (++ i == contour.size())
i = 0;
}
pl.points.emplace_back(contour[i]);
}
// Add contour points from interval (idx_start, idx_end> to polyline, limited by the Eucleidean length taken.
// Returns length of the contour taken.
static inline double take_ccw_limited(Polyline &pl, const Points &contour, const std::vector<double> &params, size_t idx_start, size_t idx_end, double length_to_take)
{
// If appending to an infill line, then the start point of a perimeter line shall match the end point of an infill line.
assert(pl.empty() || pl.points.back() == contour[idx_start]);
assert(contour.size() + 1 == params.size());
assert(length_to_take > SCALED_EPSILON);
// Length of the contour.
double length = params.back();
// Parameter (length from contour.front()) for the first point.
double p0 = params[idx_start];
// Current (2nd) point of the contour.
size_t i = idx_start;
if (++ i == contour.size())
i = 0;
// Previous point of the contour.
size_t iprev = idx_start;
// Length of the contour curve taken at iprev.
double lprev = 0;
for (;;) {
double l = closed_contour_distance_ccw(p0, params[i], length);
if (l >= length_to_take) {
// Trim the last segment.
double t = double(length_to_take - lprev) / (l - lprev);
pl.points.emplace_back(lerp(contour[iprev], contour[i], t));
return length_to_take;
}
// Continue with the other segments.
pl.points.emplace_back(contour[i]);
if (i == idx_end)
return l;
iprev = i;
lprev = l;
if (++ i == contour.size())
i = 0;
}
assert(false);
return 0;
}
// Connect end of pl1 to the start of pl2 using the perimeter contour.
// If clockwise, then a clockwise segment from idx_start to idx_end is taken, otherwise a counter-clockwise segment is being taken.
static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, size_t idx_start, size_t idx_end, bool clockwise)
{
#ifndef NDEBUG
assert(idx_start != idx_end);
assert(pl1.size() >= 2);
assert(pl2.size() >= 2);
#endif /* NDEBUG */
{
// Reserve memory at pl1 for the connecting contour and pl2.
int new_points = int(idx_end) - int(idx_start) - 1;
if (new_points < 0)
new_points += int(contour.size());
pl1.points.reserve(pl1.points.size() + size_t(new_points) + pl2.points.size());
}
if (clockwise)
take_cw_full(pl1, contour, idx_start, idx_end);
else
take_ccw_full(pl1, contour, idx_start, idx_end);
pl1.points.insert(pl1.points.end(), pl2.points.begin() + 1, pl2.points.end());
}
static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise)
{
assert(cp_start->prev_on_contour != nullptr);
assert(cp_start->next_on_contour != nullptr);
assert(cp_end ->prev_on_contour != nullptr);
assert(cp_end ->next_on_contour != nullptr);
assert(cp_start != cp_end);
take(pl1, pl2, contour, cp_start->point_idx, cp_end->point_idx, clockwise);
// Mark the contour segments in between cp_start and cp_end as consumed.
if (clockwise)
std::swap(cp_start, cp_end);
if (cp_start->next_on_contour != cp_end)
for (auto *cp = cp_start->next_on_contour; cp->next_on_contour != cp_end; cp = cp->next_on_contour) {
cp->consume_prev();
cp->consume_next();
}
cp_start->consume_next();
cp_end->consume_prev();
}
static void take_limited(
Polyline &pl1, const Points &contour, const std::vector<double> &params,
ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise, double take_max_length, double line_half_width)
{
#ifndef NDEBUG
// This is a valid case, where a single infill line connect to two different contours (outer contour + hole or two holes).
// assert(cp_start != cp_end);
assert(cp_start->prev_on_contour != nullptr);
assert(cp_start->next_on_contour != nullptr);
assert(cp_end ->prev_on_contour != nullptr);
assert(cp_end ->next_on_contour != nullptr);
assert(pl1.size() >= 2);
assert(contour.size() + 1 == params.size());
#endif /* NDEBUG */
if (! (clockwise ? cp_start->could_take_prev() : cp_start->could_take_next()))
return;
assert(pl1.points.front() == contour[cp_start->point_idx] || pl1.points.back() == contour[cp_start->point_idx]);
bool add_at_start = pl1.points.front() == contour[cp_start->point_idx];
Points pl_tmp;
if (add_at_start) {
pl_tmp = std::move(pl1.points);
pl1.points.clear();
}
{
// Reserve memory at pl1 for the perimeter segment.
// Pessimizing - take the complete segment.
int new_points = int(cp_end->point_idx) - int(cp_start->point_idx) - 1;
if (new_points < 0)
new_points += int(contour.size());
pl1.points.reserve(pl1.points.size() + pl_tmp.size() + size_t(new_points));
}
double length = params.back();
double length_to_go = take_max_length;
cp_start->consumed = true;
if (cp_start == cp_end) {
length_to_go = std::max(0., std::min(length_to_go, length - line_half_width));
length_to_go = std::min(length_to_go, clockwise ? cp_start->contour_not_taken_length_prev : cp_start->contour_not_taken_length_next);
cp_start->consume_prev();
cp_start->consume_next();
if (length_to_go > SCALED_EPSILON)
clockwise ?
take_cw_limited (pl1, contour, params, cp_start->point_idx, cp_start->point_idx, length_to_go) :
take_ccw_limited(pl1, contour, params, cp_start->point_idx, cp_start->point_idx, length_to_go);
} else if (clockwise) {
// Going clockwise from cp_start to cp_end.
assert(cp_start != cp_end);
for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->prev_on_contour) {
// Length of the segment from cp to cp->prev_on_contour.
double l = closed_contour_distance_cw(cp->param, cp->prev_on_contour->param, length);
length_to_go = std::min(length_to_go, cp->contour_not_taken_length_prev);
//if (cp->prev_on_contour->consumed)
// Don't overlap with an already extruded infill line.
length_to_go = std::max(0., std::min(length_to_go, l - line_half_width));
cp->consume_prev();
if (l >= length_to_go) {
if (length_to_go > SCALED_EPSILON) {
cp->prev_on_contour->trim_next(l - length_to_go);
take_cw_limited(pl1, contour, params, cp->point_idx, cp->prev_on_contour->point_idx, length_to_go);
}
break;
} else {
cp->prev_on_contour->trim_next(0.);
take_cw_full(pl1, contour, cp->point_idx, cp->prev_on_contour->point_idx);
length_to_go -= l;
}
}
} else {
assert(cp_start != cp_end);
for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->next_on_contour) {
double l = closed_contour_distance_ccw(cp->param, cp->next_on_contour->param, length);
length_to_go = std::min(length_to_go, cp->contour_not_taken_length_next);
//if (cp->next_on_contour->consumed)
// Don't overlap with an already extruded infill line.
length_to_go = std::max(0., std::min(length_to_go, l - line_half_width));
cp->consume_next();
if (l >= length_to_go) {
if (length_to_go > SCALED_EPSILON) {
cp->next_on_contour->trim_prev(l - length_to_go);
take_ccw_limited(pl1, contour, params, cp->point_idx, cp->next_on_contour->point_idx, length_to_go);
}
break;
} else {
cp->next_on_contour->trim_prev(0.);
take_ccw_full(pl1, contour, cp->point_idx, cp->next_on_contour->point_idx);
length_to_go -= l;
}
}
}
if (add_at_start) {
pl1.reverse();
append(pl1.points, pl_tmp);
}
}
// Return an index of start of a segment and a point of the clipping point at distance from the end of polyline.
struct SegmentPoint {
// Segment index, defining a line <idx_segment, idx_segment + 1).
size_t idx_segment = std::numeric_limits<size_t>::max();
// Parameter of point in <0, 1) along the line <idx_segment, idx_segment + 1)
double t;
Vec2d point;
bool valid() const { return idx_segment != std::numeric_limits<size_t>::max(); }
};
static inline SegmentPoint clip_start_segment_and_point(const Points &polyline, double distance)
{
assert(polyline.size() >= 2);
assert(distance > 0.);
// Initialized to "invalid".
SegmentPoint out;
if (polyline.size() >= 2) {
Vec2d pt_prev = polyline.front().cast<double>();
for (size_t i = 1; i < polyline.size(); ++ i) {
Vec2d pt = polyline[i].cast<double>();
Vec2d v = pt - pt_prev;
double l = v.norm();
if (l > distance) {
out.idx_segment = i - 1;
out.t = distance / l;
out.point = pt_prev + out.t * v;
break;
}
distance -= l;
pt_prev = pt;
}
}
return out;
}
static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, double distance)
{
assert(polyline.size() >= 2);
assert(distance > 0.);
// Initialized to "invalid".
SegmentPoint out;
if (polyline.size() >= 2) {
Vec2d pt_next = polyline.back().cast<double>();
for (int i = int(polyline.size()) - 2; i >= 0; -- i) {
Vec2d pt = polyline[i].cast<double>();
Vec2d v = pt - pt_next;
double l = v.norm();
if (l > distance) {
out.idx_segment = i;
out.t = distance / l;
out.point = pt_next + out.t * v;
// Store the parameter referenced to the starting point of a segment.
out.t = 1. - out.t;
break;
}
distance -= l;
pt_next = pt;
}
}
return out;
}
// Calculate intersection of a line with a thick segment.
// Returns Eucledian parameters of the line / thick segment overlap.
static inline bool line_rounded_thick_segment_collision(
const Vec2d &line_a, const Vec2d &line_b,
const Vec2d &segment_a, const Vec2d &segment_b, const double offset,
std::pair<double, double> &out_interval)
{
const Vec2d line_v0 = line_b - line_a;
double lv = line_v0.squaredNorm();
const Vec2d segment_v = segment_b - segment_a;
const double segment_l = segment_v.norm();
const double offset2 = offset * offset;
bool intersects = false;
if (lv < SCALED_EPSILON * SCALED_EPSILON)
{
// Very short line vector. Just test whether the center point is inside the offset line.
Vec2d lpt = 0.5 * (line_a + line_b);
if (segment_l > SCALED_EPSILON) {
intersects = line_alg::distance_to_squared(Linef{ segment_a, segment_b }, lpt) < offset2;
} else
intersects = (0.5 * (segment_a + segment_b) - lpt).squaredNorm() < offset2;
if (intersects) {
out_interval.first = 0.;
out_interval.second = sqrt(lv);
}
}
else
{
// Output interval.
double tmin = std::numeric_limits<double>::max();
double tmax = -tmin;
auto extend_interval = [&tmin, &tmax](double atmin, double atmax) {
tmin = std::min(tmin, atmin);
tmax = std::max(tmax, atmax);
};
// Intersections with the inflated segment end points.
auto ray_circle_intersection_interval_extend = [&extend_interval](const Vec2d &segment_pt, const double offset2, const Vec2d &line_pt, const Vec2d &line_vec) {
std::pair<Vec2d, Vec2d> pts;
Vec2d p0 = line_pt - segment_pt;
double lv2 = line_vec.squaredNorm();
if (Geometry::ray_circle_intersections_r2_lv2_c(offset2, line_vec.y(), - line_vec.x(), lv2, - line_vec.y() * p0.x() + line_vec.x() * p0.y(), pts)) {
double tmin = (pts.first - p0).dot(line_vec) / lv2;
double tmax = (pts.second - p0).dot(line_vec) / lv2;
if (tmin > tmax)
std::swap(tmin, tmax);
tmin = std::max(tmin, 0.);
tmax = std::min(tmax, 1.);
if (tmin <= tmax)
extend_interval(tmin, tmax);
}
};
// Intersections with the inflated segment.
if (segment_l > SCALED_EPSILON) {
ray_circle_intersection_interval_extend(segment_a, offset2, line_a, line_v0);
ray_circle_intersection_interval_extend(segment_b, offset2, line_a, line_v0);
// Clip the line segment transformed into a coordinate space of the segment,
// where the segment spans (0, 0) to (segment_l, 0).
const Vec2d dir_x = segment_v / segment_l;
const Vec2d dir_y(- dir_x.y(), dir_x.x());
const Vec2d line_p0(line_a - segment_a);
std::pair<double, double> interval;
if (Geometry::liang_barsky_line_clipping_interval(
Vec2d(line_p0.dot(dir_x), line_p0.dot(dir_y)),
Vec2d(line_v0.dot(dir_x), line_v0.dot(dir_y)),
BoundingBoxf(Vec2d(0., - offset), Vec2d(segment_l, offset)),
interval))
extend_interval(interval.first, interval.second);
} else
ray_circle_intersection_interval_extend(0.5 * (segment_a + segment_b), offset, line_a, line_v0);
intersects = tmin <= tmax;
if (intersects) {
lv = sqrt(lv);
out_interval.first = tmin * lv;
out_interval.second = tmax * lv;
}
}
#if 0
{
BoundingBox bbox;
bbox.merge(line_a.cast<coord_t>());
bbox.merge(line_a.cast<coord_t>());
bbox.merge(segment_a.cast<coord_t>());
bbox.merge(segment_b.cast<coord_t>());
static int iRun = 0;
::Slic3r::SVG svg(debug_out_path("%s-%03d.svg", "line-thick-segment-intersect", iRun ++), bbox);
svg.draw(Line(line_a.cast<coord_t>(), line_b.cast<coord_t>()), "black");
svg.draw(Line(segment_a.cast<coord_t>(), segment_b.cast<coord_t>()), "blue", offset * 2.);
svg.draw(segment_a.cast<coord_t>(), "blue", offset);
svg.draw(segment_b.cast<coord_t>(), "blue", offset);
svg.draw(Line(segment_a.cast<coord_t>(), segment_b.cast<coord_t>()), "black");
if (intersects)
svg.draw(Line((line_a + (line_b - line_a).normalized() * out_interval.first).cast<coord_t>(),
(line_a + (line_b - line_a).normalized() * out_interval.second).cast<coord_t>()), "red");
}
#endif
return intersects;
}
#ifndef NDEBUG
static inline bool inside_interval(double low, double high, double p)
{
return p >= low && p <= high;
}
static inline bool interval_inside_interval(double outer_low, double outer_high, double inner_low, double inner_high, double epsilon)
{
outer_low -= epsilon;
outer_high += epsilon;
return inside_interval(outer_low, outer_high, inner_low) && inside_interval(outer_low, outer_high, inner_high);
}
static inline bool cyclic_interval_inside_interval(double outer_low, double outer_high, double inner_low, double inner_high, double length)
{
if (outer_low > outer_high)
outer_high += length;
if (inner_low > inner_high)
inner_high += length;
else if (inner_high < outer_low) {
inner_low += length;
inner_high += length;
}
return interval_inside_interval(outer_low, outer_high, inner_low, inner_high, double(SCALED_EPSILON));
}
#endif // NDEBUG
#ifdef INFILL_DEBUG_OUTPUT
static void export_infill_to_svg(
// Boundary contour, along which the perimeter extrusions will be drawn.
const std::vector<Points> &boundary,
// Parametrization of boundary with Euclidian length.
const std::vector<std::vector<double>> &boundary_parameters,
// Intersections (T-joints) of the infill lines with the boundary.
std::vector<std::vector<ContourIntersectionPoint*>> &boundary_intersections,
// Infill lines, either completely inside the boundary, or touching the boundary.
const Polylines &infill,
const coord_t scaled_spacing,
const std::string &path,
const Polylines &overlap_lines = Polylines(),
const Polylines &polylines = Polylines(),
const Points &pts = Points())
{
Polygons polygons;
std::transform(boundary.begin(), boundary.end(), std::back_inserter(polygons), [](auto &pts) { return Polygon(pts); });
ExPolygons expolygons = union_ex(polygons);
BoundingBox bbox = get_extents(polygons);
bbox.offset(scale_(3.));
::Slic3r::SVG svg(path, bbox);
// Draw the filled infill polygons.
svg.draw(expolygons);
// Draw the pieces of boundary allowed to be used as anchors of infill lines, not yet consumed.
const std::string color_boundary_trimmed = "blue";
const std::string color_boundary_not_trimmed = "yellow";
const coordf_t boundary_line_width = scaled_spacing;
svg.draw_outline(polygons, "red", boundary_line_width);
for (const std::vector<ContourIntersectionPoint*> &intersections : boundary_intersections) {
const size_t boundary_idx = &intersections - boundary_intersections.data();
const Points &contour = boundary[boundary_idx];
const std::vector<double> &contour_param = boundary_parameters[boundary_idx];
for (const ContourIntersectionPoint *ip : intersections) {
assert(ip->next_trimmed == ip->next_on_contour->prev_trimmed);
assert(ip->prev_trimmed == ip->prev_on_contour->next_trimmed);
{
Polyline pl { contour[ip->point_idx] };
if (ip->next_trimmed) {
if (ip->contour_not_taken_length_next > SCALED_EPSILON) {
take_ccw_limited(pl, contour, contour_param, ip->point_idx, ip->next_on_contour->point_idx, ip->contour_not_taken_length_next);
svg.draw(pl, color_boundary_trimmed, boundary_line_width);
}
} else {
take_ccw_full(pl, contour, ip->point_idx, ip->next_on_contour->point_idx);
svg.draw(pl, color_boundary_not_trimmed, boundary_line_width);
}
}
{
Polyline pl { contour[ip->point_idx] };
if (ip->prev_trimmed) {
if (ip->contour_not_taken_length_prev > SCALED_EPSILON) {
take_cw_limited(pl, contour, contour_param, ip->point_idx, ip->prev_on_contour->point_idx, ip->contour_not_taken_length_prev);
svg.draw(pl, color_boundary_trimmed, boundary_line_width);
}
} else {
take_cw_full(pl, contour, ip->point_idx, ip->prev_on_contour->point_idx);
svg.draw(pl, color_boundary_not_trimmed, boundary_line_width);
}
}
}
}
// Draw the full infill polygon boundary.
svg.draw_outline(polygons, "green");
// Draw the infill lines, first the full length with red color, then a slightly shortened length with black color.
svg.draw(infill, "brown");
static constexpr double trim_length = scale_(0.15);
for (Polyline polyline : infill)
if (! polyline.empty()) {
Vec2d a = polyline.points.front().cast<double>();
Vec2d d = polyline.points.back().cast<double>();
if (polyline.size() == 2) {
Vec2d v = d - a;
double l = v.norm();
if (l > 2. * trim_length) {
a += v * trim_length / l;
d -= v * trim_length / l;
polyline.points.front() = a.cast<coord_t>();
polyline.points.back() = d.cast<coord_t>();
} else
polyline.points.clear();
} else if (polyline.size() > 2) {
Vec2d b = polyline.points[1].cast<double>();
Vec2d c = polyline.points[polyline.points.size() - 2].cast<double>();
Vec2d v = b - a;
double l = v.norm();
if (l > trim_length) {
a += v * trim_length / l;
polyline.points.front() = a.cast<coord_t>();
} else
polyline.points.erase(polyline.points.begin());
v = d - c;
l = v.norm();
if (l > trim_length)
polyline.points.back() = (d - v * trim_length / l).cast<coord_t>();
else
polyline.points.pop_back();
}
svg.draw(polyline, "black");
}
svg.draw(overlap_lines, "red", scale_(0.05));
svg.draw(polylines, "magenta", scale_(0.05));
svg.draw(pts, "magenta");
}
#endif // INFILL_DEBUG_OUTPUT
#ifndef NDEBUG
bool validate_boundary_intersections(const std::vector<std::vector<ContourIntersectionPoint*>> &boundary_intersections)
{
for (const std::vector<ContourIntersectionPoint*>& contour : boundary_intersections) {
for (ContourIntersectionPoint* ip : contour) {
assert(ip->next_trimmed == ip->next_on_contour->prev_trimmed);
assert(ip->prev_trimmed == ip->prev_on_contour->next_trimmed);
}
}
return true;
}
#endif // NDEBUG
// Mark the segments of split boundary as consumed if they are very close to some of the infill line.
void mark_boundary_segments_touching_infill(
// Boundary contour, along which the perimeter extrusions will be drawn.
const std::vector<Points> &boundary,
// Parametrization of boundary with Euclidian length.
const std::vector<std::vector<double>> &boundary_parameters,
// Intersections (T-joints) of the infill lines with the boundary.
std::vector<std::vector<ContourIntersectionPoint*>> &boundary_intersections,
// Bounding box around the boundary.
const BoundingBox &boundary_bbox,
// Infill lines, either completely inside the boundary, or touching the boundary.
const Polylines &infill,
// How much of the infill ends should be ignored when marking the boundary segments?
const double clip_distance,
// Roughly width of the infill line.
const double distance_colliding)
{
assert(boundary.size() == boundary_parameters.size());
#ifndef NDEBUG
for (size_t i = 0; i < boundary.size(); ++ i)
assert(boundary[i].size() + 1 == boundary_parameters[i].size());
assert(validate_boundary_intersections(boundary_intersections));
#endif
#ifdef INFILL_DEBUG_OUTPUT
static int iRun = 0;
++ iRun;
int iStep = 0;
export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-start", iRun));
Polylines perimeter_overlaps;
#endif // INFILL_DEBUG_OUTPUT
EdgeGrid::Grid grid;
// Make sure that the the grid is big enough for queries against the thick segment.
grid.set_bbox(boundary_bbox.inflated(distance_colliding * 1.43));
// Inflate the bounding box by a thick line width.
grid.create(boundary, coord_t(std::max(clip_distance, distance_colliding) + scale_(10.)));
// Visitor for the EdgeGrid to trim boundary_intersections with existing infill lines.
struct Visitor {
Visitor(const EdgeGrid::Grid &grid,
const std::vector<Points> &boundary, const std::vector<std::vector<double>> &boundary_parameters, std::vector<std::vector<ContourIntersectionPoint*>> &boundary_intersections,
const double radius) :
grid(grid), boundary(boundary), boundary_parameters(boundary_parameters), boundary_intersections(boundary_intersections), radius(radius), trim_l_threshold(0.5 * radius) {}
// Init with a segment of an infill line.
void init(const Vec2d &infill_pt1, const Vec2d &infill_pt2) {
this->infill_pt1 = &infill_pt1;
this->infill_pt2 = &infill_pt2;
this->infill_bbox.reset();
this->infill_bbox.merge(infill_pt1);
this->infill_bbox.merge(infill_pt2);
this->infill_bbox.offset(this->radius + SCALED_EPSILON);
}
bool operator()(coord_t iy, coord_t ix) {
// Called with a row and colum of the grid cell, which is intersected by a line.
auto cell_data_range = this->grid.cell_data_range(iy, ix);
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
// End points of the line segment and their vector.
auto segment = this->grid.segment(*it_contour_and_segment);
std::vector<ContourIntersectionPoint*> &intersections = boundary_intersections[it_contour_and_segment->first];
if (intersections.empty())
// There is no infil line touching this contour, thus effort will be saved to calculate overlap with other infill lines.
continue;
const Vec2d seg_pt1 = segment.first.cast<double>();
const Vec2d seg_pt2 = segment.second.cast<double>();
std::pair<double, double> interval;
BoundingBoxf bbox_seg;
bbox_seg.merge(seg_pt1);
bbox_seg.merge(seg_pt2);
#ifdef INFILL_DEBUG_OUTPUT
//if (this->infill_bbox.overlap(bbox_seg)) this->perimeter_overlaps.push_back({ segment.first, segment.second });
#endif // INFILL_DEBUG_OUTPUT
if (this->infill_bbox.overlap(bbox_seg) && line_rounded_thick_segment_collision(seg_pt1, seg_pt2, *this->infill_pt1, *this->infill_pt2, this->radius, interval)) {
// The boundary segment intersects with the infill segment thickened by radius.
// Interval is specified in Euclidian length from seg_pt1 to seg_pt2.
// 1) Find the Euclidian parameters of seg_pt1 and seg_pt2 on its boundary contour.
const std::vector<double> &contour_parameters = boundary_parameters[it_contour_and_segment->first];
const double contour_length = contour_parameters.back();
const double param_seg_pt1 = contour_parameters[it_contour_and_segment->second];
const double param_seg_pt2 = contour_parameters[it_contour_and_segment->second + 1];
#ifdef INFILL_DEBUG_OUTPUT
this->perimeter_overlaps.push_back({ Point((seg_pt1 + (seg_pt2 - seg_pt1).normalized() * interval.first).cast<coord_t>()),
Point((seg_pt1 + (seg_pt2 - seg_pt1).normalized() * interval.second).cast<coord_t>()) });
#endif // INFILL_DEBUG_OUTPUT
assert(interval.first >= 0.);
assert(interval.second >= 0.);
assert(interval.first <= interval.second);
const auto param_overlap1 = std::min(param_seg_pt2, param_seg_pt1 + interval.first);
const auto param_overlap2 = std::min(param_seg_pt2, param_seg_pt1 + interval.second);
// 2) Find the ContourIntersectionPoints before param_overlap1 and after param_overlap2.
// Find the span of ContourIntersectionPoints, that is trimmed by the interval (param_overlap1, param_overlap2).
ContourIntersectionPoint *ip_low, *ip_high;
if (intersections.size() == 1) {
// Only a single infill line touches this contour.
ip_low = ip_high = intersections.front();
} else {
assert(intersections.size() > 1);
auto it_low = Slic3r::lower_bound_by_predicate(intersections.begin(), intersections.end(), [param_overlap1](const ContourIntersectionPoint *l) { return l->param < param_overlap1; });
auto it_high = Slic3r::lower_bound_by_predicate(intersections.begin(), intersections.end(), [param_overlap2](const ContourIntersectionPoint *l) { return l->param < param_overlap2; });
ip_low = it_low == intersections.end() ? intersections.front() : *it_low;
ip_high = it_high == intersections.end() ? intersections.front() : *it_high;
if (ip_low->param != param_overlap1)
ip_low = ip_low->prev_on_contour;
assert(ip_low != ip_high);
// Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param).
assert(cyclic_interval_inside_interval(ip_low->param, ip_high->param, param_overlap1, param_overlap2, contour_length));
}
assert(validate_boundary_intersections(boundary_intersections));
// Mark all ContourIntersectionPoints between ip_low and ip_high as consumed.
if (ip_low->next_on_contour != ip_high)
for (ContourIntersectionPoint *ip = ip_low->next_on_contour; ip != ip_high; ip = ip->next_on_contour) {
ip->consume_prev();
ip->consume_next();
}
// Subtract the interval from the first and last segments.
double trim_l = closed_contour_distance_ccw(ip_low->param, param_overlap1, contour_length);
//if (trim_l > trim_l_threshold)
ip_low->trim_next(trim_l);
trim_l = closed_contour_distance_ccw(param_overlap2, ip_high->param, contour_length);
//if (trim_l > trim_l_threshold)
ip_high->trim_prev(trim_l);
assert(ip_low->next_trimmed == ip_high->prev_trimmed);
assert(validate_boundary_intersections(boundary_intersections));
//FIXME mark point as consumed?
//FIXME verify the sequence between prev and next?
#ifdef INFILL_DEBUG_OUTPUT
{
#if 0
static size_t iRun = 0;
ExPolygon expoly(Polygon(*grid.contours().front()));
for (size_t i = 1; i < grid.contours().size(); ++i)
expoly.holes.emplace_back(Polygon(*grid.contours()[i]));
SVG svg(debug_out_path("%s-%d.svg", "FillBase-mark_boundary_segments_touching_infill", iRun ++).c_str(), get_extents(expoly));
svg.draw(expoly, "green");
svg.draw(Line(segment.first, segment.second), "red");
svg.draw(Line(this->infill_pt1->cast<coord_t>(), this->infill_pt2->cast<coord_t>()), "magenta");
#endif
}
#endif // INFILL_DEBUG_OUTPUT
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid &grid;
const std::vector<Points> &boundary;
const std::vector<std::vector<double>> &boundary_parameters;
std::vector<std::vector<ContourIntersectionPoint*>> &boundary_intersections;
// Maximum distance between the boundary and the infill line allowed to consider the boundary not touching the infill line.
const double radius;
// Region around the contour / infill line intersection point, where the intersections are ignored.
const double trim_l_threshold;
const Vec2d *infill_pt1;
const Vec2d *infill_pt2;
BoundingBoxf infill_bbox;
#ifdef INFILL_DEBUG_OUTPUT
Polylines perimeter_overlaps;
#endif // INFILL_DEBUG_OUTPUT
} visitor(grid, boundary, boundary_parameters, boundary_intersections, distance_colliding);
for (const Polyline &polyline : infill) {
#ifdef INFILL_DEBUG_OUTPUT
++ iStep;
#endif // INFILL_DEBUG_OUTPUT
// Clip the infill polyline by the Eucledian distance along the polyline.
SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance);
SegmentPoint end_point = clip_end_segment_and_point(polyline.points, clip_distance);
if (start_point.valid() && end_point.valid() &&
(start_point.idx_segment < end_point.idx_segment || (start_point.idx_segment == end_point.idx_segment && start_point.t < end_point.t))) {
// The clipped polyline is non-empty.
#ifdef INFILL_DEBUG_OUTPUT
visitor.perimeter_overlaps.clear();
#endif // INFILL_DEBUG_OUTPUT
for (size_t point_idx = start_point.idx_segment; point_idx <= end_point.idx_segment; ++ point_idx) {
//FIXME extend the EdgeGrid to suport tracing a thick line.
#if 0
Point pt1, pt2;
Vec2d pt1d, pt2d;
if (point_idx == start_point.idx_segment) {
pt1d = start_point.point;
pt1 = pt1d.cast<coord_t>();
} else {
pt1 = polyline.points[point_idx];
pt1d = pt1.cast<double>();
}
if (point_idx == start_point.idx_segment) {
pt2d = end_point.point;
pt2 = pt1d.cast<coord_t>();
} else {
pt2 = polyline.points[point_idx];
pt2d = pt2.cast<double>();
}
visitor.init(pt1d, pt2d);
grid.visit_cells_intersecting_thick_line(pt1, pt2, distance_colliding, visitor);
#else
Vec2d pt1 = (point_idx == start_point.idx_segment) ? start_point.point : polyline.points[point_idx ].cast<double>();
Vec2d pt2 = (point_idx == end_point .idx_segment) ? end_point .point : polyline.points[point_idx + 1].cast<double>();
#if 0
{
static size_t iRun = 0;
ExPolygon expoly(Polygon(*grid.contours().front()));
for (size_t i = 1; i < grid.contours().size(); ++i)
expoly.holes.emplace_back(Polygon(*grid.contours()[i]));
SVG svg(debug_out_path("%s-%d.svg", "FillBase-mark_boundary_segments_touching_infill0", iRun ++).c_str(), get_extents(expoly));
svg.draw(expoly, "green");
svg.draw(polyline, "blue");
svg.draw(Line(pt1.cast<coord_t>(), pt2.cast<coord_t>()), "magenta", scale_(0.1));
}
#endif
visitor.init(pt1, pt2);
// Simulate tracing of a thick line. This only works reliably if distance_colliding <= grid cell size.
Vec2d v = (pt2 - pt1).normalized() * distance_colliding;
Vec2d vperp = perp(v);
Vec2d a = pt1 - v - vperp;
Vec2d b = pt2 + v - vperp;
assert(grid.bbox().contains(a.cast<coord_t>()));
assert(grid.bbox().contains(b.cast<coord_t>()));
grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
a = pt1 - v + vperp;
b = pt2 + v + vperp;
assert(grid.bbox().contains(a.cast<coord_t>()));
assert(grid.bbox().contains(b.cast<coord_t>()));
grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
#endif
#ifdef INFILL_DEBUG_OUTPUT
// export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d-%03d-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-step", iRun, iStep, int(point_idx)), { polyline });
#endif // INFILL_DEBUG_OUTPUT
}
#ifdef INFILL_DEBUG_OUTPUT
Polylines perimeter_overlaps;
export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-step", iRun, iStep), visitor.perimeter_overlaps, { polyline });
append(perimeter_overlaps, std::move(visitor.perimeter_overlaps));
perimeter_overlaps.clear();
#endif // INFILL_DEBUG_OUTPUT
}
}
#ifdef INFILL_DEBUG_OUTPUT
export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-end", iRun), perimeter_overlaps);
#endif // INFILL_DEBUG_OUTPUT
assert(validate_boundary_intersections(boundary_intersections));
}
void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const double spacing, const FillParams &params)
{
assert(! boundary_src.contour.points.empty());
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, get_extents(boundary_src.contour), polylines_out, spacing, params);
}
void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams &params)
{
auto polygons_src = reserve_vector<const Polygon*>(boundary_src.size());
for (const Polygon &polygon : boundary_src)
polygons_src.emplace_back(&polygon);
connect_infill(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params);
}
static constexpr auto boundary_idx_unconnected = std::numeric_limits<size_t>::max();
struct BoundaryInfillGraph
{
std::vector<Points> boundary;
std::vector<std::vector<double>> boundary_params;
std::vector<ContourIntersectionPoint> map_infill_end_point_to_boundary;
const Point& point(const ContourIntersectionPoint &cp) const {
assert(cp.contour_idx != size_t(-1));
assert(cp.point_idx != size_t(-1));
return this->boundary[cp.contour_idx][cp.point_idx];
}
const Point& infill_end_point(size_t infill_end_point_idx) const {
return this->point(this->map_infill_end_point_to_boundary[infill_end_point_idx]);
}
const Point interpolate_contour_point(const ContourIntersectionPoint &cp, double param) {
const Points &contour = this->boundary[cp.contour_idx];
const std::vector<double> &contour_params = this->boundary_params[cp.contour_idx];
// Find the start of a contour segment with param.
auto it = std::lower_bound(contour_params.begin(), contour_params.end(), param);
if (*it != param) {
assert(it != contour_params.begin());
-- it;
}
size_t i = it - contour_params.begin();
if (i == contour.size())
i = 0;
double t1 = contour_params[i];
double t2 = next_value_modulo(i, contour_params);
return lerp(contour[i], next_value_modulo(i, contour), (param - t1) / (t2 - t1));
}
enum Direction {
Left,
Right,
Up,
Down,
Taken,
};
static Direction dir(const Point &p1, const Point &p2) {
return p1.x() == p2.x() ?
(p1.y() < p2.y() ? Up : Down) :
(p1.x() < p2.x() ? Right : Left);
}
const Direction dir_prev(const ContourIntersectionPoint &cp) const {
assert(cp.prev_on_contour);
return cp.could_take_prev() ?
dir(this->point(cp), this->point(*cp.prev_on_contour)) :
Taken;
}
const Direction dir_next(const ContourIntersectionPoint &cp) const {
assert(cp.next_on_contour);
return cp.could_take_next() ?
dir(this->point(cp), this->point(*cp.next_on_contour)) :
Taken;
}
bool first(const ContourIntersectionPoint &cp) const {
return ((&cp - this->map_infill_end_point_to_boundary.data()) & 1) == 0;
}
const ContourIntersectionPoint& other(const ContourIntersectionPoint &cp) const {
return this->map_infill_end_point_to_boundary[((&cp - this->map_infill_end_point_to_boundary.data()) ^ 1)];
}
ContourIntersectionPoint& other(const ContourIntersectionPoint &cp) {
return this->map_infill_end_point_to_boundary[((&cp - this->map_infill_end_point_to_boundary.data()) ^ 1)];
}
bool prev_vertical(const ContourIntersectionPoint &cp) const {
return this->point(cp).x() == this->point(*cp.prev_on_contour).x();
}
bool next_vertical(const ContourIntersectionPoint &cp) const {
return this->point(cp).x() == this->point(*cp.next_on_contour).x();
}
};
// After mark_boundary_segments_touching_infill() marks boundary segments overlapping trimmed infill lines,
// there are possibly some very short boundary segments unmarked, but overlapping the untrimmed infill lines fully
// Mark those short boundary segments.
static inline void mark_boundary_segments_overlapping_infill(
BoundaryInfillGraph &graph,
// Infill lines, either completely inside the boundary, or touching the boundary.
const Polylines &infill,
// Spacing (width) of the infill lines.
const double spacing)
{
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
const Points &contour = graph.boundary[cp.contour_idx];
const std::vector<double> &contour_params = graph.boundary_params[cp.contour_idx];
const Polyline &infill_polyline = infill[(&cp - graph.map_infill_end_point_to_boundary.data()) / 2];
const double radius = 0.5 * (spacing + SCALED_EPSILON);
assert(infill_polyline.size() == 2);
const Linef infill_line { infill_polyline.points.front().cast<double>(), infill_polyline.points.back().cast<double>() };
if (cp.could_take_next()) {
bool inside = true;
for (size_t i = cp.point_idx; i != cp.next_on_contour->point_idx; ) {
size_t j = next_idx_modulo(i, contour);
const Vec2d seg_pt2 = contour[j].cast<double>();
if (line_alg::distance_to_squared(infill_line, seg_pt2) < radius * radius) {
// The segment is completely inside.
} else {
std::pair<double, double> interval;
line_rounded_thick_segment_collision(contour[i].cast<double>(), seg_pt2, infill_line.a, infill_line.b, radius, interval);
assert(interval.first == 0.);
double len_out = closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second;
if (len_out < cp.contour_not_taken_length_next) {
// Leaving the infill line region before exiting cp.contour_not_taken_length_next,
// thus at least some of the contour is outside and we will extrude this segment.
inside = false;
break;
}
}
if (closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[j], contour_params.back()) >= cp.contour_not_taken_length_next)
break;
i = j;
}
if (inside) {
if (! cp.next_trimmed)
// The arc from cp to cp.next_on_contour was not trimmed yet, however it is completely overlapping the infill line.
cp.next_on_contour->trim_prev(0);
cp.trim_next(0);
}
} else
cp.trim_next(0);
if (cp.could_take_prev()) {
bool inside = true;
for (size_t i = cp.point_idx; i != cp.prev_on_contour->point_idx; ) {
size_t j = prev_idx_modulo(i, contour);
const Vec2d seg_pt2 = contour[j].cast<double>();
// Distance of the second segment line from the infill line.
if (line_alg::distance_to_squared(infill_line, seg_pt2) < radius * radius) {
// The segment is completely inside.
} else {
std::pair<double, double> interval;
line_rounded_thick_segment_collision(contour[i].cast<double>(), seg_pt2, infill_line.a, infill_line.b, radius, interval);
assert(interval.first == 0.);
double len_out = closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second;
if (len_out < cp.contour_not_taken_length_prev) {
// Leaving the infill line region before exiting cp.contour_not_taken_length_next,
// thus at least some of the contour is outside and we will extrude this segment.
inside = false;
break;
}
}
if (closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[j], contour_params.back()) >= cp.contour_not_taken_length_prev)
break;
i = j;
}
if (inside) {
if (! cp.prev_trimmed)
// The arc from cp to cp.prev_on_contour was not trimmed yet, however it is completely overlapping the infill line.
cp.prev_on_contour->trim_next(0);
cp.trim_prev(0);
}
} else
cp.trim_prev(0);
}
}
BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered, const std::vector<const Polygon*> &boundary_src, const BoundingBox &bbox, const double spacing)
{
BoundaryInfillGraph out;
out.boundary.assign(boundary_src.size(), Points());
out.boundary_params.assign(boundary_src.size(), std::vector<double>());
out.map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, ContourIntersectionPoint{ boundary_idx_unconnected, boundary_idx_unconnected });
{
// Project the infill_ordered end points onto boundary_src.
std::vector<std::pair<EdgeGrid::Grid::ClosestPointResult, size_t>> intersection_points;
{
EdgeGrid::Grid grid;
grid.set_bbox(bbox.inflated(SCALED_EPSILON));
grid.create(boundary_src, coord_t(scale_(10.)));
intersection_points.reserve(infill_ordered.size() * 2);
for (const Polyline &pl : infill_ordered)
for (const Point *pt : { &pl.points.front(), &pl.points.back() }) {
EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(*pt, coord_t(SCALED_EPSILON));
if (cp.valid()) {
// The infill end point shall lie on the contour.
assert(cp.distance <= 3.);
intersection_points.emplace_back(cp, (&pl - infill_ordered.data()) * 2 + (pt == &pl.points.front() ? 0 : 1));
}
}
std::sort(intersection_points.begin(), intersection_points.end(), [](const std::pair<EdgeGrid::Grid::ClosestPointResult, size_t> &cp1, const std::pair<EdgeGrid::Grid::ClosestPointResult, size_t> &cp2) {
return cp1.first.contour_idx < cp2.first.contour_idx ||
(cp1.first.contour_idx == cp2.first.contour_idx &&
(cp1.first.start_point_idx < cp2.first.start_point_idx ||
(cp1.first.start_point_idx == cp2.first.start_point_idx && cp1.first.t < cp2.first.t)));
});
}
auto it = intersection_points.begin();
auto it_end = intersection_points.end();
std::vector<std::vector<ContourIntersectionPoint*>> boundary_intersection_points(out.boundary.size(), std::vector<ContourIntersectionPoint*>());
for (size_t idx_contour = 0; idx_contour < boundary_src.size(); ++ idx_contour) {
// Copy contour_src to contour_dst while adding intersection points.
// Map infill end points map_infill_end_point_to_boundary to the newly inserted boundary points of contour_dst.
// chain the points of map_infill_end_point_to_boundary along their respective contours.
const Polygon &contour_src = *boundary_src[idx_contour];
Points &contour_dst = out.boundary[idx_contour];
std::vector<ContourIntersectionPoint*> &contour_intersection_points = boundary_intersection_points[idx_contour];
ContourIntersectionPoint *pfirst = nullptr;
ContourIntersectionPoint *pprev = nullptr;
{
// Reserve intersection points.
size_t n_intersection_points = 0;
for (auto itx = it; itx != it_end && itx->first.contour_idx == idx_contour; ++ itx)
++ n_intersection_points;
contour_intersection_points.reserve(n_intersection_points);
}
for (size_t idx_point = 0; idx_point < contour_src.points.size(); ++ idx_point) {
const Point &ipt = contour_src.points[idx_point];
if (contour_dst.empty() || contour_dst.back() != ipt)
contour_dst.emplace_back(ipt);
for (; it != it_end && it->first.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) {
// Add these points to the destination contour.
const Polyline &infill_line = infill_ordered[it->second / 2];
const Point &pt = (it->second & 1) ? infill_line.points.back() : infill_line.points.front();
//#ifndef NDEBUG
// {
// const Vec2d pt1 = ipt.cast<double>();
// const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast<double>();
// const Vec2d ptx = lerp(pt1, pt2, it->first.t);
// assert(std::abs(ptx.x() - pt.x()) < SCALED_EPSILON);
// assert(std::abs(ptx.y() - pt.y()) < SCALED_EPSILON);
// }
//#endif // NDEBUG
size_t idx_tjoint_pt = 0;
if (idx_point + 1 < contour_src.size() || pt != contour_dst.front()) {
if (pt != contour_dst.back())
contour_dst.emplace_back(pt);
idx_tjoint_pt = contour_dst.size() - 1;
}
out.map_infill_end_point_to_boundary[it->second] = ContourIntersectionPoint{ /* it->second, */ idx_contour, idx_tjoint_pt };
ContourIntersectionPoint *pthis = &out.map_infill_end_point_to_boundary[it->second];
if (pprev) {
pprev->next_on_contour = pthis;
pthis->prev_on_contour = pprev;
} else
pfirst = pthis;
contour_intersection_points.emplace_back(pthis);
pprev = pthis;
}
if (pfirst) {
pprev->next_on_contour = pfirst;
pfirst->prev_on_contour = pprev;
}
}
// Parametrize the new boundary with the intersection points inserted.
std::vector<double> &contour_params = out.boundary_params[idx_contour];
contour_params.assign(contour_dst.size() + 1, 0.);
for (size_t i = 1; i < contour_dst.size(); ++i) {
contour_params[i] = contour_params[i - 1] + (contour_dst[i].cast<double>() - contour_dst[i - 1].cast<double>()).norm();
assert(contour_params[i] > contour_params[i - 1]);
}
contour_params.back() = contour_params[contour_params.size() - 2] + (contour_dst.back().cast<double>() - contour_dst.front().cast<double>()).norm();
assert(contour_params.back() > contour_params[contour_params.size() - 2]);
// Map parameters from contour_params to boundary_intersection_points.
for (ContourIntersectionPoint *ip : contour_intersection_points)
ip->param = contour_params[ip->point_idx];
// and measure distance to the previous and next intersection point.
const double contour_length = contour_params.back();
for (ContourIntersectionPoint *ip : contour_intersection_points)
if (ip->next_on_contour == ip) {
assert(ip->prev_on_contour == ip);
ip->contour_not_taken_length_prev = ip->contour_not_taken_length_next = contour_length;
} else {
assert(ip->prev_on_contour != ip);
ip->contour_not_taken_length_prev = closed_contour_distance_ccw(ip->prev_on_contour->param, ip->param, contour_length);
ip->contour_not_taken_length_next = closed_contour_distance_ccw(ip->param, ip->next_on_contour->param, contour_length);
}
}
assert(out.boundary.size() == boundary_src.size());
#if 0
// Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary.
assert(std::all_of(out.map_infill_end_point_to_boundary.begin(), out.map_infill_end_point_to_boundary.end(),
[&out.boundary](const ContourIntersectionPoint &contour_point) {
return contour_point.contour_idx < out.boundary.size() && contour_point.point_idx < out.boundary[contour_point.contour_idx].size();
}));
#endif
// Mark the points and segments of split out.boundary as consumed if they are very close to some of the infill line.
{
// @supermerill used 2. * scale_(spacing)
const double clip_distance = 1.7 * scale_(spacing);
// Allow a bit of overlap. This value must be slightly higher than the overlap of FillAdaptive, otherwise
// the anchors of the adaptive infill will mask the other side of the perimeter line.
// (see connect_lines_using_hooks() in FillAdaptive.cpp)
const double distance_colliding = 0.8 * scale_(spacing);
mark_boundary_segments_touching_infill(out.boundary, out.boundary_params, boundary_intersection_points, bbox, infill_ordered, clip_distance, distance_colliding);
}
}
return out;
}
void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams &params)
{
assert(! infill_ordered.empty());
assert(params.anchor_length >= 0.);
assert(params.anchor_length_max >= 0.01f);
assert(params.anchor_length_max >= params.anchor_length);
const double anchor_length = scale_(params.anchor_length);
const double anchor_length_max = scale_(params.anchor_length_max);
#if 0
append(polylines_out, infill_ordered);
return;
#endif
BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing);
std::vector<size_t> merged_with(infill_ordered.size());
std::iota(merged_with.begin(), merged_with.end(), 0);
auto get_and_update_merged_with = [&merged_with](size_t polyline_idx) -> size_t {
for (size_t last = polyline_idx;;) {
size_t lower = merged_with[last];
assert(lower <= last);
if (lower == last) {
merged_with[polyline_idx] = last;
return last;
}
last = lower;
}
assert(false);
return std::numeric_limits<size_t>::max();
};
const double line_half_width = 0.5 * scale_(spacing);
#if 0
// Connection from end of one infill line to the start of another infill line.
//const double length_max = scale_(spacing);
// const auto length_max = double(scale_((2. / params.density) * spacing));
const auto length_max = double(scale_((1000. / params.density) * spacing));
struct ConnectionCost {
ConnectionCost(size_t idx_first, double cost, bool reversed) : idx_first(idx_first), cost(cost), reversed(reversed) {}
size_t idx_first;
double cost;
bool reversed;
};
std::vector<ConnectionCost> connections_sorted;
connections_sorted.reserve(infill_ordered.size() * 2 - 2);
for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) {
const ContourIntersectionPoint *cp1 = &graph.map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1];
const ContourIntersectionPoint *cp2 = &graph.map_infill_end_point_to_boundary[idx_chain * 2];
if (cp1->contour_idx != boundary_idx_unconnected && cp1->contour_idx == cp2->contour_idx) {
// End points on the same contour. Try to connect them.
std::pair<double, double> len = path_lengths_along_contour(cp1, cp2, graph.boundary_params[cp1->contour_idx].back());
if (len.first < length_max)
connections_sorted.emplace_back(idx_chain - 1, len.first, false);
if (len.second < length_max)
connections_sorted.emplace_back(idx_chain - 1, len.second, true);
}
}
std::sort(connections_sorted.begin(), connections_sorted.end(), [](const ConnectionCost& l, const ConnectionCost& r) { return l.cost < r.cost; });
for (ConnectionCost &connection_cost : connections_sorted) {
ContourIntersectionPoint *cp1 = &graph.map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1];
ContourIntersectionPoint *cp2 = &graph.map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2];
assert(cp1 != cp2);
assert(cp1->contour_idx == cp2->contour_idx && cp1->contour_idx != boundary_idx_unconnected);
if (cp1->consumed || cp2->consumed)
continue;
const double length = connection_cost.cost;
bool could_connect;
{
// cp1, cp2 sorted CCW.
ContourIntersectionPoint *cp_low = connection_cost.reversed ? cp2 : cp1;
ContourIntersectionPoint *cp_high = connection_cost.reversed ? cp1 : cp2;
assert(std::abs(length - closed_contour_distance_ccw(cp_low->param, cp_high->param, graph.boundary_params[cp1->contour_idx].back())) < SCALED_EPSILON);
could_connect = ! cp_low->next_trimmed && ! cp_high->prev_trimmed;
if (could_connect && cp_low->next_on_contour != cp_high) {
// Other end of cp1, may or may not be on the same contour as cp1.
const ContourIntersectionPoint *cp1prev = cp1 - 1;
// Other end of cp2, may or may not be on the same contour as cp2.
const ContourIntersectionPoint *cp2next = cp2 + 1;
for (auto *cp = cp_low->next_on_contour; cp != cp_high; cp = cp->next_on_contour)
if (cp->consumed || cp == cp1prev || cp == cp2next || cp->prev_trimmed || cp->next_trimmed) {
could_connect = false;
break;
}
}
}
// Indices of the polylines to be connected by a perimeter segment.
size_t idx_first = connection_cost.idx_first;
size_t idx_second = idx_first + 1;
idx_first = get_and_update_merged_with(idx_first);
assert(idx_first < idx_second);
assert(idx_second == merged_with[idx_second]);
if (could_connect && length < anchor_length_max) {
// Take the complete contour.
// Connect the two polygons using the boundary contour.
take(infill_ordered[idx_first], infill_ordered[idx_second], graph.boundary[cp1->contour_idx], cp1, cp2, connection_cost.reversed);
// Mark the second polygon as merged with the first one.
merged_with[idx_second] = merged_with[idx_first];
infill_ordered[idx_second].points.clear();
} else {
// Try to connect cp1 resp. cp2 with a piece of perimeter line.
take_limited(infill_ordered[idx_first], graph.boundary[cp1->contour_idx], graph.boundary_params[cp1->contour_idx], cp1, cp2, connection_cost.reversed, anchor_length, line_half_width);
take_limited(infill_ordered[idx_second], graph.boundary[cp1->contour_idx], graph.boundary_params[cp1->contour_idx], cp2, cp1, ! connection_cost.reversed, anchor_length, line_half_width);
}
}
#endif
struct Arc {
ContourIntersectionPoint *intersection;
double arc_length;
};
std::vector<Arc> arches;
if (!params.dont_sort) {
arches.reserve(graph.map_infill_end_point_to_boundary.size());
for (ContourIntersectionPoint& cp : graph.map_infill_end_point_to_boundary)
if (cp.contour_idx != boundary_idx_unconnected && cp.next_on_contour != &cp && cp.could_connect_next())
arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, graph.boundary_params[cp.contour_idx].back()) });
std::sort(arches.begin(), arches.end(), [](const auto& l, const auto& r) { return l.arc_length < r.arc_length; });
}
//FIXME improve the Traveling Salesman problem with 2-opt and 3-opt local optimization.
for (Arc &arc : arches)
if (! arc.intersection->consumed && ! arc.intersection->next_on_contour->consumed) {
// Indices of the polylines to be connected by a perimeter segment.
ContourIntersectionPoint *cp1 = arc.intersection;
ContourIntersectionPoint *cp2 = arc.intersection->next_on_contour;
size_t polyline_idx1 = get_and_update_merged_with(((cp1 - graph.map_infill_end_point_to_boundary.data()) / 2));
size_t polyline_idx2 = get_and_update_merged_with(((cp2 - graph.map_infill_end_point_to_boundary.data()) / 2));
const Points &contour = graph.boundary[cp1->contour_idx];
const std::vector<double> &contour_params = graph.boundary_params[cp1->contour_idx];
if (polyline_idx1 != polyline_idx2) {
Polyline &polyline1 = infill_ordered[polyline_idx1];
Polyline &polyline2 = infill_ordered[polyline_idx2];
if (arc.arc_length < anchor_length_max) {
// Not closing a loop, connecting the lines.
assert(contour[cp1->point_idx] == polyline1.points.front() || contour[cp1->point_idx] == polyline1.points.back());
if (contour[cp1->point_idx] == polyline1.points.front())
polyline1.reverse();
assert(contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back());
if (contour[cp2->point_idx] == polyline2.points.back())
polyline2.reverse();
take(polyline1, polyline2, contour, cp1, cp2, false);
// Mark the second polygon as merged with the first one.
if (polyline_idx2 < polyline_idx1) {
polyline2 = std::move(polyline1);
polyline1.points.clear();
merged_with[polyline_idx1] = merged_with[polyline_idx2];
} else {
polyline2.points.clear();
merged_with[polyline_idx2] = merged_with[polyline_idx1];
}
} else if (anchor_length > SCALED_EPSILON) {
// Move along the perimeter, but don't take the whole arc.
take_limited(polyline1, contour, contour_params, cp1, cp2, false, anchor_length, line_half_width);
take_limited(polyline2, contour, contour_params, cp2, cp1, true, anchor_length, line_half_width);
}
}
}
// Connect the remaining open infill lines to the perimeter lines if possible.
for (ContourIntersectionPoint &contour_point : graph.map_infill_end_point_to_boundary)
if (! contour_point.consumed && contour_point.contour_idx != boundary_idx_unconnected) {
const Points &contour = graph.boundary[contour_point.contour_idx];
const std::vector<double> &contour_params = graph.boundary_params[contour_point.contour_idx];
double lprev = contour_point.could_connect_prev() ?
path_length_along_contour_ccw(contour_point.prev_on_contour, &contour_point, contour_params.back()) :
std::numeric_limits<double>::max();
double lnext = contour_point.could_connect_next() ?
path_length_along_contour_ccw(&contour_point, contour_point.next_on_contour, contour_params.back()) :
std::numeric_limits<double>::max();
size_t polyline_idx = get_and_update_merged_with(((&contour_point - graph.map_infill_end_point_to_boundary.data()) / 2));
Polyline &polyline = infill_ordered[polyline_idx];
assert(! polyline.empty());
assert(contour[contour_point.point_idx] == polyline.points.front() || contour[contour_point.point_idx] == polyline.points.back());
bool connected = false;
for (double l : { std::min(lprev, lnext), std::max(lprev, lnext) }) {
if (l == std::numeric_limits<double>::max() || l > anchor_length_max)
break;
// Take the complete contour.
bool reversed = l == lprev;
ContourIntersectionPoint *cp2 = reversed ? contour_point.prev_on_contour : contour_point.next_on_contour;
// Identify which end of the polyline touches the boundary.
size_t polyline_idx2 = get_and_update_merged_with(((cp2 - graph.map_infill_end_point_to_boundary.data()) / 2));
if (polyline_idx == polyline_idx2)
// Try the other side.
continue;
// Not closing a loop.
if (contour[contour_point.point_idx] == polyline.points.front())
polyline.reverse();
Polyline &polyline2 = infill_ordered[polyline_idx2];
assert(! polyline.empty());
assert(contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back());
if (contour[cp2->point_idx] == polyline2.points.back())
polyline2.reverse();
take(polyline, polyline2, contour, &contour_point, cp2, reversed);
if (polyline_idx < polyline_idx2) {
// Mark the second polyline as merged with the first one.
merged_with[polyline_idx2] = polyline_idx;
polyline2.points.clear();
} else {
// Mark the first polyline as merged with the second one.
merged_with[polyline_idx] = polyline_idx2;
polyline2 = std::move(polyline);
polyline.points.clear();
}
connected = true;
break;
}
if (! connected && anchor_length > SCALED_EPSILON) {
// Which to take? One could optimize for:
// 1) Shortest path
// 2) Hook length
// ...
// Let's take the longer now, as this improves the chance of another hook to be placed on the other side of this contour point.
double l = std::max(contour_point.contour_not_taken_length_prev, contour_point.contour_not_taken_length_next);
if (l > SCALED_EPSILON) {
if (contour_point.contour_not_taken_length_prev > contour_point.contour_not_taken_length_next)
take_limited(polyline, contour, contour_params, &contour_point, contour_point.prev_on_contour, true, anchor_length, line_half_width);
else
take_limited(polyline, contour, contour_params, &contour_point, contour_point.next_on_contour, false, anchor_length, line_half_width);
}
}
}
polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); }));
for (Polyline &pl : infill_ordered)
if (! pl.empty())
polylines_out.emplace_back(std::move(pl));
}
// Extend the infill lines along the perimeters, this is mainly useful for grid aligned support, where a perimeter line may be nearly
// aligned with the infill lines.
static inline void base_support_extend_infill_lines(Polylines &infill, BoundaryInfillGraph &graph, const double spacing, const FillParams &params)
{
/*
// Backup the source lines.
Lines lines;
lines.reserve(linfill.size());
std::transform(infill.begin(), infill.end(), std::back_inserter(lines), [](const Polyline &pl) { assert(pl.size() == 2); return Line(pl.points.begin(), pl.points.end()); });
*/
const double line_spacing = scale_(spacing) / params.density;
// Maximum deviation perpendicular to the infill line to allow merging as a continuation of the same infill line.
const auto dist_max_x = coord_t(line_spacing * 0.33);
// Minimum length of the arc away from the infill end point to allow merging as a continuation of the same infill line.
const auto dist_min_y = coord_t(line_spacing * 0.5);
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
const Points &contour = graph.boundary[cp.contour_idx];
const std::vector<double> &contour_param = graph.boundary_params[cp.contour_idx];
const Point &pt = contour[cp.point_idx];
const bool first = graph.first(cp);
int extend_next_idx = -1;
int extend_prev_idx = -1;
coord_t dist_y_prev;
coord_t dist_y_next;
double arc_len_prev;
double arc_len_next;
if (! graph.next_vertical(cp)){
size_t i = cp.point_idx;
size_t j = next_idx_modulo(i, contour);
while (j != cp.next_on_contour->point_idx) {
//const Point &p1 = contour[i];
const Point &p2 = contour[j];
if (std::abs(p2.x() - pt.x()) > dist_max_x)
break;
i = j;
j = next_idx_modulo(j, contour);
}
if (i != cp.point_idx) {
const Point &p2 = contour[i];
coord_t dist_y = p2.y() - pt.y();
if (first)
dist_y = - dist_y;
if (dist_y > dist_min_y) {
arc_len_next = closed_contour_distance_ccw(contour_param[cp.point_idx], contour_param[i], contour_param.back());
if (arc_len_next < cp.contour_not_taken_length_next) {
extend_next_idx = i;
dist_y_next = dist_y;
}
}
}
}
if (! graph.prev_vertical(cp)) {
size_t i = cp.point_idx;
size_t j = prev_idx_modulo(i, contour);
while (j != cp.prev_on_contour->point_idx) {
//const Point &p1 = contour[i];
const Point &p2 = contour[j];
if (std::abs(p2.x() - pt.x()) > dist_max_x)
break;
i = j;
j = prev_idx_modulo(j, contour);
}
if (i != cp.point_idx) {
const Point &p2 = contour[i];
coord_t dist_y = p2.y() - pt.y();
if (first)
dist_y = - dist_y;
if (dist_y > dist_min_y) {
arc_len_prev = closed_contour_distance_ccw(contour_param[i], contour_param[cp.point_idx], contour_param.back());
if (arc_len_prev < cp.contour_not_taken_length_prev) {
extend_prev_idx = i;
dist_y_prev = dist_y;
}
}
}
}
if (extend_prev_idx >= 0 && extend_next_idx >= 0)
// Which side to move the point?
dist_y_prev < dist_y_next ? extend_prev_idx : extend_next_idx = -1;
assert(cp.prev_trimmed == cp.prev_on_contour->next_trimmed);
assert(cp.next_trimmed == cp.next_on_contour->prev_trimmed);
Polyline &infill_line = infill[(&cp - graph.map_infill_end_point_to_boundary.data()) / 2];
if (extend_prev_idx >= 0) {
if (first)
infill_line.reverse();
take_cw_full(infill_line, contour, cp.point_idx, extend_prev_idx);
if (first)
infill_line.reverse();
cp.point_idx = extend_prev_idx;
if (cp.prev_trimmed)
cp.contour_not_taken_length_prev -= arc_len_prev;
else
cp.contour_not_taken_length_prev = cp.prev_on_contour->contour_not_taken_length_next =
closed_contour_distance_ccw(contour_param[cp.prev_on_contour->point_idx], contour_param[cp.point_idx], contour_param.back());
cp.trim_next(0);
cp.next_on_contour->prev_trimmed = true;
} else if (extend_next_idx >= 0) {
if (first)
infill_line.reverse();
take_ccw_full(infill_line, contour, cp.point_idx, extend_next_idx);
if (first)
infill_line.reverse();
cp.point_idx = extend_next_idx;
cp.trim_prev(0);
cp.prev_on_contour->next_trimmed = true;
if (cp.next_trimmed)
cp.contour_not_taken_length_next -= arc_len_next;
else
cp.contour_not_taken_length_next = cp.next_on_contour->contour_not_taken_length_prev =
closed_contour_distance_ccw(contour_param[cp.point_idx], contour_param[cp.next_on_contour->point_idx], contour_param.back());
}
}
}
// Called by Fill::connect_base_support() as part of the sparse support infill generator.
// Emit contour loops tracing the contour from tbegin to tend inside a band of (left, right).
// The contour is supposed to enter the "forbidden" zone outside of the (left, right) band at tbegin and also at tend.
static inline void emit_loops_in_band(
// Vertical band, which will trim the contour between tbegin and tend.
coord_t left,
coord_t right,
// Contour and its parametrization.
const Points &contour,
const std::vector<double> &contour_params,
// Span of the parameters of an arch to trim with the vertical band.
double tbegin,
double tend,
// Minimum arch length to put into polylines_out. Shorter arches are not necessary to support a dense support infill.
double min_length,
Polylines &polylines_out)
{
assert(left < right);
assert(contour.size() + 1 == contour_params.size());
assert(contour.size() >= 3);
#ifndef NDEBUG
double contour_length = contour_params.back();
assert(tbegin >= 0 && tbegin < contour_length);
assert(tend >= 0 && tend < contour_length);
assert(min_length > 0);
#endif // NDEBUG
// Find iterators of the range of segments, where the first and last segment contains tbegin and tend.
size_t ibegin, iend;
{
auto it_begin = std::lower_bound(contour_params.begin(), contour_params.end(), tbegin);
auto it_end = std::lower_bound(contour_params.begin(), contour_params.end(), tend);
assert(it_begin != contour_params.end());
assert(it_end != contour_params.end());
if (*it_begin != tbegin) {
assert(it_begin != contour_params.begin());
-- it_begin;
}
ibegin = it_begin - contour_params.begin();
iend = it_end - contour_params.begin();
}
if (ibegin == contour.size())
ibegin = 0;
if (iend == contour.size())
iend = 0;
assert(ibegin != iend);
// Trim the start and end segment to calculate start and end points.
Point pbegin, pend;
{
double t1 = contour_params[ibegin];
double t2 = next_value_modulo(ibegin, contour_params);
pbegin = lerp(contour[ibegin], next_value_modulo(ibegin, contour), (tbegin - t1) / (t2 - t1));
t1 = contour_params[iend];
t2 = prev_value_modulo(iend, contour_params);
pend = lerp(contour[iend], prev_value_modulo(iend, contour), (tend - t1) / (t2 - t1));
}
// Trace the contour from ibegin to iend.
enum Side {
Left,
Right,
Mid,
Unknown
};
enum InOutBand {
Entering,
Leaving,
};
class State {
public:
State(coord_t left, coord_t right, double min_length, Polylines &polylines_out) :
m_left(left), m_right(right), m_min_length(min_length), m_polylines_out(polylines_out) {}
void add_inner_point(const Point* p)
{
m_polyline.points.emplace_back(*p);
}
void add_outer_point(const Point* p)
{
if (m_polyline_end > 0)
m_polyline.points.emplace_back(*p);
}
void add_interpolated_point(const Point* p1, const Point* p2, Side side, InOutBand inout)
{
assert(side == Left || side == Right);
coord_t x = side == Left ? m_left : m_right;
coord_t y = p1->y() + coord_t(double(x - p1->x()) * double(p2->y() - p1->y()) / double(p2->x() - p1->x()));
if (inout == Leaving) {
assert(m_polyline_end == 0);
m_polyline_end = m_polyline.size();
m_polyline.points.emplace_back(x, y);
} else {
assert(inout == Entering);
if (m_polyline_end > 0) {
if ((this->side1 == Left) == (y - m_polyline.points[m_polyline_end].y() < 0)) {
// Emit the vertical segment. Remove the point, where the source contour was split the last time at m_left / m_right.
m_polyline.points.erase(m_polyline.points.begin() + m_polyline_end);
} else {
// Don't emit the vertical segment, split the contour.
this->finalize();
m_polyline.points.emplace_back(x, y);
}
m_polyline_end = 0;
} else
m_polyline.points.emplace_back(x, y);
}
};
void finalize()
{
m_polyline.points.erase(m_polyline.points.begin() + m_polyline_end, m_polyline.points.end());
if (! m_polyline.empty()) {
if (! m_polylines_out.empty() && (m_polylines_out.back().points.back() - m_polyline.points.front()).cast<int64_t>().squaredNorm() < SCALED_EPSILON)
m_polylines_out.back().points.insert(m_polylines_out.back().points.end(), m_polyline.points.begin() + 1, m_polyline.points.end());
else if (m_polyline.length() > m_min_length)
m_polylines_out.emplace_back(std::move(m_polyline));
m_polyline.clear();
}
};
private:
coord_t m_left;
coord_t m_right;
double m_min_length;
Polylines &m_polylines_out;
Polyline m_polyline;
size_t m_polyline_end { 0 };
Polyline m_overlapping;
public:
Side side1 { Unknown };
Side side2 { Unknown };
};
State state { left, right, min_length, polylines_out };
const Point *p1 = &pbegin;
auto side = [left, right](const Point* p) {
coord_t x = p->x();
return x < left ? Left : x > right ? Right : Mid;
};
state.side1 = side(p1);
if (state.side1 == Mid)
state.add_inner_point(p1);
for (size_t i = ibegin; i != iend; ) {
size_t inext = i + 1;
if (inext == contour.size())
inext = 0;
const Point *p2 = inext == iend ? &pend : &contour[inext];
state.side2 = side(p2);
if (state.side1 == Mid) {
if (state.side2 == Mid) {
// Inside the band.
state.add_inner_point(p2);
} else {
// From intisde the band to the outside of the band.
state.add_interpolated_point(p1, p2, state.side2, Leaving);
state.add_outer_point(p2);
}
} else if (state.side2 == Mid) {
// From outside the band into the band.
state.add_interpolated_point(p1, p2, state.side1, Entering);
state.add_inner_point(p2);
} else if (state.side1 != state.side2) {
// Both points outside the band.
state.add_interpolated_point(p1, p2, state.side1, Entering);
state.add_interpolated_point(p1, p2, state.side2, Leaving);
} else {
// Complete segment is outside.
assert((state.side1 == Left && state.side2 == Left) || (state.side1 == Right && state.side2 == Right));
state.add_outer_point(p2);
}
state.side1 = state.side2;
p1 = p2;
i = inext;
}
state.finalize();
}
#ifdef INFILL_DEBUG_OUTPUT
static void export_partial_infill_to_svg(const std::string &path, const BoundaryInfillGraph &graph, const Polylines &infill, const Polylines &emitted)
{
Polygons polygons;
for (const Points &pts : graph.boundary)
polygons.emplace_back(pts);
BoundingBox bbox = get_extents(polygons);
bbox.merge(get_extents(infill));
::Slic3r::SVG svg(path, bbox);
svg.draw(union_ex(polygons));
svg.draw(infill, "blue");
svg.draw(emitted, "darkblue");
for (const ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary)
svg.draw(graph.point(cp), cp.consumed ? "red" : "green", scaled(0.2));
for (const ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
assert(cp.next_trimmed == cp.next_on_contour->prev_trimmed);
assert(cp.prev_trimmed == cp.prev_on_contour->next_trimmed);
if (cp.contour_not_taken_length_next > SCALED_EPSILON) {
Polyline pl { graph.point(cp) };
take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next);
svg.draw(pl, cp.could_take_next() ? "lime" : "magenta", scaled(0.1));
}
if (cp.contour_not_taken_length_prev > SCALED_EPSILON) {
Polyline pl { graph.point(cp) };
take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev);
svg.draw(pl, cp.could_take_prev() ? "lime" : "magenta", scaled(0.1));
}
}
}
#endif // INFILL_DEBUG_OUTPUT
// To classify perimeter segments connecting infill lines, whether they are required for structural stability of the supports.
struct SupportArcCost
{
// Connecting one end of an infill line to the other end of the same infill line.
bool self_loop { false };
// Some of the arc touches some infill line.
bool open { false };
// How needed is this arch for support structural stability.
// Zero means don't take. The higher number, the more likely it is to take the arc.
double cost { 0 };
};
static double evaluate_support_arch_cost(const Polyline &pl)
{
Point front = pl.points.front();
Point back = pl.points.back();
coord_t ymin = front.y();
coord_t ymax = back.y();
if (ymin > ymax)
std::swap(ymin, ymax);
double dmax = 0;
// Maximum distance in Y axis out of the (ymin, ymax) band and from the (front, back) line.
Linef line { front.cast<double>(), back.cast<double>() };
for (const Point &pt : pl.points)
dmax = std::max<double>(std::max(dmax, line_alg::distance_to(line, Vec2d(pt.cast<double>()))), std::max(pt.y() - ymax, ymin - pt.y()));
return dmax;
}
// Costs for prev / next arch of each infill line end point.
static inline std::vector<SupportArcCost> evaluate_support_arches(Polylines &infill, BoundaryInfillGraph &graph, const double spacing, const FillParams &params)
{
std::vector<SupportArcCost> arches(graph.map_infill_end_point_to_boundary.size() * 2);
Polyline pl;
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
// Not a losed loop, such loops should already be consumed.
assert(cp.next_on_contour != &cp);
const size_t infill_line_idx = &cp - graph.map_infill_end_point_to_boundary.data();
const bool first = (infill_line_idx & 1) == 0;
const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1;
SupportArcCost &out_prev = arches[infill_line_idx * 2];
SupportArcCost &out_next = *(&out_prev + 1);
out_prev.self_loop = cp.prev_on_contour == other_end;
out_prev.open = cp.prev_trimmed;
out_next.self_loop = cp.next_on_contour == other_end;
out_next.open = cp.next_trimmed;
if (cp.contour_not_taken_length_next > SCALED_EPSILON) {
pl.clear();
pl.points.emplace_back(graph.point(cp));
if (cp.next_trimmed)
take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next);
else
take_ccw_full(pl, graph.boundary[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx);
out_next.cost = evaluate_support_arch_cost(pl);
}
if (cp.contour_not_taken_length_prev > SCALED_EPSILON) {
pl.clear();
pl.points.emplace_back(graph.point(cp));
if (cp.prev_trimmed)
take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev);
else
take_cw_full(pl, graph.boundary[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx);
out_prev.cost = evaluate_support_arch_cost(pl);
}
}
return arches;
}
// Both the poly_with_offset and polylines_out are rotated, so the infill lines are strictly vertical.
void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams &params)
{
// assert(! infill_ordered.empty());
assert(params.anchor_length >= 0.);
assert(params.anchor_length_max >= 0.01f);
assert(params.anchor_length_max >= params.anchor_length);
BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing);
#ifdef INFILL_DEBUG_OUTPUT
static int iRun = 0;
++ iRun;
export_partial_infill_to_svg(debug_out_path("connect_base_support-initial-%03d.svg", iRun), graph, infill_ordered, polylines_out);
#endif // INFILL_DEBUG_OUTPUT
const double line_half_width = 0.5 * scale_(spacing);
const double line_spacing = scale_(spacing) / params.density;
const double min_arch_length = 1.3 * line_spacing;
const double trim_length = line_half_width * 0.3;
// After mark_boundary_segments_touching_infill() marks boundary segments overlapping trimmed infill lines,
// there are possibly some very short boundary segments unmarked, but overlapping the untrimmed infill lines fully.
// Mark those short boundary segments.
mark_boundary_segments_overlapping_infill(graph, infill_ordered, scale_(spacing));
#ifdef INFILL_DEBUG_OUTPUT
export_partial_infill_to_svg(debug_out_path("connect_base_support-marked-%03d.svg", iRun), graph, infill_ordered, polylines_out);
#endif // INFILL_DEBUG_OUTPUT
// Detect loops with zero infill end points connected.
// Extrude these loops as perimeters.
{
std::vector<size_t> num_boundary_contour_infill_points(graph.boundary.size(), 0);
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary)
++ num_boundary_contour_infill_points[cp.contour_idx];
for (size_t i = 0; i < num_boundary_contour_infill_points.size(); ++ i)
if (num_boundary_contour_infill_points[i] == 0 && graph.boundary_params[i].back() > trim_length + 0.5 * line_spacing) {
// Emit a perimeter.
Polyline pl(graph.boundary[i]);
pl.points.emplace_back(pl.points.front());
pl.clip_end(trim_length);
if (pl.size() > 1)
polylines_out.emplace_back(std::move(pl));
}
}
// Before processing the boundary arches, emit those arches, which were trimmed by the infill lines at both sides, but which
// depart from the infill line at least once after touching the infill line.
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
if (cp.next_on_contour && cp.next_trimmed && cp.next_on_contour->prev_trimmed) {
// The arch is leaving one infill line to end up at the same infill line or at the neighbouring one.
// The arch is touching one of those infill lines at least once.
// Trace those arches and emit their parts, which are not attached to the end points and they are not overlapping with the two infill lines mentioned.
bool first = graph.first(cp);
coord_t left = graph.point(cp).x();
coord_t right = left;
if (first) {
left += line_half_width;
right += line_spacing - line_half_width;
} else {
left -= line_spacing - line_half_width;
right -= line_half_width;
}
double param_start = cp.param + cp.contour_not_taken_length_next;
double param_end = cp.next_on_contour->param - cp.next_on_contour->contour_not_taken_length_prev;
double contour_length = graph.boundary_params[cp.contour_idx].back();
if (param_start >= contour_length)
param_start -= contour_length;
if (param_end < 0)
param_end += contour_length;
// Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param).
assert(cyclic_interval_inside_interval(cp.param, cp.next_on_contour->param, param_start, param_end, contour_length));
emit_loops_in_band(left, right, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], param_start, param_end, 0.5 * line_spacing, polylines_out);
}
}
#ifdef INFILL_DEBUG_OUTPUT
export_partial_infill_to_svg(debug_out_path("connect_base_support-excess-%03d.svg", iRun), graph, infill_ordered, polylines_out);
#endif // INFILL_DEBUG_OUTPUT
base_support_extend_infill_lines(infill_ordered, graph, spacing, params);
#ifdef INFILL_DEBUG_OUTPUT
export_partial_infill_to_svg(debug_out_path("connect_base_support-extended-%03d.svg", iRun), graph, infill_ordered, polylines_out);
#endif // INFILL_DEBUG_OUTPUT
std::vector<size_t> merged_with(infill_ordered.size());
std::iota(merged_with.begin(), merged_with.end(), 0);
auto get_and_update_merged_with = [&graph, &merged_with](const ContourIntersectionPoint *cp) -> size_t {
size_t polyline_idx = (cp - graph.map_infill_end_point_to_boundary.data()) / 2;
for (size_t last = polyline_idx;;) {
size_t lower = merged_with[last];
assert(lower <= last);
if (lower == last) {
merged_with[polyline_idx] = last;
return last;
}
last = lower;
}
assert(false);
return std::numeric_limits<size_t>::max();
};
auto vertical = [](BoundaryInfillGraph::Direction dir) {
return dir == BoundaryInfillGraph::Up || dir == BoundaryInfillGraph::Down;
};
// When both left / right arch connected to cp is vertical (ends up at the same vertical infill line), which one to take?
auto take_vertical_prev = [](const ContourIntersectionPoint &cp) {
return cp.prev_trimmed == cp.next_trimmed ?
// Both are either trimmed or not trimmed. Take the longer contour.
cp.contour_not_taken_length_prev > cp.contour_not_taken_length_next :
// One is trimmed, the other is not trimmed. Take the not trimmed.
! cp.prev_trimmed && cp.next_trimmed;
};
// Connect infill lines at cp and cpo_next_on_contour.
// If the complete arch cannot be taken, then
// if (take_first)
// take the infill line at cp and an arc from cp towards cp.next_on_contour.
// else
// take the infill line at cp_next_on_contour and an arc from cp.next_on_contour towards cp.
// If cp1 == next_on_contour (a single infill line is connected to a contour, this is a valid case for contours with holes),
// then extrude the full circle.
// Nothing is done if the arch could no more be taken (one of it end points were consumed already).
auto take_next = [&graph, &infill_ordered, &merged_with, get_and_update_merged_with, line_half_width, trim_length](ContourIntersectionPoint &cp, bool take_first) {
// Indices of the polylines to be connected by a perimeter segment.
ContourIntersectionPoint *cp1 = &cp;
ContourIntersectionPoint *cp2 = cp.next_on_contour;
assert(cp1->next_trimmed == cp2->prev_trimmed);
//assert(cp1->next_trimmed || cp1->consumed == cp2->consumed);
if (take_first ? cp1->consumed : cp2->consumed)
return;
size_t polyline_idx1 = get_and_update_merged_with(cp1);
size_t polyline_idx2 = get_and_update_merged_with(cp2);
Polyline &polyline1 = infill_ordered[polyline_idx1];
Polyline &polyline2 = infill_ordered[polyline_idx2];
const Points &contour = graph.boundary[cp1->contour_idx];
const std::vector<double> &contour_params = graph.boundary_params[cp1->contour_idx];
assert(cp1->consumed || contour[cp1->point_idx] == polyline1.points.front() || contour[cp1->point_idx] == polyline1.points.back());
assert(cp2->consumed || contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back());
bool trimmed = take_first ? cp1->next_trimmed : cp2->prev_trimmed;
if (! trimmed) {
// Trim the end if closing a loop or making a T-joint.
trimmed = cp1 == cp2 || polyline_idx1 == polyline_idx2 || (take_first ? cp2->consumed : cp1->consumed);
if (! trimmed) {
const bool cp1_first = ((cp1 - graph.map_infill_end_point_to_boundary.data()) & 1) == 0;
const ContourIntersectionPoint* cp1_other = cp1_first ? cp1 + 1 : cp1 - 1;
// Self loop, connecting the end points of the same infill line.
trimmed = cp2 == cp1_other;
}
if (trimmed) /* [[unlikely]] */ {
// Single end point on a contour. This may happen on contours with holes. Extrude a loop.
// Or a self loop, connecting the end points of the same infill line.
// Or closing a chain of infill lines. This may happen if infilling a contour with a hole.
double len = cp1 == cp2 ? contour_params.back() : path_length_along_contour_ccw(cp1, cp2, contour_params.back());
if (take_first) {
cp1->trim_next(std::max(0., len - trim_length - SCALED_EPSILON));
cp2->trim_prev(0);
} else {
cp1->trim_next(0);
cp2->trim_prev(std::max(0., len - trim_length - SCALED_EPSILON));
}
}
}
if (trimmed) {
if (take_first)
take_limited(polyline1, contour, contour_params, cp1, cp2, false, 1e10, line_half_width);
else
take_limited(polyline2, contour, contour_params, cp2, cp1, true, 1e10, line_half_width);
} else if (! cp1->consumed && ! cp2->consumed) {
if (contour[cp1->point_idx] == polyline1.points.front())
polyline1.reverse();
if (contour[cp2->point_idx] == polyline2.points.back())
polyline2.reverse();
take(polyline1, polyline2, contour, cp1, cp2, false);
// Mark the second polygon as merged with the first one.
if (polyline_idx2 < polyline_idx1) {
polyline2 = std::move(polyline1);
polyline1.points.clear();
merged_with[polyline_idx1] = merged_with[polyline_idx2];
} else {
polyline2.points.clear();
merged_with[polyline_idx2] = merged_with[polyline_idx1];
}
}
};
// Consume all vertical arches. If a vertical arch is touching a neighboring vertical infill line, thus the vertical arch is trimmed,
// only consume the trimmed part if it is longer than min_arch_length.
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
assert(cp.contour_idx != boundary_idx_unconnected);
if (cp.consumed)
continue;
const ContourIntersectionPoint &cp_other = graph.other(cp);
assert((cp.next_on_contour == &cp_other) == (cp_other.prev_on_contour == &cp));
assert((cp.prev_on_contour == &cp_other) == (cp_other.next_on_contour == &cp));
BoundaryInfillGraph::Direction dir_prev = graph.dir_prev(cp);
BoundaryInfillGraph::Direction dir_next = graph.dir_next(cp);
// Following code will also consume contours with just a single infill line attached. (cp1->next_on_contour == cp1).
assert((cp.next_on_contour == &cp) == (cp.prev_on_contour == &cp));
bool can_take_prev = vertical(dir_prev) && ! cp.prev_on_contour->consumed && cp.prev_on_contour != &cp_other;
bool can_take_next = vertical(dir_next) && ! cp.next_on_contour->consumed && cp.next_on_contour != &cp_other;
if (can_take_prev && (! can_take_next || take_vertical_prev(cp))) {
if (! cp.prev_trimmed || cp.contour_not_taken_length_prev > min_arch_length)
// take previous
take_next(*cp.prev_on_contour, false);
} else if (can_take_next) {
if (! cp.next_trimmed || cp.contour_not_taken_length_next > min_arch_length)
// take next
take_next(cp, true);
}
}
#ifdef INFILL_DEBUG_OUTPUT
export_partial_infill_to_svg(debug_out_path("connect_base_support-vertical-%03d.svg", iRun), graph, infill_ordered, polylines_out);
#endif // INFILL_DEBUG_OUTPUT
const std::vector<SupportArcCost> arches = evaluate_support_arches(infill_ordered, graph, spacing, params);
static const double cost_low = line_spacing * 1.3;
static const double cost_high = line_spacing * 2.;
static const double cost_veryhigh = line_spacing * 3.;
{
std::vector<const SupportArcCost*> selected;
selected.reserve(graph.map_infill_end_point_to_boundary.size());
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
if (cp.consumed)
continue;
const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2];
const SupportArcCost &cost_next = *(&cost_prev + 1);
double cost_min = cost_prev.cost;
double cost_max = cost_next.cost;
if (cost_min > cost_max)
std::swap(cost_min, cost_max);
if (cost_max < cost_low || cost_min > cost_high)
// Don't take any of the prev / next arches now, take zig-zag instead. It does not matter which one will be taken.
continue;
const double cost_diff_relative = (cost_max - cost_min) / cost_max;
if (cost_diff_relative < 0.25)
// Don't take any of the prev / next arches now, take zig-zag instead. It does not matter which one will be taken.
continue;
if (cost_prev.cost > cost_low)
selected.emplace_back(&cost_prev);
if (cost_next.cost > cost_low)
selected.emplace_back(&cost_next);
}
// Take the longest arch first.
std::sort(selected.begin(), selected.end(), [](const auto *l, const auto *r) { return l->cost > r->cost; });
// And connect along the arches.
for (const SupportArcCost *arc : selected) {
ContourIntersectionPoint &cp = graph.map_infill_end_point_to_boundary[(arc - arches.data()) / 2];
if (! cp.consumed) {
bool prev = ((arc - arches.data()) & 1) == 0;
if (prev)
take_next(*cp.prev_on_contour, false);
else
take_next(cp, true);
}
}
}
#if 0
{
// Connect infill lines with long horizontal arches. Only take a horizontal arch, if it will not block
// the end caps (vertical arches) at the other side of the infill line.
struct Arc {
ContourIntersectionPoint *intersection;
double arc_length;
bool take_next;
};
std::vector<Arc> arches;
arches.reserve(graph.map_infill_end_point_to_boundary.size());
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
if (cp.consumed)
continue;
// Not a losed loop, such loops should already be consumed.
assert(cp.next_on_contour != &cp);
const bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0;
const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1;
const bool loop_next = cp.next_on_contour == other_end;
if (! loop_next && cp.could_connect_next()) {
if (cp.contour_not_taken_length_next > min_arch_length) {
// Try both directions. This is useful to be able to close a loop back to the same line to take a long arch.
arches.push_back({ &cp, cp.contour_not_taken_length_next, true });
arches.push_back({ cp.next_on_contour, cp.contour_not_taken_length_next, false });
}
} else {
//bool first = ((&cp - graph.map_infill_end_point_to_boundary) & 1) == 0;
if (cp.prev_trimmed && cp.could_take_prev()) {
//FIXME trace the trimmed line to decide what priority to assign to it.
// Is the end point close to the current vertical line or to the other vertical line?
const Point &pt = graph.point(cp);
const Point &prev = graph.point(*cp.prev_on_contour);
if (std::abs(pt.x() - prev.x()) < coord_t(0.5 * line_spacing)) {
// End point on the same line.
// Measure maximum distance from the current vertical line.
if (cp.contour_not_taken_length_prev > 0.5 * line_spacing)
arches.push_back({ &cp, cp.contour_not_taken_length_prev, false });
} else {
// End point on the other line.
if (cp.contour_not_taken_length_prev > min_arch_length)
arches.push_back({ &cp, cp.contour_not_taken_length_prev, false });
}
}
if (cp.next_trimmed && cp.could_take_next()) {
//FIXME trace the trimmed line to decide what priority to assign to it.
const Point &pt = graph.point(cp);
const Point &next = graph.point(*cp.next_on_contour);
if (std::abs(pt.x() - next.x()) < coord_t(0.5 * line_spacing)) {
// End point on the same line.
// Measure maximum distance from the current vertical line.
if (cp.contour_not_taken_length_next > 0.5 * line_spacing)
arches.push_back({ &cp, cp.contour_not_taken_length_next, true });
} else {
// End point on the other line.
if (cp.contour_not_taken_length_next > min_arch_length)
arches.push_back({ &cp, cp.contour_not_taken_length_next, true });
}
}
}
}
// Take the longest arch first.
std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length > r.arc_length; });
// And connect along the arches.
for (Arc &arc : arches)
if (arc.take_next)
take_next(*arc.intersection, true);
else
take_next(*arc.intersection->prev_on_contour, false);
}
#endif
#ifdef INFILL_DEBUG_OUTPUT
export_partial_infill_to_svg(debug_out_path("connect_base_support-arches-%03d.svg", iRun), graph, infill_ordered, polylines_out);
#endif // INFILL_DEBUG_OUTPUT
// Traverse the unconnected lines in a zig-zag fashion, left to right only.
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
assert(cp.contour_idx != boundary_idx_unconnected);
if (cp.consumed)
continue;
bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0;
if (first) {
// Only connect if the two lines are not connected by the same line already.
if (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.next_on_contour))
take_next(cp, true);
} else {
if (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.prev_on_contour))
take_next(*cp.prev_on_contour, false);
}
}
#ifdef INFILL_DEBUG_OUTPUT
export_partial_infill_to_svg(debug_out_path("connect_base_support-zigzag-%03d.svg", iRun), graph, infill_ordered, polylines_out);
#endif // INFILL_DEBUG_OUTPUT
// Add the left caps.
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
const bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0;
const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1;
const bool loop_next = cp.next_on_contour == other_end;
const bool loop_prev = other_end->next_on_contour == &cp;
#ifndef NDEBUG
const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2];
const SupportArcCost &cost_next = *(&cost_prev + 1);
assert(cost_prev.self_loop == loop_prev);
assert(cost_next.self_loop == loop_next);
#endif // NDEBUG
if (loop_prev && cp.could_take_prev())
take_next(*cp.prev_on_contour, false);
if (loop_next && cp.could_take_next())
take_next(cp, true);
}
#ifdef INFILL_DEBUG_OUTPUT
export_partial_infill_to_svg(debug_out_path("connect_base_support-caps-%03d.svg", iRun), graph, infill_ordered, polylines_out);
#endif // INFILL_DEBUG_OUTPUT
// Connect with T joints using long arches. Loops could be created only if a very long arc has to be added.
{
std::vector<const SupportArcCost*> candidates;
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
if (cp.could_take_prev())
candidates.emplace_back(&arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]);
if (cp.could_take_next())
candidates.emplace_back(&arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2 + 1]);
}
std::sort(candidates.begin(), candidates.end(), [](auto *c1, auto *c2) { return c1->cost > c2->cost; });
for (const SupportArcCost *candidate : candidates) {
ContourIntersectionPoint &cp = graph.map_infill_end_point_to_boundary[(candidate - arches.data()) / 2];
bool prev = ((candidate - arches.data()) & 1) == 0;
if (prev) {
if (cp.could_take_prev() && (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.prev_on_contour) || candidate->cost > cost_high))
take_next(*cp.prev_on_contour, false);
} else {
if (cp.could_take_next() && (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.next_on_contour) || candidate->cost > cost_high))
take_next(cp, true);
}
}
}
#ifdef INFILL_DEBUG_OUTPUT
export_partial_infill_to_svg(debug_out_path("connect_base_support-Tjoints-%03d.svg", iRun), graph, infill_ordered, polylines_out);
#endif // INFILL_DEBUG_OUTPUT
// Add very long arches and reasonably long caps even if both of its end points were already consumed.
const double cap_cost = 0.5 * line_spacing;
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2];
const SupportArcCost &cost_next = *(&cost_prev + 1);
if (cp.contour_not_taken_length_prev > SCALED_EPSILON &&
(cost_prev.self_loop ?
cost_prev.cost > cap_cost :
cost_prev.cost > cost_veryhigh)) {
assert(cp.consumed && (cp.prev_on_contour->consumed || cp.prev_trimmed));
Polyline pl { graph.point(cp) };
if (! cp.prev_trimmed) {
cp.trim_prev(cp.contour_not_taken_length_prev - line_half_width);
cp.prev_on_contour->trim_next(0);
}
if (cp.contour_not_taken_length_prev > SCALED_EPSILON) {
take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev);
cp.trim_prev(0);
pl.clip_start(line_half_width);
polylines_out.emplace_back(std::move(pl));
}
}
if (cp.contour_not_taken_length_next > SCALED_EPSILON &&
(cost_next.self_loop ?
cost_next.cost > cap_cost :
cost_next.cost > cost_veryhigh)) {
assert(cp.consumed && (cp.next_on_contour->consumed || cp.next_trimmed));
Polyline pl { graph.point(cp) };
if (! cp.next_trimmed) {
cp.trim_next(cp.contour_not_taken_length_next - line_half_width);
cp.next_on_contour->trim_prev(0);
}
if (cp.contour_not_taken_length_next > SCALED_EPSILON) {
take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); // line_half_width);
cp.trim_next(0);
pl.clip_start(line_half_width);
polylines_out.emplace_back(std::move(pl));
}
}
}
#ifdef INFILL_DEBUG_OUTPUT
export_partial_infill_to_svg(debug_out_path("connect_base_support-final-%03d.svg", iRun), graph, infill_ordered, polylines_out);
#endif // INFILL_DEBUG_OUTPUT
polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); }));
for (Polyline &pl : infill_ordered)
if (! pl.empty())
polylines_out.emplace_back(std::move(pl));
}
void Fill::connect_base_support(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams &params)
{
auto polygons_src = reserve_vector<const Polygon*>(boundary_src.size());
for (const Polygon &polygon : boundary_src)
polygons_src.emplace_back(&polygon);
connect_base_support(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params);
}
} // namespace Slic3r