ENH: fix hang issue for archimedean chords pattern
This is fix for STUDIO-2079 that slicer may hang when generated archimedean chords pattern lines. Thanks prusa. Signed-off-by: salt.wei <salt.wei@bambulab.com> Change-Id: I923c1ef48f093b2ab9576cb04beb4787c903ca00
This commit is contained in:
parent
223291452b
commit
03e031fe5c
4 changed files with 186 additions and 63 deletions
|
@ -867,6 +867,8 @@ Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygon
|
|||
{ return _clipper_pl_closed(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); }
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip)
|
||||
{ return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::SinglePathProvider(clip.points)); }
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip)
|
||||
{ return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonProvider(clip)); }
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip)
|
||||
{ return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip)); }
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip)
|
||||
|
|
|
@ -494,7 +494,8 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r
|
|||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines& subject, const Slic3r::ExPolygon& clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip);
|
||||
|
|
|
@ -6,6 +6,66 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
class InfillPolylineClipper : public InfillPolylineOutput {
|
||||
public:
|
||||
InfillPolylineClipper(const BoundingBox bbox, const double scale_out) : InfillPolylineOutput(scale_out), m_bbox(bbox) {}
|
||||
|
||||
void add_point(const Vec2d &pt);
|
||||
Points&& result() { return std::move(m_out); }
|
||||
bool clips() const override { return true; }
|
||||
|
||||
private:
|
||||
enum class Side {
|
||||
Left = 1,
|
||||
Right = 2,
|
||||
Top = 4,
|
||||
Bottom = 8
|
||||
};
|
||||
|
||||
int sides(const Point &p) const {
|
||||
return int(p.x() < m_bbox.min.x()) * int(Side::Left) +
|
||||
int(p.x() > m_bbox.max.x()) * int(Side::Right) +
|
||||
int(p.y() < m_bbox.min.y()) * int(Side::Bottom) +
|
||||
int(p.y() > m_bbox.max.y()) * int(Side::Top);
|
||||
};
|
||||
|
||||
// Bounding box to clip the polyline with.
|
||||
BoundingBox m_bbox;
|
||||
|
||||
// Classification of the two last points processed.
|
||||
int m_sides_prev;
|
||||
int m_sides_this;
|
||||
};
|
||||
|
||||
void InfillPolylineClipper::add_point(const Vec2d &fpt)
|
||||
{
|
||||
const Point pt{ this->scaled(fpt) };
|
||||
|
||||
if (m_out.size() < 2) {
|
||||
// Collect the two first points and their status.
|
||||
(m_out.empty() ? m_sides_prev : m_sides_this) = sides(pt);
|
||||
m_out.emplace_back(pt);
|
||||
} else {
|
||||
// Classify the last inserted point, possibly remove it.
|
||||
int sides_next = sides(pt);
|
||||
if (// This point is inside. Take it.
|
||||
m_sides_this == 0 ||
|
||||
// Either this point is outside and previous or next is inside, or
|
||||
// the edge possibly cuts corner of the bounding box.
|
||||
(m_sides_prev & m_sides_this & sides_next) == 0) {
|
||||
// Keep the last point.
|
||||
m_sides_prev = m_sides_this;
|
||||
} else {
|
||||
// All the three points (this, prev, next) are outside at the same side.
|
||||
// Ignore the last point.
|
||||
m_out.pop_back();
|
||||
}
|
||||
// And save the current point.
|
||||
m_out.emplace_back(pt);
|
||||
m_sides_this = sides_next;
|
||||
}
|
||||
}
|
||||
|
||||
void FillPlanePath::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
|
@ -13,37 +73,52 @@ void FillPlanePath::_fill_surface_single(
|
|||
ExPolygon expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
expolygon.rotate(- direction.first);
|
||||
expolygon.rotate(-direction.first);
|
||||
|
||||
coord_t distance_between_lines = coord_t(scale_(this->spacing) / params.density);
|
||||
|
||||
// align infill across layers using the object's bounding box
|
||||
// Rotated bounding box of the whole object.
|
||||
BoundingBox bounding_box = this->bounding_box.rotated(- direction.first);
|
||||
|
||||
Point shift = this->_centered() ?
|
||||
//FIXME Vojtech: We are not sure whether the user expects the fill patterns on visible surfaces to be aligned across all the islands of a single layer.
|
||||
// One may align for this->centered() to align the patterns for Archimedean Chords and Octagram Spiral patterns.
|
||||
const bool align = params.density < 0.995;
|
||||
|
||||
BoundingBox snug_bounding_box = get_extents(expolygon).inflated(SCALED_EPSILON);
|
||||
|
||||
// Rotated bounding box of the area to fill in with the pattern.
|
||||
BoundingBox bounding_box = align ?
|
||||
// Sparse infill needs to be aligned across layers. Align infill across layers using the object's bounding box.
|
||||
this->bounding_box.rotated(-direction.first) :
|
||||
// Solid infill does not need to be aligned across layers, generate the infill pattern
|
||||
// around the clipping expolygon only.
|
||||
snug_bounding_box;
|
||||
|
||||
Point shift = this->centered() ?
|
||||
bounding_box.center() :
|
||||
bounding_box.min;
|
||||
expolygon.translate(-shift.x(), -shift.y());
|
||||
bounding_box.translate(-shift.x(), -shift.y());
|
||||
|
||||
Pointfs pts = _generate(
|
||||
coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines)),
|
||||
params.resolution);
|
||||
Polyline polyline;
|
||||
{
|
||||
auto distance_between_lines = scaled<double>(this->spacing) / params.density;
|
||||
auto min_x = coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines));
|
||||
auto min_y = coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines));
|
||||
auto max_x = coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines));
|
||||
auto max_y = coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines));
|
||||
auto resolution = scaled<double>(params.resolution) / distance_between_lines;
|
||||
if (align) {
|
||||
// Filling in a bounding box over the whole object, clip generated polyline against the snug bounding box.
|
||||
snug_bounding_box.translate(-shift.x(), -shift.y());
|
||||
InfillPolylineClipper output(snug_bounding_box, distance_between_lines);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
polyline.points = std::move(output.result());
|
||||
} else {
|
||||
// Filling in a snug bounding box, no need to clip.
|
||||
InfillPolylineOutput output(distance_between_lines);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
polyline.points = std::move(output.result());
|
||||
}
|
||||
}
|
||||
|
||||
if (pts.size() >= 2) {
|
||||
// Convert points to a polyline, upscale.
|
||||
Polylines polylines(1, Polyline());
|
||||
Polyline &polyline = polylines.front();
|
||||
polyline.points.reserve(pts.size());
|
||||
for (const Vec2d &pt : pts)
|
||||
polyline.points.emplace_back(
|
||||
coord_t(floor(pt.x() * distance_between_lines + 0.5)),
|
||||
coord_t(floor(pt.y() * distance_between_lines + 0.5)));
|
||||
polylines = intersection_pl(polylines, expolygon);
|
||||
if (polyline.size() >= 2) {
|
||||
Polylines polylines = intersection_pl(polyline, expolygon);
|
||||
Polylines chained;
|
||||
if (params.dont_connect() || params.density > 0.5 || polylines.size() <= 1)
|
||||
chained = chain_polylines(std::move(polylines));
|
||||
|
@ -59,7 +134,8 @@ void FillPlanePath::_fill_surface_single(
|
|||
}
|
||||
|
||||
// Follow an Archimedean spiral, in polar coordinates: r=a+b\theta
|
||||
Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution)
|
||||
template<typename Output>
|
||||
static void generate_archimedean_chords(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, Output &output)
|
||||
{
|
||||
// Radius to achieve.
|
||||
coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5;
|
||||
|
@ -70,15 +146,22 @@ Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t m
|
|||
coordf_t r = 1;
|
||||
Pointfs out;
|
||||
//FIXME Vojtech: If used as a solid infill, there is a gap left at the center.
|
||||
out.emplace_back(0, 0);
|
||||
out.emplace_back(1, 0);
|
||||
output.add_point({ 0, 0 });
|
||||
output.add_point({ 1, 0 });
|
||||
while (r < rmax) {
|
||||
// Discretization angle to achieve a discretization error lower than resolution.
|
||||
theta += 2. * acos(1. - resolution / r);
|
||||
r = a + b * theta;
|
||||
out.emplace_back(r * cos(theta), r * sin(theta));
|
||||
output.add_point({ r * cos(theta), r * sin(theta) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void FillArchimedeanChords::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output)
|
||||
{
|
||||
if (output.clips())
|
||||
generate_archimedean_chords(min_x, min_y, max_x, max_y, resolution, static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
generate_archimedean_chords(min_x, min_y, max_x, max_y, resolution, output);
|
||||
}
|
||||
|
||||
// Adapted from
|
||||
|
@ -126,7 +209,8 @@ static inline Point hilbert_n_to_xy(const size_t n)
|
|||
return Point(x, y);
|
||||
}
|
||||
|
||||
Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */)
|
||||
template<typename Output>
|
||||
static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output)
|
||||
{
|
||||
// Minimum power of two square to fit the domain.
|
||||
size_t sz = 2;
|
||||
|
@ -140,46 +224,59 @@ Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x,
|
|||
}
|
||||
|
||||
size_t sz2 = sz * sz;
|
||||
Pointfs line;
|
||||
line.reserve(sz2);
|
||||
output.reserve(sz2);
|
||||
for (size_t i = 0; i < sz2; ++ i) {
|
||||
Point p = hilbert_n_to_xy(i);
|
||||
line.emplace_back(p.x() + min_x, p.y() + min_y);
|
||||
output.add_point({ p.x() + min_x, p.y() + min_y });
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */)
|
||||
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
|
||||
{
|
||||
if (output.clips())
|
||||
generate_hilbert_curve(min_x, min_y, max_x, max_y, static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
generate_hilbert_curve(min_x, min_y, max_x, max_y, output);
|
||||
}
|
||||
|
||||
template<typename Output>
|
||||
static void generate_octagram_spiral(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output)
|
||||
{
|
||||
// Radius to achieve.
|
||||
coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5;
|
||||
// Now unwind the spiral.
|
||||
coordf_t r = 0;
|
||||
coordf_t r_inc = sqrt(2.);
|
||||
Pointfs out;
|
||||
out.emplace_back(0., 0.);
|
||||
output.add_point({ 0., 0. });
|
||||
while (r < rmax) {
|
||||
r += r_inc;
|
||||
coordf_t rx = r / sqrt(2.);
|
||||
coordf_t r2 = r + rx;
|
||||
out.emplace_back( r, 0.);
|
||||
out.emplace_back( r2, rx);
|
||||
out.emplace_back( rx, rx);
|
||||
out.emplace_back( rx, r2);
|
||||
out.emplace_back( 0., r);
|
||||
out.emplace_back(-rx, r2);
|
||||
out.emplace_back(-rx, rx);
|
||||
out.emplace_back(-r2, rx);
|
||||
out.emplace_back(- r, 0.);
|
||||
out.emplace_back(-r2, -rx);
|
||||
out.emplace_back(-rx, -rx);
|
||||
out.emplace_back(-rx, -r2);
|
||||
out.emplace_back( 0., -r);
|
||||
out.emplace_back( rx, -r2);
|
||||
out.emplace_back( rx, -rx);
|
||||
out.emplace_back( r2+r_inc, -rx);
|
||||
output.add_point({ r, 0. });
|
||||
output.add_point({ r2, rx });
|
||||
output.add_point({ rx, rx });
|
||||
output.add_point({ rx, r2 });
|
||||
output.add_point({ 0., r });
|
||||
output.add_point({-rx, r2 });
|
||||
output.add_point({-rx, rx });
|
||||
output.add_point({-r2, rx });
|
||||
output.add_point({- r, 0. });
|
||||
output.add_point({-r2, -rx });
|
||||
output.add_point({-rx, -rx });
|
||||
output.add_point({-rx, -r2 });
|
||||
output.add_point({ 0., -r });
|
||||
output.add_point({ rx, -r2 });
|
||||
output.add_point({ rx, -rx });
|
||||
output.add_point({ r2+r_inc, -rx });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
|
||||
{
|
||||
if (output.clips())
|
||||
generate_octagram_spiral(min_x, min_y, max_x, max_y, static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
generate_octagram_spiral(min_x, min_y, max_x, max_y, output);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -13,6 +13,26 @@ namespace Slic3r {
|
|||
// http://user42.tuxfamily.org/math-planepath/
|
||||
// http://user42.tuxfamily.org/math-planepath/gallery.html
|
||||
|
||||
class InfillPolylineOutput {
|
||||
public:
|
||||
InfillPolylineOutput(const double scale_out) : m_scale_out(scale_out) {}
|
||||
|
||||
void reserve(size_t n) { m_out.reserve(n); }
|
||||
void add_point(const Vec2d& pt) { m_out.emplace_back(this->scaled(pt)); }
|
||||
Points&& result() { return std::move(m_out); }
|
||||
virtual bool clips() const { return false; }
|
||||
|
||||
protected:
|
||||
const Point scaled(const Vec2d& fpt) const { return { coord_t(floor(fpt.x() * m_scale_out + 0.5)), coord_t(floor(fpt.y() * m_scale_out + 0.5)) }; }
|
||||
|
||||
// Output polyline.
|
||||
Points m_out;
|
||||
|
||||
private:
|
||||
// Scaling coefficient of the generated points before tested against m_bbox and clipped by bbox.
|
||||
double m_scale_out;
|
||||
};
|
||||
|
||||
class FillPlanePath : public Fill
|
||||
{
|
||||
public:
|
||||
|
@ -27,8 +47,11 @@ protected:
|
|||
Polylines &polylines_out) override;
|
||||
|
||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
virtual bool _centered() const = 0;
|
||||
virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) = 0;
|
||||
virtual bool centered() const = 0;
|
||||
|
||||
friend class InfillPolylineClipper;
|
||||
|
||||
virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) = 0;
|
||||
};
|
||||
|
||||
class FillArchimedeanChords : public FillPlanePath
|
||||
|
@ -38,8 +61,8 @@ public:
|
|||
~FillArchimedeanChords() override = default;
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return true; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
bool centered() const override { return true; }
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
class FillHilbertCurve : public FillPlanePath
|
||||
|
@ -49,8 +72,8 @@ public:
|
|||
~FillHilbertCurve() override = default;
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return false; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
bool centered() const override { return false; }
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
class FillOctagramSpiral : public FillPlanePath
|
||||
|
@ -60,8 +83,8 @@ public:
|
|||
~FillOctagramSpiral() override = default;
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return true; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
bool centered() const override { return true; }
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
Loading…
Reference in a new issue