Merge branch 'master' into fs_emboss

# Conflicts:
#	src/libslic3r/Point.hpp
This commit is contained in:
Filip Sykala - NTB T15p 2022-11-29 15:11:53 +01:00
commit d5fedd928c
121 changed files with 12367 additions and 12670 deletions

View File

@ -32,18 +32,12 @@ use Moo 1.003001;
use Slic3r::XS; # import all symbols (constants etc.) before they get parsed
use Slic3r::Config;
use Slic3r::ExPolygon;
use Slic3r::ExtrusionLoop;
use Slic3r::ExtrusionPath;
use Slic3r::GCode::Reader;
use Slic3r::Layer;
use Slic3r::Line;
use Slic3r::Model;
use Slic3r::Point;
use Slic3r::Polygon;
use Slic3r::Polyline;
use Slic3r::Print::Object;
use Slic3r::Surface;
our $build = eval "use Slic3r::Build; 1";
# Scaling between the float and integer coordinates.

View File

@ -1,12 +0,0 @@
package Slic3r::ExPolygon;
use strict;
use warnings;
# an ExPolygon is a polygon with holes
sub bounding_box {
my $self = shift;
return $self->contour->bounding_box;
}
1;

View File

@ -1,12 +0,0 @@
package Slic3r::ExtrusionLoop;
use strict;
use warnings;
use parent qw(Exporter);
our @EXPORT_OK = qw(EXTRL_ROLE_DEFAULT
EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER EXTRL_ROLE_SKIRT);
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
1;

View File

@ -1,13 +0,0 @@
package Slic3r::ExtrusionPath;
use strict;
use warnings;
use parent qw(Exporter);
our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER
EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_GAPFILL EXTR_ROLE_BRIDGE
EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE
EXTR_ROLE_NONE);
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
1;

View File

@ -34,21 +34,4 @@ sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR }
sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR }
# 2D
sub bounding_box {
my ($points) = @_;
my @x = map $_->x, @$points;
my @y = map $_->y, @$points; #,,
my @bb = (undef, undef, undef, undef);
for (0..$#x) {
$bb[X1] = $x[$_] if !defined $bb[X1] || $x[$_] < $bb[X1];
$bb[X2] = $x[$_] if !defined $bb[X2] || $x[$_] > $bb[X2];
$bb[Y1] = $y[$_] if !defined $bb[Y1] || $y[$_] < $bb[Y1];
$bb[Y2] = $y[$_] if !defined $bb[Y2] || $y[$_] > $bb[Y2];
}
return @bb[X1,Y1,X2,Y2];
}
1;

View File

@ -1,18 +0,0 @@
# Extends the C++ class Slic3r::Layer.
package Slic3r::Layer;
use strict;
use warnings;
# the following two were previously generated by Moo
sub print {
my $self = shift;
return $self->object->print;
}
sub config {
my $self = shift;
return $self->object->config;
}
1;

View File

@ -1,14 +0,0 @@
package Slic3r::Print::Object;
# extends c++ class Slic3r::PrintObject (Print.xsp)
use strict;
use warnings;
use List::Util qw(min max sum first);
use Slic3r::Surface ':types';
sub layers {
my $self = shift;
return [ map $self->get_layer($_), 0..($self->layer_count - 1) ];
}
1;

View File

@ -1,15 +0,0 @@
package Slic3r::Surface;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(S_TYPE_TOP S_TYPE_BOTTOM S_TYPE_BOTTOMBRIDGE S_TYPE_INTERNAL S_TYPE_INTERNALSOLID S_TYPE_INTERNALBRIDGE S_TYPE_INTERNALVOID);
our %EXPORT_TAGS = (types => \@EXPORT_OK);
sub p {
my $self = shift;
return @{$self->polygons};
}
1;

View File

@ -225,7 +225,7 @@ int PointInPolygon(const IntPoint &pt, const Path &path)
if (ipNext.x() > pt.x()) result = 1 - result;
else
{
double d = (double)(ip.x() - pt.x()) * (ipNext.y() - pt.y()) - (double)(ipNext.x() - pt.x()) * (ip.y() - pt.y());
auto d = CrossProductType(ip.x() - pt.x()) * CrossProductType(ipNext.y() - pt.y()) - CrossProductType(ipNext.x() - pt.x()) * CrossProductType(ip.y() - pt.y());
if (!d) return -1;
if ((d > 0) == (ipNext.y() > ip.y())) result = 1 - result;
}
@ -233,7 +233,7 @@ int PointInPolygon(const IntPoint &pt, const Path &path)
{
if (ipNext.x() > pt.x())
{
double d = (double)(ip.x() - pt.x()) * (ipNext.y() - pt.y()) - (double)(ipNext.x() - pt.x()) * (ip.y() - pt.y());
auto d = CrossProductType(ip.x() - pt.x()) * CrossProductType(ipNext.y() - pt.y()) - CrossProductType(ipNext.x() - pt.x()) * CrossProductType(ip.y() - pt.y());
if (!d) return -1;
if ((d > 0) == (ipNext.y() > ip.y())) result = 1 - result;
}
@ -265,7 +265,7 @@ int PointInPolygon (const IntPoint &pt, OutPt *op)
if (op->Next->Pt.x() > pt.x()) result = 1 - result;
else
{
double d = (double)(op->Pt.x() - pt.x()) * (op->Next->Pt.y() - pt.y()) - (double)(op->Next->Pt.x() - pt.x()) * (op->Pt.y() - pt.y());
auto d = CrossProductType(op->Pt.x() - pt.x()) * CrossProductType(op->Next->Pt.y() - pt.y()) - CrossProductType(op->Next->Pt.x() - pt.x()) * CrossProductType(op->Pt.y() - pt.y());
if (!d) return -1;
if ((d > 0) == (op->Next->Pt.y() > op->Pt.y())) result = 1 - result;
}
@ -273,7 +273,7 @@ int PointInPolygon (const IntPoint &pt, OutPt *op)
{
if (op->Next->Pt.x() > pt.x())
{
double d = (double)(op->Pt.x() - pt.x()) * (op->Next->Pt.y() - pt.y()) - (double)(op->Next->Pt.x() - pt.x()) * (op->Pt.y() - pt.y());
auto d = CrossProductType(op->Pt.x() - pt.x()) * CrossProductType(op->Next->Pt.y() - pt.y()) - CrossProductType(op->Next->Pt.x() - pt.x()) * CrossProductType(op->Pt.y() - pt.y());
if (!d) return -1;
if ((d > 0) == (op->Next->Pt.y() > op->Pt.y())) result = 1 - result;
}

View File

@ -85,9 +85,11 @@ enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
// Point coordinate type
#ifdef CLIPPERLIB_INT32
// Coordinates and their differences (vectors of the coordinates) have to fit int32_t.
typedef int32_t cInt;
using cInt = int32_t;
using CrossProductType = int64_t;
#else
typedef int64_t cInt;
using cInt = int64_t;
using CrossProductType = double;
// Maximum cInt value to allow a cross product calculation using 32bit expressions.
static constexpr cInt const loRange = 0x3FFFFFFF; // 0x3FFFFFFF = 1 073 741 823
// Maximum allowed cInt value.

View File

@ -274,7 +274,7 @@ std::vector<Point> SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_
Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments);
coord_t d = (right_point - left_point).cast<int64_t>().norm();
Point middle = (left_point + right_point) / 2;
Point x_axis_dir = Point(right_point - left_point).rotate_90_degree_ccw();
Point x_axis_dir = perp(Point(right_point - left_point));
coord_t x_axis_length = x_axis_dir.cast<int64_t>().norm();
const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge.

View File

@ -10,6 +10,11 @@
#include "SkeletalTrapezoidationEdge.hpp"
#include "SkeletalTrapezoidationJoint.hpp"
namespace Slic3r
{
class Line;
};
namespace Slic3r::Arachne
{

View File

@ -268,13 +268,13 @@ void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extr
{
for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) {
ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path);
Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled<float>(0.05), float(SCALED_EPSILON)).paths);
Slic3r::append(dst, PerimeterGenerator::thick_polyline_to_multi_path(thick_polyline, role, flow, scaled<float>(0.05), float(SCALED_EPSILON)).paths);
}
}
void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow)
{
ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion);
Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled<float>(0.05), float(SCALED_EPSILON)).paths);
Slic3r::append(dst, PerimeterGenerator::thick_polyline_to_multi_path(thick_polyline, role, flow, scaled<float>(0.05), float(SCALED_EPSILON)).paths);
}
} // namespace Slic3r

View File

@ -165,7 +165,7 @@ std::vector<Point> VoronoiUtils::discretizeParabola(const Point& p, const Segmen
Line(a, b).distance_to_infinite_squared(p, &pxx);
const Point ppxx = pxx - p;
const coord_t d = ppxx.cast<int64_t>().norm();
const PointMatrix rot = PointMatrix(ppxx.rotate_90_degree_ccw());
const PointMatrix rot = PointMatrix(perp(ppxx));
if (d == 0)
{

View File

@ -38,15 +38,11 @@ inline static bool isInsideCorner(const Point &a, const Point &b, const Point &c
return (p0.cast<int64_t>() * int64_t(len) / _len).cast<coord_t>();
};
auto rotate_90_degree_ccw = [](const Vec2d &p) -> Vec2d {
return {-p.y(), p.x()};
};
constexpr coord_t normal_length = 10000; //Create a normal vector of reasonable length in order to reduce rounding error.
const Point ba = normal(a - b, normal_length);
const Point bc = normal(c - b, normal_length);
const Vec2d bq = query_point.cast<double>() - b.cast<double>();
const Vec2d perpendicular = rotate_90_degree_ccw(bq); //The query projects to this perpendicular to coordinate 0.
const Vec2d perpendicular = perp(bq); //The query projects to this perpendicular to coordinate 0.
const double project_a_perpendicular = ba.cast<double>().dot(perpendicular); //Project vertex A on the perpendicular line.
const double project_c_perpendicular = bc.cast<double>().dot(perpendicular); //Project vertex C on the perpendicular line.

View File

@ -187,6 +187,8 @@ public:
friend BoundingBox get_extents_rotated(const Points &points, double angle);
};
using BoundingBoxes = std::vector<BoundingBox>;
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
{
public:
@ -242,6 +244,39 @@ auto cast(const BoundingBox3Base<Tin> &b)
b.max.template cast<Tout>()};
}
// Distance of a point to a bounding box. Zero inside and on the boundary, positive outside.
inline double bbox_point_distance(const BoundingBox &bbox, const Point &pt)
{
if (pt.x() < bbox.min.x())
return pt.y() < bbox.min.y() ? (bbox.min - pt).cast<double>().norm() :
pt.y() > bbox.max.y() ? (Point(bbox.min.x(), bbox.max.y()) - pt).cast<double>().norm() :
double(bbox.min.x() - pt.x());
else if (pt.x() > bbox.max.x())
return pt.y() < bbox.min.y() ? (Point(bbox.max.x(), bbox.min.y()) - pt).cast<double>().norm() :
pt.y() > bbox.max.y() ? (bbox.max - pt).cast<double>().norm() :
double(pt.x() - bbox.max.x());
else
return pt.y() < bbox.min.y() ? bbox.min.y() - pt.y() :
pt.y() > bbox.max.y() ? pt.y() - bbox.max.y() :
coord_t(0);
}
inline double bbox_point_distance_squared(const BoundingBox &bbox, const Point &pt)
{
if (pt.x() < bbox.min.x())
return pt.y() < bbox.min.y() ? (bbox.min - pt).cast<double>().squaredNorm() :
pt.y() > bbox.max.y() ? (Point(bbox.min.x(), bbox.max.y()) - pt).cast<double>().squaredNorm() :
Slic3r::sqr(double(bbox.min.x() - pt.x()));
else if (pt.x() > bbox.max.x())
return pt.y() < bbox.min.y() ? (Point(bbox.max.x(), bbox.min.y()) - pt).cast<double>().squaredNorm() :
pt.y() > bbox.max.y() ? (bbox.max - pt).cast<double>().squaredNorm() :
Slic3r::sqr<double>(pt.x() - bbox.max.x());
else
return Slic3r::sqr<double>(pt.y() < bbox.min.y() ? bbox.min.y() - pt.y() :
pt.y() > bbox.max.y() ? pt.y() - bbox.max.y() :
coord_t(0));
}
} // namespace Slic3r
// Serialization through the Cereal library

View File

@ -53,7 +53,7 @@ static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &pr
{
ExPolygons ex_polygons;
for (LayerRegion *region : print_object.layers().front()->regions())
Slic3r::append(ex_polygons, closing_ex(region->slices.surfaces, float(SCALED_EPSILON)));
Slic3r::append(ex_polygons, closing_ex(region->slices().surfaces, float(SCALED_EPSILON)));
return ex_polygons;
}

View File

@ -48,6 +48,100 @@ err:
namespace ClipperUtils {
Points EmptyPathsProvider::s_empty_points;
Points SinglePathProvider::s_end;
// Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon.
// Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by one
// with a set of polygons covering the whole layer below.
template<typename PointType>
inline void clip_clipper_polygon_with_subject_bbox_templ(const std::vector<PointType> &src, const BoundingBox &bbox, std::vector<PointType> &out)
{
out.clear();
const size_t cnt = src.size();
if (cnt < 3)
return;
enum class Side {
Left = 1,
Right = 2,
Top = 4,
Bottom = 8
};
auto sides = [bbox](const PointType &p) {
return int(p.x() < bbox.min.x()) * int(Side::Left) +
int(p.x() > bbox.max.x()) * int(Side::Right) +
int(p.y() < bbox.min.y()) * int(Side::Bottom) +
int(p.y() > bbox.max.y()) * int(Side::Top);
};
int sides_prev = sides(src.back());
int sides_this = sides(src.front());
const size_t last = cnt - 1;
for (size_t i = 0; i < last; ++ i) {
int sides_next = sides(src[i + 1]);
if (// This point is inside. Take it.
sides_this == 0 ||
// Either this point is outside and previous or next is inside, or
// the edge possibly cuts corner of the bounding box.
(sides_prev & sides_this & sides_next) == 0) {
out.emplace_back(src[i]);
sides_prev = sides_this;
} else {
// All the three points (this, prev, next) are outside at the same side.
// Ignore this point.
}
sides_this = sides_next;
}
// Never produce just a single point output polygon.
if (! out.empty())
if (int sides_next = sides(out.front());
// The last point is inside. Take it.
sides_this == 0 ||
// Either this point is outside and previous or next is inside, or
// the edge possibly cuts corner of the bounding box.
(sides_prev & sides_this & sides_next) == 0)
out.emplace_back(src.back());
}
void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out)
{ clip_clipper_polygon_with_subject_bbox_templ(src, bbox, out); }
void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out)
{ clip_clipper_polygon_with_subject_bbox_templ(src, bbox, out); }
template<typename PointType>
[[nodiscard]] std::vector<PointType> clip_clipper_polygon_with_subject_bbox_templ(const std::vector<PointType> &src, const BoundingBox &bbox)
{
std::vector<PointType> out;
clip_clipper_polygon_with_subject_bbox(src, bbox, out);
return out;
}
[[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox)
{ return clip_clipper_polygon_with_subject_bbox_templ(src, bbox); }
[[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox)
{ return clip_clipper_polygon_with_subject_bbox_templ(src, bbox); }
void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out)
{
clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points);
}
[[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox)
{
Polygon out;
clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points);
return out;
}
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox)
{
Polygons out;
out.reserve(src.size());
for (const Polygon &p : src)
out.emplace_back(clip_clipper_polygon_with_subject_bbox(p, bbox));
return out;
}
}
static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree)
@ -432,6 +526,8 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d
{ return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); }
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
@ -535,6 +631,8 @@ Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &cli
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons diff_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return diff(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); }
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)

View File

@ -24,6 +24,8 @@ using Slic3r::ClipperLib::jtSquare;
namespace Slic3r {
class BoundingBox;
static constexpr const float ClipperSafetyOffset = 10.f;
static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter;
@ -306,6 +308,21 @@ namespace ClipperUtils {
const SurfacesPtr &m_surfaces;
size_t m_size;
};
// For ClipperLib with Z coordinates.
using ZPoint = Vec3i32;
using ZPoints = std::vector<Vec3i32>;
// Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon.
// Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by one
// with a set of polygons covering the whole layer below.
void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out);
void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out);
[[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox);
[[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox);
void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out);
[[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox);
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox);
}
// offset Polygons
@ -326,6 +343,7 @@ Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); }
@ -390,6 +408,9 @@ Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &su
Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox().
// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon.
Slic3r::Polygons diff_clipped(const Slic3r::Polygons &src, const Slic3r::Polygons &clipping, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);

View File

@ -218,7 +218,7 @@ ColorRGBA ColorRGBA::operator * (float value) const
for (size_t i = 0; i < 3; ++i) {
ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f);
}
ret.m_data[3] = this->m_data[3];
ret.m_data[3] = m_data[3];
return ret;
}

View File

@ -98,34 +98,53 @@ bool ExPolygon::contains(const Polylines &polylines) const
return pl_out.empty();
}
bool ExPolygon::contains(const Point &point) const
bool ExPolygon::contains(const Point &point, bool border_result /* = true */) const
{
if (! this->contour.contains(point))
if (! Slic3r::contains(contour, point, border_result))
// Outside the outer contour, not on the contour boundary.
return false;
for (const Polygon &hole : this->holes)
if (hole.contains(point))
if (Slic3r::contains(hole, point, ! border_result))
// Inside a hole, not on the hole boundary.
return false;
return true;
}
// inclusive version of contains() that also checks whether point is on boundaries
bool ExPolygon::contains_b(const Point &point) const
bool ExPolygon::on_boundary(const Point &point, double eps) const
{
return this->contains(point) || this->has_boundary_point(point);
if (this->contour.on_boundary(point, eps))
return true;
for (const Polygon &hole : this->holes)
if (hole.on_boundary(point, eps))
return true;
return false;
}
bool
ExPolygon::has_boundary_point(const Point &point) const
// Projection of a point onto the polygon.
Point ExPolygon::point_projection(const Point &point) const
{
if (this->contour.has_boundary_point(point)) return true;
for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) {
if (h->has_boundary_point(point)) return true;
if (this->holes.empty()) {
return this->contour.point_projection(point);
} else {
double dist_min2 = std::numeric_limits<double>::max();
Point closest_pt_min;
for (size_t i = 0; i < this->num_contours(); ++ i) {
Point closest_pt = this->contour_or_hole(i).point_projection(point);
double d2 = (closest_pt - point).cast<double>().squaredNorm();
if (d2 < dist_min2) {
dist_min2 = d2;
closest_pt_min = closest_pt;
}
}
return closest_pt_min;
}
return false;
}
bool ExPolygon::overlaps(const ExPolygon &other) const
{
if (this->empty() || other.empty())
return false;
#if 0
BoundingBox bbox = get_extents(other);
bbox.merge(get_extents(*this));
@ -135,14 +154,18 @@ bool ExPolygon::overlaps(const ExPolygon &other) const
svg.draw_outline(*this);
svg.draw_outline(other, "blue");
#endif
Polylines pl_out = intersection_pl(to_polylines(other), *this);
#if 0
svg.draw(pl_out, "red");
#endif
if (! pl_out.empty())
return true;
//FIXME ExPolygon::overlaps() shall be commutative, it is not!
return ! other.contour.points.empty() && this->contains_b(other.contour.points.front());
// See unit test SCENARIO("Clipper diff with polyline", "[Clipper]")
// for in which case the intersection_pl produces any intersection.
return ! pl_out.empty() ||
// If *this is completely inside other, then pl_out is empty, but the expolygons overlap. Test for that situation.
other.contains(this->contour.points.front());
}
void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const
@ -183,12 +206,10 @@ void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
append(*expolygons, this->simplify(tolerance));
}
void
ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const
void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* polylines) const
{
// init helper object
Slic3r::Geometry::MedialAxis ma(max_width, min_width, this);
ma.lines = this->lines();
Slic3r::Geometry::MedialAxis ma(min_width, max_width, *this);
// compute the Voronoi diagram and extract medial axis polylines
ThickPolylines pp;
@ -219,7 +240,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
call, so we keep the inner point until we perform the second intersection() as well */
Point new_front = polyline.points.front();
Point new_back = polyline.points.back();
if (polyline.endpoints.first && !this->has_boundary_point(new_front)) {
if (polyline.endpoints.first && !this->on_boundary(new_front, SCALED_EPSILON)) {
Vec2d p1 = polyline.points.front().cast<double>();
Vec2d p2 = polyline.points[1].cast<double>();
// prevent the line from touching on the other side, otherwise intersection() might return that solution
@ -229,7 +250,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
p1 -= (p2 - p1).normalized() * max_width;
this->contour.intersection(Line(p1.cast<coord_t>(), p2.cast<coord_t>()), &new_front);
}
if (polyline.endpoints.second && !this->has_boundary_point(new_back)) {
if (polyline.endpoints.second && !this->on_boundary(new_back, SCALED_EPSILON)) {
Vec2d p1 = (polyline.points.end() - 2)->cast<double>();
Vec2d p2 = polyline.points.back().cast<double>();
// prevent the line from touching on the other side, otherwise intersection() might return that solution
@ -295,11 +316,10 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
polylines->insert(polylines->end(), pp.begin(), pp.end());
}
void
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polylines) const
{
ThickPolylines tp;
this->medial_axis(max_width, min_width, &tp);
this->medial_axis(min_width, max_width, &tp);
polylines->insert(polylines->end(), tp.begin(), tp.end());
}
@ -313,6 +333,18 @@ Lines ExPolygon::lines() const
return lines;
}
// Do expolygons match? If they match, they must have the same topology,
// however their contours may be rotated.
bool expolygons_match(const ExPolygon &l, const ExPolygon &r)
{
if (l.holes.size() != r.holes.size() || ! polygons_match(l.contour, r.contour))
return false;
for (size_t hole_idx = 0; hole_idx < l.holes.size(); ++ hole_idx)
if (! polygons_match(l.holes[hole_idx], r.holes[hole_idx]))
return false;
return true;
}
BoundingBox get_extents(const ExPolygon &expolygon)
{
return get_extents(expolygon.contour);

View File

@ -51,23 +51,29 @@ public:
bool contains(const Line &line) const;
bool contains(const Polyline &polyline) const;
bool contains(const Polylines &polylines) const;
bool contains(const Point &point) const;
bool contains_b(const Point &point) const;
bool has_boundary_point(const Point &point) const;
bool contains(const Point &point, bool border_result = true) const;
// Approximate on boundary test.
bool on_boundary(const Point &point, double eps) const;
// Projection of a point onto the polygon.
Point point_projection(const Point &point) const;
// Does this expolygon overlap another expolygon?
// Either the ExPolygons intersect, or one is fully inside the other,
// and it is not inside a hole of the other expolygon.
// The test may not be commutative if the two expolygons touch by a boundary only,
// see unit test SCENARIO("Clipper diff with polyline", "[Clipper]").
// Namely expolygons touching at a vertical boundary are considered overlapping, while expolygons touching
// at a horizontal boundary are NOT considered overlapping.
bool overlaps(const ExPolygon &other) const;
void simplify_p(double tolerance, Polygons* polygons) const;
Polygons simplify_p(double tolerance) const;
ExPolygons simplify(double tolerance) const;
void simplify(double tolerance, ExPolygons* expolygons) const;
void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const;
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
Polylines medial_axis(double max_width, double min_width) const
{ Polylines out; this->medial_axis(max_width, min_width, &out); return out; }
void medial_axis(double min_width, double max_width, ThickPolylines* polylines) const;
void medial_axis(double min_width, double max_width, Polylines* polylines) const;
Polylines medial_axis(double min_width, double max_width) const
{ Polylines out; this->medial_axis(min_width, max_width, &out); return out; }
Lines lines() const;
// Number of contours (outer contour with holes).
@ -420,14 +426,14 @@ inline void expolygons_append(ExPolygons &dst, ExPolygons &&src)
inline void expolygons_rotate(ExPolygons &expolys, double angle)
{
for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p)
p->rotate(angle);
for (ExPolygon &expoly : expolys)
expoly.rotate(angle);
}
inline bool expolygons_contain(ExPolygons &expolys, const Point &pt)
inline bool expolygons_contain(ExPolygons &expolys, const Point &pt, bool border_result = true)
{
for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p)
if (p->contains(pt))
for (const ExPolygon &expoly : expolys)
if (expoly.contains(pt, border_result))
return true;
return false;
}
@ -441,6 +447,10 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc
return out;
}
// Do expolygons match? If they match, they must have the same topology,
// however their contours may be rotated.
bool expolygons_match(const ExPolygon &l, const ExPolygon &r);
BoundingBox get_extents(const ExPolygon &expolygon);
BoundingBox get_extents(const ExPolygons &expolygons);
BoundingBox get_extents_rotated(const ExPolygon &poly, double angle);

View File

@ -186,7 +186,7 @@ public:
ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); }
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); }
virtual bool can_reverse() const { return false; }
virtual bool can_reverse() const override { return false; }
};
typedef std::vector<ExtrusionPath> ExtrusionPaths;

View File

@ -45,6 +45,13 @@ public:
~ExtrusionEntityCollection() override { clear(); }
explicit operator ExtrusionPaths() const;
ExtrusionEntitiesPtr::const_iterator cbegin() const { return this->entities.cbegin(); }
ExtrusionEntitiesPtr::const_iterator cend() const { return this->entities.cend(); }
ExtrusionEntitiesPtr::const_iterator begin() const { return this->entities.cbegin(); }
ExtrusionEntitiesPtr::const_iterator end() const { return this->entities.cend(); }
ExtrusionEntitiesPtr::iterator begin() { return this->entities.begin(); }
ExtrusionEntitiesPtr::iterator end() { return this->entities.end(); }
bool is_collection() const override { return true; }
ExtrusionRole role() const override {
ExtrusionRole out = erNone;
@ -106,6 +113,9 @@ public:
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
size_t size() const { return entities.size(); }
// Recursively count paths and loops contained in this collection.
// this->items_count() >= this->size()
size_t items_count() const;
/// Returns a flattened copy of this ExtrusionEntityCollection. That is, all of the items in its entities vector are not collections.
/// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy).
@ -121,12 +131,12 @@ public:
};
void collect_polylines(Polylines &dst) const override {
for (ExtrusionEntity* extrusion_entity : this->entities)
for (const ExtrusionEntity *extrusion_entity : this->entities)
extrusion_entity->collect_polylines(dst);
}
void collect_points(Points &dst) const override {
for (ExtrusionEntity* extrusion_entity : this->entities)
for (const ExtrusionEntity *extrusion_entity : this->entities)
extrusion_entity->collect_points(dst);
}

View File

@ -8,6 +8,7 @@
#include "../Print.hpp"
#include "../PrintConfig.hpp"
#include "../Surface.hpp"
// for Arachne based infills
#include "../PerimeterGenerator.hpp"
#include "FillBase.hpp"
@ -121,8 +122,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
bool has_internal_voids = false;
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
const LayerRegion &layerm = *layer.regions()[region_id];
region_to_surface_params[region_id].assign(layerm.fill_surfaces.size(), nullptr);
for (const Surface &surface : layerm.fill_surfaces.surfaces)
region_to_surface_params[region_id].assign(layerm.fill_surfaces().size(), nullptr);
for (const Surface &surface : layerm.fill_surfaces())
if (surface.surface_type == stInternalVoid)
has_internal_voids = true;
else {
@ -180,7 +181,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
auto it_params = set_surface_params.find(params);
if (it_params == set_surface_params.end())
it_params = set_surface_params.insert(it_params, params);
region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()] = &(*it_params);
region_to_surface_params[region_id][&surface - &layerm.fill_surfaces().surfaces.front()] = &(*it_params);
}
}
@ -192,9 +193,9 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
const LayerRegion &layerm = *layer.regions()[region_id];
for (const Surface &surface : layerm.fill_surfaces.surfaces)
for (const Surface &surface : layerm.fill_surfaces())
if (surface.surface_type != stInternalVoid) {
const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()];
const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces().surfaces.front()];
if (params != nullptr) {
SurfaceFill &fill = surface_fills[params->idx];
if (fill.region_id == size_t(-1)) {
@ -321,12 +322,104 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
}
#endif
// friend to Layer
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator)
static void insert_fills_into_islands(Layer &layer, uint32_t fill_region_id, uint32_t fill_begin, uint32_t fill_end)
{
if (fill_begin < fill_end) {
// Sort the extrusion range into its LayerIsland.
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
auto point_inside_surface = [&layer](const size_t lslice_idx, const Point &point) {
const BoundingBox &bbox = layer.lslices_ex[lslice_idx].bbox;
return point.x() >= bbox.min.x() && point.x() < bbox.max.x() &&
point.y() >= bbox.min.y() && point.y() < bbox.max.y() &&
layer.lslices[lslice_idx].contour.contains(point);
};
Point point = layer.get_region(fill_region_id)->fills().entities[fill_begin]->first_point();
int lslice_idx = int(layer.lslices_ex.size()) - 1;
for (; lslice_idx >= 0; -- lslice_idx)
if (point_inside_surface(lslice_idx, point))
break;
assert(lslice_idx >= 0);
if (lslice_idx >= 0) {
LayerSlice &lslice = layer.lslices_ex[lslice_idx];
// Find an island.
LayerIsland *island = nullptr;
if (lslice.islands.size() == 1) {
// Cool, just save the extrusions in there.
island = &lslice.islands.front();
} else {
// The infill was created for one of the infills.
// In case of ironing, the infill may not fall into any of the infill expolygons either.
// In case of some numerical error, the infill may not fall into any of the infill expolygons either.
// 1) Try an exact test, it should be cheaper than a closest region test.
for (LayerIsland &li : lslice.islands) {
const BoundingBoxes &bboxes = li.fill_expolygons_composite() ?
layer.get_region(li.perimeters.region())->fill_expolygons_composite_bboxes() :
layer.get_region(li.fill_region_id)->fill_expolygons_bboxes();
const ExPolygons &expolygons = li.fill_expolygons_composite() ?
layer.get_region(li.perimeters.region())->fill_expolygons_composite() :
layer.get_region(li.fill_region_id)->fill_expolygons();
for (uint32_t fill_expolygon_id : li.fill_expolygons)
if (bboxes[fill_expolygon_id].contains(point) && expolygons[fill_expolygon_id].contains(point)) {
island = &li;
goto found;
}
}
// 2) Find closest fill_expolygon, branch and bound by distance to bounding box.
{
struct Island {
uint32_t island_idx;
uint32_t expolygon_idx;
double distance2;
};
std::vector<Island> islands_sorted;
for (uint32_t island_idx = 0; island_idx < uint32_t(lslice.islands.size()); ++ island_idx) {
const LayerIsland &li = lslice.islands[island_idx];
const BoundingBoxes &bboxes = li.fill_expolygons_composite() ?
layer.get_region(li.perimeters.region())->fill_expolygons_composite_bboxes() :
layer.get_region(li.fill_region_id)->fill_expolygons_bboxes();
for (uint32_t fill_expolygon_id : li.fill_expolygons)
islands_sorted.push_back({ island_idx, fill_expolygon_id, bbox_point_distance_squared(bboxes[fill_expolygon_id], point) });
}
std::sort(islands_sorted.begin(), islands_sorted.end(), [](auto &l, auto &r){ return l.distance2 < r.distance2; });
auto dist_min2 = std::numeric_limits<double>::max();
for (uint32_t sorted_bbox_idx = 0; sorted_bbox_idx < uint32_t(islands_sorted.size()); ++ sorted_bbox_idx) {
const Island &isl = islands_sorted[sorted_bbox_idx];
if (isl.distance2 > dist_min2)
// Branch & bound condition.
break;
LayerIsland &li = lslice.islands[isl.island_idx];
const ExPolygons &expolygons = li.fill_expolygons_composite() ?
layer.get_region(li.perimeters.region())->fill_expolygons_composite() :
layer.get_region(li.fill_region_id)->fill_expolygons();
double d2 = (expolygons[isl.expolygon_idx].point_projection(point) - point).cast<double>().squaredNorm();
if (d2 < dist_min2) {
dist_min2 = d2;
island = &li;
}
}
}
found:;
}
assert(island);
if (island)
island->add_fill_range(LayerExtrusionRange{ fill_region_id, { fill_begin, fill_end }});
}
}
}
void Layer::clear_fills()
{
for (LayerRegion *layerm : m_regions)
layerm->fills.clear();
layerm->m_fills.clear();
for (LayerSlice &lslice : lslices_ex)
for (LayerIsland &island : lslice.islands)
island.fills.clear();
}
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator)
{
this->clear_fills();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
// this->export_region_fill_surfaces_to_svg_debug("10_fill-initial");
@ -381,6 +474,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
LayerRegion &layerm = *m_regions[surface_fill.region_id];
// apply half spacing using this flow's own spacing and generate infill
FillParams params;
params.density = float(0.01 * surface_fill.params.density);
@ -389,7 +484,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution;
params.use_arachne = perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric;
params.layer_height = m_regions[surface_fill.region_id]->layer()->height;
params.layer_height = layerm.layer()->height;
for (ExPolygon &expoly : surface_fill.expolygons) {
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
@ -420,14 +515,15 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
}
// Save into layer.
ExtrusionEntityCollection* eec = nullptr;
m_regions[surface_fill.region_id]->fills.entities.push_back(eec = new ExtrusionEntityCollection());
auto fill_begin = uint32_t(layerm.fills().size());
layerm.m_fills.entities.push_back(eec = new ExtrusionEntityCollection());
// Only concentric fills are not sorted.
eec->no_sort = f->no_sort();
if (params.use_arachne) {
for (const ThickPolyline &thick_polyline : thick_polylines) {
Flow new_flow = surface_fill.params.flow.with_spacing(float(f->spacing));
ExtrusionMultiPath multi_path = thick_polyline_to_multi_path(thick_polyline, surface_fill.params.extrusion_role, new_flow, scaled<float>(0.05), float(SCALED_EPSILON));
ExtrusionMultiPath multi_path = PerimeterGenerator::thick_polyline_to_multi_path(thick_polyline, surface_fill.params.extrusion_role, new_flow, scaled<float>(0.05), float(SCALED_EPSILON));
// Append paths to collection.
if (!multi_path.empty()) {
if (multi_path.paths.front().first_point() == multi_path.paths.back().last_point())
@ -444,25 +540,47 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
surface_fill.params.extrusion_role,
flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height());
}
insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size()));
}
}
}
// add thin fill regions
// Unpacks the collection, creates multiple collections per path.
// The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection.
// Why the paths are unpacked?
for (LayerRegion *layerm : m_regions)
for (const ExtrusionEntity *thin_fill : layerm->thin_fills.entities) {
for (LayerSlice &lslice : this->lslices_ex)
for (LayerIsland &island : lslice.islands) {
if (! island.thin_fills.empty()) {
// Copy thin fills into fills packed as a collection.
// Fills are always stored as collections, the rest of the pipeline (wipe into infill, G-code generator) relies on it.
LayerRegion &layerm = *this->get_region(island.perimeters.region());
ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection());
layerm->fills.entities.push_back(&collection);
collection.entities.push_back(thin_fill->clone());
layerm.m_fills.entities.push_back(&collection);
collection.entities.reserve(island.thin_fills.size());
for (uint32_t fill_id : island.thin_fills)
collection.entities.push_back(layerm.thin_fills().entities[fill_id]->clone());
island.add_fill_range({ island.perimeters.region(), { uint32_t(layerm.m_fills.entities.size() - 1), uint32_t(layerm.m_fills.entities.size()) } });
}
// Sort the fills by region ID.
std::sort(island.fills.begin(), island.fills.end(), [](auto &l, auto &r){ return l.region() < r.region() || (l.region() == r.region() && *l.begin() < *r.begin()); });
// Compress continuous fill ranges of the same region.
{
size_t k = 0;
for (size_t i = 0; i < island.fills.size();) {
uint32_t region_id = island.fills[i].region();
uint32_t begin = *island.fills[i].begin();
uint32_t end = *island.fills[i].end();
size_t j = i + 1;
for (; j < island.fills.size() && island.fills[j].region() == region_id && *island.fills[j].begin() == end; ++ j)
end = *island.fills[j].end();
island.fills[k ++] = { region_id, { begin, end } };
i = j;
}
island.fills.erase(island.fills.begin() + k, island.fills.end());
}
}
#ifndef NDEBUG
for (LayerRegion *layerm : m_regions)
for (size_t i = 0; i < layerm->fills.entities.size(); ++ i)
assert(dynamic_cast<ExtrusionEntityCollection*>(layerm->fills.entities[i]) != nullptr);
for (const ExtrusionEntity *e : layerm->fills())
assert(dynamic_cast<const ExtrusionEntityCollection*>(e) != nullptr);
#endif
}
@ -518,7 +636,8 @@ void Layer::make_ironing()
this->angle == rhs.angle;
}
LayerRegion *layerm = nullptr;
LayerRegion *layerm;
uint32_t region_id;
// IdeaMaker: ironing
// ironing flowrate (5% percent)
@ -538,8 +657,8 @@ void Layer::make_ironing()
std::vector<IroningParams> by_extruder;
double default_layer_height = this->object()->config().layer_height;
for (LayerRegion *layerm : m_regions)
if (! layerm->slices.empty()) {
for (uint32_t region_id = 0; region_id < uint32_t(this->regions().size()); ++region_id)
if (LayerRegion *layerm = this->get_region(region_id); ! layerm->slices().empty()) {
IroningParams ironing_params;
const PrintRegionConfig &config = layerm->region().config();
if (config.ironing &&
@ -563,6 +682,7 @@ void Layer::make_ironing()
ironing_params.speed = config.ironing_speed;
ironing_params.angle = config.fill_angle * M_PI / 180.;
ironing_params.layerm = layerm;
ironing_params.region_id = region_id;
by_extruder.emplace_back(ironing_params);
}
}
@ -602,7 +722,7 @@ void Layer::make_ironing()
if (iron_everything) {
// Check whether there is any non-solid hole in the regions.
bool internal_infill_solid = region_config.fill_density.value > 95.;
for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces)
for (const Surface &surface : ironing_params.layerm->fill_surfaces())
if ((! internal_infill_solid && surface.surface_type == stInternal) || surface.surface_type == stInternalBridge || surface.surface_type == stInternalVoid) {
// Some fill region is not quite solid. Don't iron over the whole surface.
iron_completely = false;
@ -611,10 +731,10 @@ void Layer::make_ironing()
}
if (iron_completely) {
// Iron everything. This is likely only good for solid transparent objects.
for (const Surface &surface : ironing_params.layerm->slices.surfaces)
for (const Surface &surface : ironing_params.layerm->slices())
polygons_append(polys, surface.expolygon);
} else {
for (const Surface &surface : ironing_params.layerm->slices.surfaces)
for (const Surface &surface : ironing_params.layerm->slices())
if (surface.surface_type == stTop || (iron_everything && surface.surface_type == stBottom))
// stBottomBridge is not being ironed on purpose, as it would likely destroy the bridges.
polygons_append(polys, surface.expolygon);
@ -622,7 +742,7 @@ void Layer::make_ironing()
if (iron_everything && ! iron_completely) {
// Add solid fill surfaces. This may not be ideal, as one will not iron perimeters touching these
// solid fill surfaces, but it is likely better than nothing.
for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces)
for (const Surface &surface : ironing_params.layerm->fill_surfaces())
if (surface.surface_type == stInternalSolid)
polygons_append(infills, surface.expolygon);
}
@ -658,14 +778,16 @@ void Layer::make_ironing()
}
if (! polylines.empty()) {
// Save into layer.
auto fill_begin = uint32_t(ironing_params.layerm->fills().size());
ExtrusionEntityCollection *eec = nullptr;
ironing_params.layerm->fills.entities.push_back(eec = new ExtrusionEntityCollection());
ironing_params.layerm->m_fills.entities.push_back(eec = new ExtrusionEntityCollection());
// Don't sort the ironing infill lines as they are monotonicly ordered.
eec->no_sort = true;
extrusion_entities_append_paths(
eec->entities, std::move(polylines),
erIroning,
flow_mm3_per_mm, extrusion_width, float(extrusion_height));
insert_fills_into_islands(*this, ironing_params.region_id, fill_begin, uint32_t(ironing_params.layerm->fills().size()));
}
}

View File

@ -316,9 +316,9 @@ std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_ob
for (const Layer *layer : print_object.layers())
for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) {
RegionFillData &rd = region_fill_data[region_id];
if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces().empty())
rd.has_adaptive_infill = Tristate::Yes;
if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces().empty())
rd.has_support_infill = Tristate::Yes;
}

View File

@ -40,7 +40,7 @@ void FillConcentric::_fill_surface_single(
size_t iPathFirst = polylines_out.size();
Point last_pos(0, 0);
for (const Polygon &loop : loops) {
polylines_out.emplace_back(loop.split_at_index(last_pos.nearest_point_index(loop.points)));
polylines_out.emplace_back(loop.split_at_index(nearest_point_index(loop.points, last_pos)));
last_pos = polylines_out.back().last_point();
}
@ -100,7 +100,7 @@ void FillConcentric::_fill_surface_single(const FillParams &params,
if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) {
thick_polyline.points.pop_back();
assert(thick_polyline.points.size() * 2 == thick_polyline.width.size());
int nearest_idx = last_pos.nearest_point_index(thick_polyline.points);
int nearest_idx = nearest_point_index(thick_polyline.points, last_pos);
std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end());
std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end());
thick_polyline.points.emplace_back(thick_polyline.points.front());

View File

@ -62,7 +62,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object
throw_on_cancel_callback();
Polygons infill_area_here;
for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions())
for (const Surface& surface : layerm->fill_surfaces.surfaces)
for (const Surface& surface : layerm->fill_surfaces())
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
append(infill_area_here, to_polygons(surface.expolygon));
@ -93,7 +93,7 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi
for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--) {
throw_on_cancel_callback();
for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions())
for (const Surface &surface : layerm->fill_surfaces.surfaces)
for (const Surface &surface : layerm->fill_surfaces())
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
append(infill_outlines[layer_id], to_polygons(surface.expolygon));

View File

@ -119,8 +119,7 @@ namespace Slic3r {
Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
// find standby point
Point standby_point;
pos.nearest_point(this->standby_points, &standby_point);
Point standby_point = nearest_point(this->standby_points, pos).first;
/* We don't call gcodegen.travel_to() because we don't need retraction (it was already
triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
@ -478,9 +477,9 @@ namespace Slic3r {
// Collect pairs of object_layer + support_layer sorted by print_z.
// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON.
std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObject& object)
GCode::ObjectsLayerToPrint GCode::collect_layers_to_print(const PrintObject& object)
{
std::vector<GCode::LayerToPrint> layers_to_print;
GCode::ObjectsLayerToPrint layers_to_print;
layers_to_print.reserve(object.layers().size() + object.support_layers().size());
/*
@ -504,9 +503,9 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
// Pair the object layers with the support layers by z.
size_t idx_object_layer = 0;
size_t idx_support_layer = 0;
const LayerToPrint* last_extrusion_layer = nullptr;
const ObjectLayerToPrint* last_extrusion_layer = nullptr;
while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) {
LayerToPrint layer_to_print;
ObjectLayerToPrint layer_to_print;
layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr;
layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr;
if (layer_to_print.object_layer && layer_to_print.support_layer) {
@ -578,8 +577,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
// will be printed for all objects at once.
// Return a list of <print_z, per object LayerToPrint> items.
std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collect_layers_to_print(const Print& print)
// Return a list of <print_z, per object ObjectLayerToPrint> items.
std::vector<std::pair<coordf_t, GCode::ObjectsLayerToPrint>> GCode::collect_layers_to_print(const Print& print)
{
struct OrderingItem {
coordf_t print_z;
@ -587,15 +586,15 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
size_t layer_idx;
};
std::vector<std::vector<LayerToPrint>> per_object(print.objects().size(), std::vector<LayerToPrint>());
std::vector<ObjectsLayerToPrint> per_object(print.objects().size(), ObjectsLayerToPrint());
std::vector<OrderingItem> ordering;
for (size_t i = 0; i < print.objects().size(); ++i) {
per_object[i] = collect_layers_to_print(*print.objects()[i]);
OrderingItem ordering_item;
ordering_item.object_idx = i;
ordering.reserve(ordering.size() + per_object[i].size());
const LayerToPrint& front = per_object[i].front();
for (const LayerToPrint& ltp : per_object[i]) {
const ObjectLayerToPrint &front = per_object[i].front();
for (const ObjectLayerToPrint &ltp : per_object[i]) {
ordering_item.print_z = ltp.print_z();
ordering_item.layer_idx = &ltp - &front;
ordering.emplace_back(ordering_item);
@ -604,7 +603,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; });
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print;
std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> layers_to_print;
// Merge numerically very close Z values.
for (size_t i = 0; i < ordering.size();) {
@ -613,10 +612,10 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
coordf_t zmax = ordering[i].print_z + EPSILON;
for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j);
// Merge into layers_to_print.
std::pair<coordf_t, std::vector<LayerToPrint>> merged;
std::pair<coordf_t, ObjectsLayerToPrint> merged;
// Assign an average print_z to the set of layers with nearly equal print_z.
merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z);
merged.second.assign(print.objects().size(), LayerToPrint());
merged.second.assign(print.objects().size(), ObjectLayerToPrint());
for (; i < j; ++i) {
const OrderingItem& oi = ordering[i];
assert(merged.second[oi.object_idx].layer() == nullptr);
@ -852,7 +851,7 @@ namespace DoExport {
region.config().get_abs_value("small_perimeter_speed") == 0 ||
region.config().get_abs_value("external_perimeter_speed") == 0 ||
region.config().get_abs_value("bridge_speed") == 0)
mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm());
mm3_per_mm.push_back(layerm->perimeters().min_mm3_per_mm());
if (region.config().get_abs_value("infill_speed") == 0 ||
region.config().get_abs_value("solid_infill_speed") == 0 ||
region.config().get_abs_value("top_solid_infill_speed") == 0 ||
@ -869,7 +868,7 @@ namespace DoExport {
return min;
};
mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills));
mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills()));
}
}
}
@ -1350,7 +1349,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
} else {
// Sort layers by Z.
// All extrusion moves with the same top layer height are extruded uninterrupted.
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> layers_to_print = collect_layers_to_print(print);
// Prusa Multi-Material wipe tower.
if (has_wipe_tower && ! layers_to_print.empty()) {
m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()));
@ -1478,7 +1477,7 @@ void GCode::process_layers(
const Print &print,
const ToolOrdering &tool_ordering,
const std::vector<const PrintInstance*> &print_object_instances_ordering,
const std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> &layers_to_print,
const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
GCodeOutputStream &output_stream)
{
// The pipeline is variable: The vase mode filter is optional.
@ -1496,7 +1495,7 @@ void GCode::process_layers(
return LayerResult::make_nop_layer_result();
}
} else {
const std::pair<coordf_t, std::vector<LayerToPrint>>& layer = layers_to_print[layer_to_print_idx++];
const std::pair<coordf_t, ObjectsLayerToPrint> &layer = layers_to_print[layer_to_print_idx++];
const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first);
if (m_wipe_tower && layer_tools.has_wipe_tower)
m_wipe_tower->next_layer();
@ -1562,7 +1561,7 @@ void GCode::process_layers(
void GCode::process_layers(
const Print &print,
const ToolOrdering &tool_ordering,
std::vector<LayerToPrint> layers_to_print,
ObjectsLayerToPrint layers_to_print,
const size_t single_object_idx,
GCodeOutputStream &output_stream)
{
@ -1581,7 +1580,7 @@ void GCode::process_layers(
return LayerResult::make_nop_layer_result();
}
} else {
LayerToPrint &layer = layers_to_print[layer_to_print_idx ++];
ObjectLayerToPrint &layer = layers_to_print[layer_to_print_idx ++];
print.throw_if_canceled();
return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx);
}
@ -1832,34 +1831,8 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr
}
}
inline GCode::ObjectByExtruder& object_by_extruder(
std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
unsigned int extruder_id,
size_t object_idx,
size_t num_objects)
{
std::vector<GCode::ObjectByExtruder> &objects_by_extruder = by_extruder[extruder_id];
if (objects_by_extruder.empty())
objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder());
return objects_by_extruder[object_idx];
}
inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
unsigned int extruder_id,
size_t object_idx,
size_t num_objects,
size_t num_islands)
{
std::vector<GCode::ObjectByExtruder::Island> &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands;
if (islands.empty())
islands.assign(num_islands, GCode::ObjectByExtruder::Island());
return islands;
}
std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
std::vector<GCode::ObjectByExtruder> &objects_by_extruder,
const std::vector<LayerToPrint> &layers,
const std::vector<ObjectLayerToPrint> &object_layers,
// Ordering must be defined for normal (non-sequential print).
const std::vector<const PrintInstance*> *ordering,
// For sequential print, the instance of the object to be printing has to be defined.
@ -1869,33 +1842,28 @@ std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
if (ordering == nullptr) {
// Sequential print, single object is being printed.
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
const PrintObject *print_object = layers[layer_id].object();
if (print_object)
out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx);
}
assert(object_layers.size() == 1);
const Layer *layer = object_layers.front().object_layer;
assert(layer != nullptr);
out.emplace_back(0, *layer->object(), single_object_instance_idx);
} else {
// Create mapping from PrintObject* to ObjectByExtruder*.
std::vector<std::pair<const PrintObject*, ObjectByExtruder*>> sorted;
sorted.reserve(objects_by_extruder.size());
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
const PrintObject *print_object = layers[layer_id].object();
if (print_object)
sorted.emplace_back(print_object, &object_by_extruder);
}
// Create mapping from PrintObject* to ObjectLayerToPrint ID.
std::vector<std::pair<const PrintObject*, size_t>> sorted;
sorted.reserve(object_layers.size());
for (const ObjectLayerToPrint &object : object_layers)
if (const PrintObject* print_object = object.object(); print_object)
sorted.emplace_back(print_object, &object - object_layers.data());
std::sort(sorted.begin(), sorted.end());
if (! sorted.empty()) {
out.reserve(sorted.size());
for (const PrintInstance *instance : *ordering) {
const PrintObject &print_object = *instance->print_object;
std::pair<const PrintObject*, ObjectByExtruder*> key(&print_object, nullptr);
std::pair<const PrintObject*, size_t> key(&print_object, 0);
auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
if (it != sorted.end() && it->first == &print_object)
// ObjectByExtruder for this PrintObject was found.
out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data());
// ObjectLayerToPrint for this PrintObject was found.
out.emplace_back(it->second, print_object, instance - print_object.instances().data());
}
}
}
@ -2062,7 +2030,7 @@ namespace Skirt {
LayerResult GCode::process_layer(
const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers,
const ObjectsLayerToPrint &layers,
const LayerTools &layer_tools,
const bool last_layer,
// Pairs of PrintObject index and its instance index.
@ -2079,7 +2047,7 @@ LayerResult GCode::process_layer(
const Layer *object_layer = nullptr;
const SupportLayer *support_layer = nullptr;
const SupportLayer *raft_layer = nullptr;
for (const LayerToPrint &l : layers) {
for (const ObjectLayerToPrint &l : layers) {
if (l.object_layer && ! object_layer)
object_layer = l.object_layer;
if (l.support_layer) {
@ -2111,8 +2079,8 @@ LayerResult GCode::process_layer(
if (enable) {
for (const LayerRegion *layer_region : layer.regions())
if (size_t(layer_region->region().config().bottom_solid_layers.value) > layer.id() ||
layer_region->perimeters.items_count() > 1u ||
layer_region->fills.items_count() > 0) {
layer_region->perimeters().items_count() > 1u ||
layer_region->fills().items_count() > 0) {
enable = false;
break;
}
@ -2192,167 +2160,9 @@ LayerResult GCode::process_layer(
Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) :
Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done);
// Group extrusions by an extruder, then by an object, an island and a region.
std::map<unsigned int, std::vector<ObjectByExtruder>> by_extruder;
bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
for (const LayerToPrint &layer_to_print : layers) {
if (layer_to_print.support_layer != nullptr) {
const SupportLayer &support_layer = *layer_to_print.support_layer;
const PrintObject &object = *support_layer.object();
if (! support_layer.support_fills.entities.empty()) {
ExtrusionRole role = support_layer.support_fills.role();
bool has_support = role == erMixed || role == erSupportMaterial;
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
// Extruder ID of the support base. -1 if "don't care".
unsigned int support_extruder = object.config().support_material_extruder.value - 1;
// Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
bool support_dontcare = object.config().support_material_extruder.value == 0;
// Extruder ID of the support interface. -1 if "don't care".
unsigned int interface_extruder = object.config().support_material_interface_extruder.value - 1;
// Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
bool interface_dontcare = object.config().support_material_interface_extruder.value == 0;
if (support_dontcare || interface_dontcare) {
// Some support will be printed with "don't care" material, preferably non-soluble.
// Is the current extruder assigned a soluble filament?
unsigned int dontcare_extruder = first_extruder_id;
if (print.config().filament_soluble.get_at(dontcare_extruder)) {
// The last extruder printed on the previous layer extrudes soluble filament.
// Try to find a non-soluble extruder on the same layer.
for (unsigned int extruder_id : layer_tools.extruders)
if (! print.config().filament_soluble.get_at(extruder_id)) {
dontcare_extruder = extruder_id;
break;
}
}
if (support_dontcare)
support_extruder = dontcare_extruder;
if (interface_dontcare)
interface_extruder = dontcare_extruder;
}
// Both the support and the support interface are printed with the same extruder, therefore
// the interface may be interleaved with the support base.
bool single_extruder = ! has_support || support_extruder == interface_extruder;
// Assign an extruder to the base.
ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size());
obj.support = &support_layer.support_fills;
obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial;
if (! single_extruder && has_interface) {
ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size());
obj_interface.support = &support_layer.support_fills;
obj_interface.support_extrusion_role = erSupportMaterialInterface;
}
}
}
if (layer_to_print.object_layer != nullptr) {
const Layer &layer = *layer_to_print.object_layer;
// We now define a strategy for building perimeters and fills. The separation
// between regions doesn't matter in terms of printing order, as we follow
// another logic instead:
// - we group all extrusions by extruder so that we minimize toolchanges
// - we start from the last used extruder
// - for each extruder, we group extrusions by island
// - for each island, we extrude perimeters first, unless user set the infill_first
// option
// (Still, we have to keep track of regions because we need to apply their config)
size_t n_slices = layer.lslices.size();
const std::vector<BoundingBox> &layer_surface_bboxes = layer.lslices_bboxes;
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
std::vector<size_t> slices_test_order;
slices_test_order.reserve(n_slices);
for (size_t i = 0; i < n_slices; ++ i)
slices_test_order.emplace_back(i);
std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) {
const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>();
const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
return s1.x() * s1.y() < s2.x() * s2.y();
});
auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) {
const BoundingBox &bbox = layer_surface_bboxes[i];
return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
point(1) >= bbox.min(1) && point(1) < bbox.max(1) &&
layer.lslices[i].contour.contains(point);
};
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
const LayerRegion *layerm = layer.regions()[region_id];
if (layerm == nullptr)
continue;
// PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not
// identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion.
const PrintRegion &region = print.get_print_region(layerm->region().print_region_id());
// Now we must process perimeters and infills and create islands of extrusions in by_region std::map.
// It is also necessary to save which extrusions are part of MM wiping and which are not.
// The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice:
std::vector<unsigned int> printing_extruders;
for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) {
for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills.entities : layerm->perimeters.entities) {
// extrusions represents infill or perimeter extrusions of a single island.
assert(dynamic_cast<const ExtrusionEntityCollection*>(ee) != nullptr);
const auto *extrusions = static_cast<const ExtrusionEntityCollection*>(ee);
if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail.
continue;
// This extrusion is part of certain Region, which tells us which extruder should be used for it:
int correct_extruder_id = layer_tools.extruder(*extrusions, region);
// Let's recover vector of extruder overrides:
const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr;
if (! layer_tools.has_extruder(correct_extruder_id)) {
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
correct_extruder_id = layer_tools.extruders.back();
}
printing_extruders.clear();
if (is_anything_overridden) {
entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size());
if (entity_overrides == nullptr) {
printing_extruders.emplace_back(correct_extruder_id);
} else {
printing_extruders.reserve(entity_overrides->size());
for (int extruder : *entity_overrides)
printing_extruders.emplace_back(extruder >= 0 ?
// at least one copy is overridden to use this extruder
extruder :
// at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
static_cast<unsigned int>(- extruder - 1));
Slic3r::sort_remove_duplicates(printing_extruders);
}
} else
printing_extruders.emplace_back(correct_extruder_id);
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
for (unsigned int extruder : printing_extruders)
{
std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder(
by_extruder,
extruder,
&layer_to_print - layers.data(),
layers.size(), n_slices+1);
for (size_t i = 0; i <= n_slices; ++ i) {
bool last = i == n_slices;
size_t island_idx = last ? n_slices : slices_test_order[i];
if (// extrusions->first_point does not fit inside any slice
last ||
// extrusions->first_point fits inside ith slice
point_inside_surface(island_idx, extrusions->first_point())) {
if (islands[island_idx].by_region.empty())
islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region());
islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides);
break;
}
}
}
}
}
} // for regions
}
} // for objects
if (this->config().avoid_curled_filament_during_travels) {
m_avoid_curled_filaments.clear();
for (const LayerToPrint &layer_to_print : layers) {
for (const ObjectLayerToPrint &layer_to_print : layers) {
m_avoid_curled_filaments.add_obstacles(layer_to_print.object_layer, Point(scaled(this->origin())));
m_avoid_curled_filaments.add_obstacles(layer_to_print.support_layer, Point(scaled(this->origin())));
}
@ -2404,91 +2214,28 @@ LayerResult GCode::process_layer(
m_avoid_crossing_perimeters.disable_once();
}
auto objects_by_extruder_it = by_extruder.find(extruder_id);
if (objects_by_extruder_it == by_extruder.end())
continue;
std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx);
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
if (is_anything_overridden && print_wipe_extrusions == 0)
bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden();
if (is_anything_overridden) {
// Extrude wipes.
size_t gcode_size_old = gcode.size();
for (const InstanceToPrint &instance : instances_to_print)
this->process_layer_single_object(
gcode, extruder_id, instance,
layers[instance.object_layer_to_print_id], layer_tools,
is_anything_overridden, true /* print_wipe_extrusions */);
if (gcode_size_old < gcode.size())
gcode+="; PURGING FINISHED\n";
for (InstanceToPrint &instance_to_print : instances_to_print) {
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
// To control print speed of the 1st object layer printed over raft interface.
bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 &&
instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id();
m_config.apply(instance_to_print.print_object.config(), true);
m_layer = layer_to_print.layer();
m_object_layer_over_raft = object_layer_over_raft;
if (m_config.avoid_crossing_perimeters)
m_avoid_crossing_perimeters.init_layer(*m_layer);
if (this->config().gcode_label_objects)
gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
// When starting a new object, use the external motion planner for the first travel move.
const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
if (m_last_obj_copy != this_object_copy)
m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset));
if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) {
m_layer = layer_to_print.support_layer;
m_object_layer_over_raft = false;
gcode += this->extrude_support(
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role));
m_layer = layer_to_print.layer();
m_object_layer_over_raft = object_layer_over_raft;
}
//FIXME order islands?
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
//FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
if (print.config().infill_first) {
gcode += this->extrude_infill(print, by_region_specific, false);
gcode += this->extrude_perimeters(print, by_region_specific);
} else {
gcode += this->extrude_perimeters(print, by_region_specific);
gcode += this->extrude_infill(print,by_region_specific, false);
// Extrude normal extrusions.
for (const InstanceToPrint &instance : instances_to_print)
this->process_layer_single_object(
gcode, extruder_id, instance,
layers[instance.object_layer_to_print_id], layer_tools,
is_anything_overridden, false /* print_wipe_extrusions */);
}
// ironing
gcode += this->extrude_infill(print,by_region_specific, true);
}
if (this->config().gcode_label_objects)
gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
}
}
}
#if 0
// Apply spiral vase post-processing if this layer contains suitable geometry
// (we must feed all the G-code into the post-processor, including the first
// bottom non-spiral layers otherwise it will mess with positions)
// we apply spiral vase at this stage because it requires a full layer.
// Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only.
if (m_spiral_vase)
gcode = m_spiral_vase->process_layer(std::move(gcode));
// Apply cooling logic; this may alter speeds.
if (m_cooling_buffer)
gcode = m_cooling_buffer->process_layer(std::move(gcode), layer.id(),
// Flush the cooling buffer at each object layer or possibly at the last layer, even if it contains just supports (This should not happen).
object_layer || last_layer);
// Apply pressure equalization if enabled;
// printf("G-code before filter:\n%s\n", gcode.c_str());
if (m_pressure_equalizer)
gcode = m_pressure_equalizer->process(gcode.c_str(), false);
// printf("G-code after filter:\n%s\n", out.c_str());
file.write(gcode);
#endif
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
log_memory_info();
@ -2498,6 +2245,227 @@ LayerResult GCode::process_layer(
return result;
}
static const auto comment_perimeter = "perimeter"sv;
// Comparing string_view pointer & length for speed.
static inline bool comment_is_perimeter(const std::string_view comment) {
return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size();
}
void GCode::process_layer_single_object(
// output
std::string &gcode,
// Index of the extruder currently active.
const unsigned int extruder_id,
// What object and instance is going to be printed.
const InstanceToPrint &print_instance,
// and the object & support layer of the above.
const ObjectLayerToPrint &layer_to_print,
// Container for extruder overrides (when wiping into object or infill).
const LayerTools &layer_tools,
// Is any extrusion possibly marked as wiping extrusion?
const bool is_anything_overridden,
// Round 1 (wiping into object or infill) or round 2 (normal extrusions).
const bool print_wipe_extrusions)
{
//FIXME what the heck ID is this? Layer ID or Object ID? More likely an Object ID.
uint32_t layer_id = 0;
bool first = true;
// Delay layer initialization as many layers may not print with all extruders.
auto init_layer_delayed = [this, &print_instance, &layer_to_print, layer_id, &first, &gcode]() {
if (first) {
first = false;
const PrintObject &print_object = print_instance.print_object;
const Print &print = *print_object.print();
m_config.apply(print_object.config(), true);
m_layer = layer_to_print.layer();
if (print.config().avoid_crossing_perimeters)
m_avoid_crossing_perimeters.init_layer(*m_layer);
// When starting a new object, use the external motion planner for the first travel move.
const Point &offset = print_object.instances()[print_instance.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&print_object, offset);
if (m_last_obj_copy != this_object_copy)
m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset));
if (this->config().gcode_label_objects)
gcode += std::string("; printing object ") + print_object.model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(print_instance.instance_id) + "\n";
}
};
const PrintObject &print_object = print_instance.print_object;
const Print &print = *print_object.print();
if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr)
if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) {
ExtrusionRole role = support_layer.support_fills.role();
bool has_support = role == erMixed || role == erSupportMaterial;
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
// Extruder ID of the support base. -1 if "don't care".
unsigned int support_extruder = print_object.config().support_material_extruder.value - 1;
// Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
bool support_dontcare = print_object.config().support_material_extruder.value == 0;
// Extruder ID of the support interface. -1 if "don't care".
unsigned int interface_extruder = print_object.config().support_material_interface_extruder.value - 1;
// Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
bool interface_dontcare = print_object.config().support_material_interface_extruder.value == 0;
if (support_dontcare || interface_dontcare) {
// Some support will be printed with "don't care" material, preferably non-soluble.
// Is the current extruder assigned a soluble filament?
unsigned int dontcare_extruder = layer_tools.extruders.front();
if (print.config().filament_soluble.get_at(dontcare_extruder)) {
// The last extruder printed on the previous layer extrudes soluble filament.
// Try to find a non-soluble extruder on the same layer.
for (unsigned int extruder_id : layer_tools.extruders)
if (! print.config().filament_soluble.get_at(extruder_id)) {
dontcare_extruder = extruder_id;
break;
}
}
if (support_dontcare)
support_extruder = dontcare_extruder;
if (interface_dontcare)
interface_extruder = dontcare_extruder;
}
bool extrude_support = has_support && support_extruder == extruder_id;
bool extrude_interface = interface_extruder && interface_extruder == extruder_id;
if (extrude_support || extrude_interface) {
init_layer_delayed();
m_layer = layer_to_print.support_layer;
m_object_layer_over_raft = false;
gcode += this->extrude_support(
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
support_layer.support_fills.chained_path_from(m_last_pos, has_support ? (has_interface ? erMixed : erSupportMaterial) : erSupportMaterialInterface));
}
}
m_layer = layer_to_print.layer();
// To control print speed of the 1st object layer printed over raft interface.
m_object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 &&
print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id();
// Check whether this ExtrusionEntityCollection should be printed now with extruder_id, given print_wipe_extrusions
// (wipe extrusions are printed before regular extrusions).
auto shall_print_this_extrusion_collection = [extruder_id, instance_id = print_instance.instance_id, &layer_tools, is_anything_overridden, print_wipe_extrusions](const ExtrusionEntityCollection *eec, const PrintRegion &region) -> bool {
assert(eec != nullptr);
if (eec->entities.empty())
// This shouldn't happen. FIXME why? but first_point() would fail.
return false;
// This extrusion is part of certain Region, which tells us which extruder should be used for it:
int correct_extruder_id = layer_tools.extruder(*eec, region);
if (! layer_tools.has_extruder(correct_extruder_id)) {
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
correct_extruder_id = layer_tools.extruders.back();
}
int extruder_override_id = is_anything_overridden ? layer_tools.wiping_extrusions().get_extruder_override(eec, instance_id) : -1;
return print_wipe_extrusions ?
extruder_override_id == int(extruder_id) :
extruder_override_id < 0 && extruder_id == correct_extruder_id;
};
ExtrusionEntitiesPtr temp_fill_extrusions;
if (const Layer *layer = layer_to_print.object_layer; layer)
for (const LayerSlice &lslice : layer->lslices_ex) {
auto extrude_infill_range = [&](
const LayerRegion &layerm, const ExtrusionEntityCollection &fills,
LayerExtrusionRanges::const_iterator it_fill_ranges_begin, LayerExtrusionRanges::const_iterator it_fill_ranges_end, bool ironing) {
// PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not
// identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion.
const PrintRegion &region = print.get_print_region(layerm.region().print_region_id());
temp_fill_extrusions.clear();
for (auto it_fill_range = it_fill_ranges_begin; it_fill_range != it_fill_ranges_end; ++ it_fill_range) {
assert(it_fill_range->region() == it_fill_ranges_begin->region());
for (uint32_t fill_id : *it_fill_range) {
assert(dynamic_cast<ExtrusionEntityCollection*>(fills.entities[fill_id]));
if (auto *eec = static_cast<ExtrusionEntityCollection*>(fills.entities[fill_id]);
(eec->role() == erIroning) == ironing && shall_print_this_extrusion_collection(eec, region)) {
if (eec->can_reverse())
// Flatten the infill collection for better path planning.
for (auto *ee : eec->entities)
temp_fill_extrusions.emplace_back(ee);
else
temp_fill_extrusions.emplace_back(eec);
}
}
}
if (! temp_fill_extrusions.empty()) {
init_layer_delayed();
m_config.apply(region.config());
//FIXME The source extrusions may be reversed, thus modifying the extrusions! Is it a problem? How about the initial G-code preview?
// Will parallel access of initial G-code preview to these extrusions while reordering them at backend cause issues?
chain_and_reorder_extrusion_entities(temp_fill_extrusions, &m_last_pos);
const auto extrusion_name = ironing ? "ironing"sv : "infill"sv;
for (const ExtrusionEntity *fill : temp_fill_extrusions)
if (auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill); eec) {
for (const ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
gcode += this->extrude_entity(*ee, extrusion_name);
} else
gcode += this->extrude_entity(*fill, extrusion_name);
}
};
//FIXME order islands?
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
for (const LayerIsland &island : lslice.islands) {
auto process_perimeters = [&]() {
const LayerRegion &layerm = *layer->get_region(island.perimeters.region());
// PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not
// identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion.
const PrintRegion &region = print.get_print_region(layerm.region().print_region_id());
bool first = true;
for (uint32_t perimeter_id : island.perimeters) {
assert(dynamic_cast<const ExtrusionEntityCollection*>(layerm.perimeters().entities[perimeter_id]));
if (const auto *eec = static_cast<const ExtrusionEntityCollection*>(layerm.perimeters().entities[perimeter_id]);
shall_print_this_extrusion_collection(eec, region)) {
// This may not apply to Arachne, but maybe the Arachne gap fill should disable reverse as well?
// assert(! eec->can_reverse());
if (first) {
first = false;
init_layer_delayed();
m_config.apply(region.config());
}
for (const ExtrusionEntity *ee : *eec)
gcode += this->extrude_entity(*ee, comment_perimeter, -1.);
}
}
};
auto process_infill = [&]() {
for (auto it = island.fills.begin(); it != island.fills.end();) {
// Gather range of fill ranges with the same region.
auto it_end = it;
for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ;
const LayerRegion &layerm = *layer->get_region(it->region());
extrude_infill_range(layerm, layerm.fills(), it, it_end, false /* normal extrusions, not ironing */);
it = it_end;
}
};
if (print.config().infill_first) {
process_infill();
process_perimeters();
} else {
process_perimeters();
process_infill();
}
}
// ironing
//FIXME move ironing into the loop above over LayerIslands?
// First Ironing changes extrusion rate quickly, second single ironing may be done over multiple perimeter regions.
// Ironing in a second phase is safer, but it may be less efficient.
for (const LayerIsland &island : lslice.islands) {
for (auto it = island.fills.begin(); it != island.fills.end();) {
// Gather range of fill ranges with the same region.
auto it_end = it;
for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ;
const LayerRegion &layerm = *layer->get_region(it->region());
extrude_infill_range(layerm, layerm.fills(), it, it_end, true /* ironing, not normal extrusions */);
it = it_end;
}
}
}
if (! first && this->config().gcode_label_objects)
gcode += std::string("; stop printing object ") + print_object.model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(print_instance.instance_id) + "\n";
}
void GCode::apply_print_config(const PrintConfig &print_config)
{
m_writer.apply_print_config(print_config);
@ -2587,12 +2555,6 @@ std::string GCode::change_layer(coordf_t print_z)
return gcode;
}
static const auto comment_perimeter = "perimeter"sv;
// Comparing string_view pointer & length for speed.
static inline bool comment_is_perimeter(const std::string_view comment) {
return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size();
}
std::string GCode::extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed)
{
// get a copy; don't modify the orientation of the original loop object otherwise
@ -2759,49 +2721,6 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string_view description
return gcode;
}
// Extrude perimeters: Decide where to put seams (hide or align seams).
std::string GCode::extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region)
{
std::string gcode;
for (const ObjectByExtruder::Island::Region &region : by_region)
if (! region.perimeters.empty()) {
m_config.apply(print.get_print_region(&region - &by_region.front()).config());
for (const ExtrusionEntity* ee : region.perimeters)
gcode += this->extrude_entity(*ee, comment_perimeter, -1.);
}
return gcode;
}
// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, bool ironing)
{
std::string gcode;
ExtrusionEntitiesPtr extrusions;
const auto extrusion_name = ironing ? "ironing"sv : "infill"sv;
for (const ObjectByExtruder::Island::Region &region : by_region)
if (! region.infills.empty()) {
extrusions.clear();
extrusions.reserve(region.infills.size());
for (ExtrusionEntity *ee : region.infills)
if ((ee->role() == erIroning) == ironing)
extrusions.emplace_back(ee);
if (! extrusions.empty()) {
m_config.apply(print.get_print_region(&region - &by_region.front()).config());
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
for (const ExtrusionEntity *fill : extrusions) {
auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
if (eec) {
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
gcode += this->extrude_entity(*ee, extrusion_name);
} else
gcode += this->extrude_entity(*fill, extrusion_name);
}
}
}
return gcode;
}
std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills)
{
static constexpr const auto support_label = "support material"sv;
@ -3326,104 +3245,4 @@ Point GCode::gcode_to_point(const Vec2d &point) const
scale_(point(1) - m_origin(1) + extruder_offset(1)));
}
// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed
// during infill/perimeter wiping, or normally (depends on wiping_entities parameter)
// Fills in by_region_per_copy_cache and returns its reference.
const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const
{
bool has_overrides = false;
for (const auto& reg : by_region)
if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) {
has_overrides = true;
break;
}
// Data is cleared, but the memory is not.
by_region_per_copy_cache.clear();
if (! has_overrides)
// Simple case. No need to copy the regions.
return wiping_entities ? by_region_per_copy_cache : this->by_region;
// Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions.
// Some of the extrusions of some object instances are printed later - those are the clean print extrusions.
// Filter out the extrusions based on the infill_overrides / perimeter_overrides:
for (const auto& reg : by_region) {
by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island
// Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed
// References are used so that we don't have to repeat the same code
for (int iter = 0; iter < 2; ++iter) {
const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters);
ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters);
const std::vector<const WipingExtrusions::ExtruderPerCopy*>& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides);
// Now the most important thing - which extrusion should we print.
// See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack.
if (wiping_entities) {
// Apply overrides for this region.
for (unsigned int i = 0; i < overrides.size(); ++ i) {
const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
// This copy (aka object instance) should be printed with this extruder, which overrides the default one.
if (this_override != nullptr && (*this_override)[copy] == int(extruder))
target_eec.emplace_back(entities[i]);
}
} else {
// Apply normal extrusions (non-overrides) for this region.
unsigned int i = 0;
for (; i < overrides.size(); ++ i) {
const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
// This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one.
if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1)
target_eec.emplace_back(entities[i]);
}
for (; i < entities.size(); ++ i)
target_eec.emplace_back(entities[i]);
}
}
}
return by_region_per_copy_cache;
}
// This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter)
// It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy.
void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder)
{
// We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves:
ExtrusionEntitiesPtr* perimeters_or_infills;
std::vector<const WipingExtrusions::ExtruderPerCopy*>* perimeters_or_infills_overrides;
switch (type) {
case PERIMETERS:
perimeters_or_infills = &perimeters;
perimeters_or_infills_overrides = &perimeters_overrides;
break;
case INFILL:
perimeters_or_infills = &infills;
perimeters_or_infills_overrides = &infills_overrides;
break;
default:
throw Slic3r::InvalidArgument("Unknown parameter!");
}
// First we append the entities, there are eec->entities.size() of them:
size_t old_size = perimeters_or_infills->size();
size_t new_size = old_size + (eec->can_reverse() ? eec->entities.size() : 1);
perimeters_or_infills->reserve(new_size);
if (eec->can_reverse()) {
for (auto* ee : eec->entities)
perimeters_or_infills->emplace_back(ee);
} else
perimeters_or_infills->emplace_back(const_cast<ExtrusionEntityCollection*>(eec));
if (copies_extruder != nullptr) {
// Don't reallocate overrides if not needed.
// Missing overrides are implicitely considered non-overridden.
perimeters_or_infills_overrides->reserve(new_size);
perimeters_or_infills_overrides->resize(old_size, nullptr);
perimeters_or_infills_overrides->resize(new_size, copies_extruder);
}
}
} // namespace Slic3r

View File

@ -189,15 +189,16 @@ public:
// Object and support extrusions of the same PrintObject at the same print_z.
// public, so that it could be accessed by free helper functions from GCode.cpp
struct LayerToPrint
struct ObjectLayerToPrint
{
LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
const Layer* object_layer;
const SupportLayer* support_layer;
const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; }
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
};
using ObjectsLayerToPrint = std::vector<ObjectLayerToPrint>;
private:
class GCodeOutputStream {
@ -240,13 +241,13 @@ private:
};
void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb);
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> collect_layers_to_print(const Print &print);
LayerResult process_layer(
const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers,
const ObjectsLayerToPrint &layers,
const LayerTools &layer_tools,
const bool last_layer,
// Pairs of PrintObject index and its instance index.
@ -261,7 +262,7 @@ private:
const Print &print,
const ToolOrdering &tool_ordering,
const std::vector<const PrintInstance*> &print_object_instances_ordering,
const std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> &layers_to_print,
const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
GCodeOutputStream &output_stream);
// Process all layers of a single object instance (sequential mode) with a parallel pipeline:
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
@ -269,7 +270,7 @@ private:
void process_layers(
const Print &print,
const ToolOrdering &tool_ordering,
std::vector<LayerToPrint> layers_to_print,
ObjectsLayerToPrint layers_to_print,
const size_t single_object_idx,
GCodeOutputStream &output_stream);
@ -283,71 +284,43 @@ private:
std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.);
std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.);
// Extruding multiple objects with soluble / non-soluble / combined supports
// on a multi-material printer, trying to minimize tool switches.
// Following structures sort extrusions by the extruder ID, by an order of objects and object islands.
struct ObjectByExtruder
{
ObjectByExtruder() : support(nullptr), support_extrusion_role(erNone) {}
const ExtrusionEntityCollection *support;
// erSupportMaterial / erSupportMaterialInterface or erMixed.
ExtrusionRole support_extrusion_role;
struct Island
{
struct Region {
// Non-owned references to LayerRegion::perimeters::entities
// std::vector<const ExtrusionEntity*> would be better here, but there is no way in C++ to convert from std::vector<T*> std::vector<const T*> without copying.
ExtrusionEntitiesPtr perimeters;
// Non-owned references to LayerRegion::fills::entities
ExtrusionEntitiesPtr infills;
std::vector<const WipingExtrusions::ExtruderPerCopy*> infills_overrides;
std::vector<const WipingExtrusions::ExtruderPerCopy*> perimeters_overrides;
enum Type {
PERIMETERS,
INFILL,
};
// Appends perimeter/infill entities and writes don't indices of those that are not to be extruder as part of perimeter/infill wiping
void append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copy_extruders);
};
std::vector<Region> by_region; // all extrusions for this island, grouped by regions
// Fills in by_region_per_copy_cache and returns its reference.
const std::vector<Region>& by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities = false) const;
};
std::vector<Island> islands;
};
struct InstanceToPrint
{
InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) :
object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {}
InstanceToPrint(size_t object_layer_to_print_id, const PrintObject &print_object, size_t instance_id) :
object_layer_to_print_id(object_layer_to_print_id), print_object(print_object), instance_id(instance_id) {}
// Repository
ObjectByExtruder &object_by_extruder;
// Index into std::vector<LayerToPrint>, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
const size_t layer_id;
// Index into std::vector<ObjectLayerToPrint>, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
const size_t object_layer_to_print_id;
const PrintObject &print_object;
// Instance idx of the copy of a print object.
const size_t instance_id;
};
std::vector<InstanceToPrint> sort_print_object_instances(
std::vector<ObjectByExtruder> &objects_by_extruder,
// Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
const std::vector<LayerToPrint> &layers,
const std::vector<ObjectLayerToPrint> &layers,
// Ordering must be defined for normal (non-sequential print).
const std::vector<const PrintInstance*> *ordering,
// For sequential print, the instance of the object to be printing has to be defined.
const size_t single_object_instance_idx);
std::string extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region);
std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, bool ironing);
// This function will be called for each printing extruder, possibly twice: First for wiping extrusions, second for normal extrusions.
void process_layer_single_object(
// output
std::string &gcode,
// Index of the extruder currently active.
const unsigned int extruder_id,
// What object and instance is going to be printed.
const InstanceToPrint &print_instance,
// and the object & support layer of the above.
const ObjectLayerToPrint &layer_to_print,
// Container for extruder overrides (when wiping into object or infill).
const LayerTools &layer_tools,
// Is any extrusion possibly marked as wiping extrusion?
const bool is_anything_overridden,
// Round 1 (wiping into object or infill) or round 2 (normal extrusions).
const bool print_wipe_extrusions);
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
@ -440,18 +413,6 @@ private:
// To control print speed of 1st object layer over raft interface.
bool object_layer_over_raft() const { return m_object_layer_over_raft; }
friend ObjectByExtruder& object_by_extruder(
std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder,
unsigned int extruder_id,
size_t object_idx,
size_t num_objects);
friend std::vector<ObjectByExtruder::Island>& object_islands_by_extruder(
std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder,
unsigned int extruder_id,
size_t object_idx,
size_t num_objects,
size_t num_islands);
friend class Wipe;
friend class WipeTowerIntegration;
friend class PressureEqualizer;

View File

@ -487,7 +487,7 @@ static float get_perimeter_spacing(const Layer &layer)
size_t regions_count = 0;
float perimeter_spacing = 0.f;
for (const LayerRegion *layer_region : layer.regions())
if (layer_region != nullptr && !layer_region->slices.empty()) {
if (layer_region != nullptr && ! layer_region->slices().empty()) {
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
++regions_count;
}
@ -508,7 +508,7 @@ static float get_perimeter_spacing_external(const Layer &layer)
for (const PrintObject *object : layer.object()->print()->objects())
if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l)
for (const LayerRegion *layer_region : l->regions())
if (layer_region != nullptr && !layer_region->slices.empty()) {
if (layer_region != nullptr && ! layer_region->slices().empty()) {
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
++ regions_count;
}
@ -527,7 +527,7 @@ static float get_external_perimeter_width(const Layer &layer)
size_t regions_count = 0;
float perimeter_width = 0.f;
for (const LayerRegion *layer_region : layer.regions())
if (layer_region != nullptr && !layer_region->slices.empty()) {
if (layer_region != nullptr && ! layer_region->slices().empty()) {
perimeter_width += float(layer_region->flow(frExternalPerimeter).scaled_width());
++regions_count;
}
@ -1070,14 +1070,14 @@ static ExPolygons get_boundary(const Layer &layer)
// Collect all top layers that will not be crossed.
size_t polygons_count = 0;
for (const LayerRegion *layer_region : layer.regions())
for (const Surface &surface : layer_region->fill_surfaces.surfaces)
for (const Surface &surface : layer_region->fill_surfaces())
if (surface.is_top()) ++polygons_count;
if (polygons_count > 0) {
ExPolygons top_layer_polygons;
top_layer_polygons.reserve(polygons_count);
for (const LayerRegion *layer_region : layer.regions())
for (const Surface &surface : layer_region->fill_surfaces.surfaces)
for (const Surface &surface : layer_region->fill_surfaces())
if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon);
top_layer_polygons = union_ex(top_layer_polygons);

View File

@ -14,6 +14,8 @@
#include <assert.h>
#include <fast_float/fast_float.h>
namespace Slic3r {
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
@ -339,12 +341,13 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
// for a sequence of extrusion moves.
size_t active_speed_modifier = size_t(-1);
std::vector<float> new_pos;
for (; *line_start != 0; line_start = line_end)
{
while (*line_end != '\n' && *line_end != 0)
++ line_end;
// sline will not contain the trailing '\n'.
std::string sline(line_start, line_end);
std::string_view sline(line_start, line_end - line_start);
// CoolingLine will contain the trailing '\n'.
if (*line_end == '\n')
++ line_end;
@ -358,20 +361,18 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
if (line.type) {
// G0, G1 or G92
// Parse the G-code line.
std::vector<float> new_pos(current_pos);
const char *c = sline.data() + 3;
for (;;) {
new_pos = current_pos;
for (auto c = sline.begin() + 3;;) {
// Skip whitespaces.
for (; *c == ' ' || *c == '\t'; ++ c);
if (*c == 0 || *c == ';')
for (; c != sline.end() && (*c == ' ' || *c == '\t'); ++ c);
if (c == sline.end() || *c == ';')
break;
assert(is_decimal_separator_point()); // for atof
// Parse the axis.
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
if (axis != size_t(-1)) {
new_pos[axis] = float(atof(++c));
auto [pend, ec] = fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
if (axis == 4) {
// Convert mm/min to mm/sec.
new_pos[4] /= 60.f;
@ -381,7 +382,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
}
}
// Skip this word.
for (; *c != ' ' && *c != '\t' && *c != 0; ++ c);
for (; c != sline.end() && *c != ' ' && *c != '\t'; ++ c);
}
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
bool wipe = boost::contains(sline, ";_WIPE");
@ -460,7 +461,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
}
active_speed_modifier = size_t(-1);
} else if (boost::starts_with(sline, m_toolchange_prefix)) {
unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size());
unsigned int new_extruder;
auto res = std::from_chars(sline.data() + m_toolchange_prefix.size(), sline.data() + sline.size(), new_extruder);
// Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored.
if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) {
if (new_extruder != current_extruder) {
@ -485,10 +487,15 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
line.type = CoolingLine::TYPE_G4;
size_t pos_S = sline.find('S', 3);
size_t pos_P = sline.find('P', 3);
assert(is_decimal_separator_point()); // for atof
line.time = line.time_max = float(
(pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
(pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
bool has_S = pos_S > 0;
bool has_P = pos_P > 0;
if (has_S || has_P) {
auto [pend, ec] = fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time);
if (has_P)
line.time *= 0.001f;
} else
line.time = 0;
line.time_max = line.time;
}
if (line.type != 0)
adjustment->lines.emplace_back(std::move(line));
@ -778,7 +785,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (line_start > pos)
new_gcode.append(pos, line_start - pos);
if (line->type & CoolingLine::TYPE_SET_TOOL) {
unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size());
unsigned int new_extruder;
auto res = std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder);
if (new_extruder != m_current_extruder) {
m_current_extruder = new_extruder;
change_extruder_set_fan();
@ -804,7 +812,10 @@ std::string CoolingBuffer::apply_layer_cooldown(
// Remove the F word from the current G-code line.
bool remove = false;
assert(fpos != nullptr);
new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos);
if (line->slowdown)
new_feedrate = int(floor(60. * line->feedrate + 0.5));
else
auto res = std::from_chars(fpos, line_end, new_feedrate);
if (new_feedrate == current_feedrate) {
// No need to change the F value.
if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_ADJUSTABLE_EMPTY | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)

View File

@ -2735,8 +2735,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
if (volume_extruded_filament != 0.)
m_used_filaments.increase_caches(volume_extruded_filament,
this->m_extruder_id, area_filament_cross_section * this->m_parking_position,
area_filament_cross_section * this->m_extra_loading_move);
m_extruder_id, area_filament_cross_section * m_parking_position,
area_filament_cross_section * m_extra_loading_move);
const EMoveType type = move_type(delta_pos);
if (type == EMoveType::Extrude) {
@ -4303,7 +4303,7 @@ void GCodeProcessor::process_filaments(CustomGCode::Type code)
m_used_filaments.process_color_change_cache();
if (code == CustomGCode::ToolChange)
m_used_filaments.process_extruder_cache(this->m_extruder_id);
m_used_filaments.process_extruder_cache(m_extruder_id);
}
void GCodeProcessor::simulate_st_synchronize(float additional_time)

View File

@ -690,7 +690,7 @@ inline bool is_just_line_with_extrude_set_speed_tag(const std::string &line)
void PressureEqualizer::push_line_to_output(const size_t line_idx, const float new_feedrate, const char *comment)
{
const GCodeLine &line = this->m_gcode_lines[line_idx];
const GCodeLine &line = m_gcode_lines[line_idx];
if (line_idx > 0 && output_buffer_length > 0) {
const std::string prev_line_str = std::string(output_buffer.begin() + int(this->output_buffer_prev_length),
output_buffer.begin() + int(this->output_buffer_length) + 1);

View File

@ -113,8 +113,8 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object
break;
BoundingBoxf bbox_this;
for (const LayerRegion *layerm : layer->regions()) {
bbox_this.merge(extrusionentity_extents(layerm->perimeters));
for (const ExtrusionEntity *ee : layerm->fills.entities)
bbox_this.merge(extrusionentity_extents(layerm->perimeters()));
for (const ExtrusionEntity *ee : layerm->fills())
// fill represents infill extrusions of a single island.
bbox_this.merge(extrusionentity_extents(*dynamic_cast<const ExtrusionEntityCollection*>(ee)));
}

View File

@ -401,7 +401,7 @@ struct GlobalModelInfo {
Polygons extract_perimeter_polygons(const Layer *layer, std::vector<const LayerRegion*> &corresponding_regions_out) {
Polygons polygons;
for (const LayerRegion *layer_region : layer->regions()) {
for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) {
for (const ExtrusionEntity *ex_entity : layer_region->perimeters()) {
if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters
for (const ExtrusionEntity *perimeter : static_cast<const ExtrusionEntityCollection*>(ex_entity)->entities) {
ExtrusionRole role = perimeter->role();
@ -1060,7 +1060,7 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po)
for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) {
size_t regions_with_perimeter = 0;
for (const LayerRegion *region : po->layers()[layer_idx]->regions()) {
if (region->perimeters.entities.size() > 0) {
if (region->perimeters().size() > 0) {
regions_with_perimeter++;
}
};

View File

@ -14,8 +14,9 @@
#include <cassert>
#include <limits>
#include <libslic3r.h>
#include <boost/log/trivial.hpp>
#include <libslic3r.h>
namespace Slic3r {
@ -103,7 +104,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
}
double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height);
// Collect extruders reuqired to print the layers.
// Collect extruders required to print the layers.
this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>());
// Reorder the extruders to minimize tool switches.
@ -187,6 +188,21 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
}
}
// Decides whether this entity could be overridden
[[nodiscard]] static bool is_overriddable(const ExtrusionEntityCollection& eec, const LayerTools& lt, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region)
{
if (print_config.filament_soluble.get_at(lt.extruder(eec, region)))
return false;
if (object.config().wipe_into_objects)
return true;
if (!region.config().wipe_into_infill || eec.role() != erInternalInfill)
return false;
return true;
}
// Collect extruders reuqired to print layers.
void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches)
{
@ -226,18 +242,20 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
for (const LayerRegion *layerm : layer->regions()) {
const PrintRegion &region = layerm->region();
if (! layerm->perimeters.entities.empty()) {
if (! layerm->perimeters().empty()) {
bool something_nonoverriddable = true;
if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors)
something_nonoverriddable = false;
for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities
if (!layer_tools.wiping_extrusions().is_overriddable_and_mark(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region))
for (const ExtrusionEntity *eec : layerm->perimeters()) // let's check if there are nonoverriddable entities
if (is_overriddable(dynamic_cast<const ExtrusionEntityCollection&>(*eec), layer_tools, *m_print_config_ptr, object, region))
layer_tools.wiping_extrusions_nonconst().set_something_overridable();
else
something_nonoverriddable = true;
}
if (something_nonoverriddable)
layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().perimeter_extruder.value : extruder_override);
layer_tools.extruders.emplace_back(extruder_override == 0 ? region.config().perimeter_extruder.value : extruder_override);
layer_tools.has_object = true;
}
@ -245,7 +263,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
bool has_infill = false;
bool has_solid_infill = false;
bool something_nonoverriddable = false;
for (const ExtrusionEntity *ee : layerm->fills.entities) {
for (const ExtrusionEntity *ee : layerm->fills()) {
// fill represents infill extrusions of a single island.
const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role();
@ -255,7 +273,9 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
has_infill = true;
if (m_print_config_ptr) {
if (! layer_tools.wiping_extrusions().is_overriddable_and_mark(*fill, *m_print_config_ptr, object, region))
if (is_overriddable(*fill, layer_tools, *m_print_config_ptr, object, region))
layer_tools.wiping_extrusions_nonconst().set_something_overridable();
else
something_nonoverriddable = true;
}
}
@ -598,23 +618,23 @@ const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const
// This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual)
void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies)
{
something_overridden = true;
m_something_overridden = true;
auto entity_map_it = (entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator
auto entity_map_it = (m_entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator
ExtruderPerCopy& copies_vector = entity_map_it->second;
copies_vector.resize(num_of_copies, -1);
assert(copies_vector[copy_id] == -1);
if (copies_vector[copy_id] != -1)
std::cout << "ERROR: Entity extruder overriden multiple times!!!\n"; // A debugging message - this must never happen.
BOOST_LOG_TRIVIAL(error) << "ERROR: Entity extruder overriden multiple times!!!";
copies_vector[copy_id] = extruder;
}
// Finds first non-soluble extruder on the layer
int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const
[[nodiscard]] static int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config, const LayerTools& layer_tools)
{
const LayerTools& lt = *m_layer_tools;
for (auto extruders_it = lt.extruders.begin(); extruders_it != lt.extruders.end(); ++extruders_it)
for (auto extruders_it = layer_tools.extruders.begin(); extruders_it != layer_tools.extruders.end(); ++extruders_it)
if (!print_config.filament_soluble.get_at(*extruders_it))
return (*extruders_it);
@ -622,44 +642,33 @@ int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& prin
}
// Finds last non-soluble extruder on the layer
int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const
[[nodiscard]] static int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config, const LayerTools& layer_tools)
{
const LayerTools& lt = *m_layer_tools;
for (auto extruders_it = lt.extruders.rbegin(); extruders_it != lt.extruders.rend(); ++extruders_it)
for (auto extruders_it = layer_tools.extruders.rbegin(); extruders_it != layer_tools.extruders.rend(); ++extruders_it)
if (!print_config.filament_soluble.get_at(*extruders_it))
return (*extruders_it);
return (-1);
}
// Decides whether this entity could be overridden
bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const
{
if (print_config.filament_soluble.get_at(m_layer_tools->extruder(eec, region)))
return false;
if (object.config().wipe_into_objects)
return true;
if (!region.config().wipe_into_infill || eec.role() != erInternalInfill)
return false;
return true;
}
// Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange
// and returns volume that is left to be wiped on the wipe tower.
float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
// Switching from old_extruder to new_extruder, trying to wipe volume_to_wipe into not yet extruded extrusions, that may change material (overridable).
float WipingExtrusions::mark_wiping_extrusions(const Print& print, const LayerTools &lt, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
{
const LayerTools& lt = *m_layer_tools;
const float min_infill_volume = 0.f; // ignore infill with smaller volume than this
if (! this->something_overridable || volume_to_wipe <= 0. || print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
return std::max(0.f, volume_to_wipe); // Soluble filament cannot be wiped in a random infill, neither the filament after it
if (! m_something_overridable || volume_to_wipe <= 0. ||
// Don't wipe a soluble filament into another object.
print.config().filament_soluble.get_at(old_extruder) ||
// Don't prime a soluble filament into another object.
print.config().filament_soluble.get_at(new_extruder))
// Soluble filament cannot be wiped in a random infill, neither the filament after it
return std::max(0.f, volume_to_wipe);
// we will sort objects so that dedicated for wiping are at the beginning:
ConstPrintObjectPtrs object_list(print.objects().begin(), print.objects().end());
std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects; });
std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects && ! b->config().wipe_into_objects; });
// We will now iterate through
// - first the dedicated objects to mark perimeters or infills (depending on infill_first)
@ -692,10 +701,10 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
bool wipe_into_infill_only = ! object->config().wipe_into_objects && region.config().wipe_into_infill;
if (print.config().infill_first != perimeters_done || wipe_into_infill_only) {
for (const ExtrusionEntity* ee : layerm->fills.entities) { // iterate through all infill Collections
for (const ExtrusionEntity* ee : layerm->fills()) { // iterate through all infill Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region))
if (!is_overriddable(*fill, lt, print.config(), *object, region))
continue;
if (wipe_into_infill_only && ! print.config().infill_first)
@ -716,9 +725,9 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
// Now the same for perimeters - see comments above for explanation:
if (object->config().wipe_into_objects && print.config().infill_first == perimeters_done)
{
for (const ExtrusionEntity* ee : layerm->perimeters.entities) {
for (const ExtrusionEntity* ee : layerm->perimeters()) {
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) {
if (is_overriddable(*fill, lt, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) {
set_extruder_override(fill, copy, new_extruder, num_of_copies);
if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
// More material was purged already than asked for.
@ -740,14 +749,13 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
// that were not actually overridden. If they are part of a dedicated object, printing them with the extruder
// they were initially assigned to might mean violating the perimeter-infill order. We will therefore go through
// them again and make sure we override it.
void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
void WipingExtrusions::ensure_perimeters_infills_order(const Print& print, const LayerTools &lt)
{
if (! this->something_overridable)
if (! m_something_overridable)
return;
const LayerTools& lt = *m_layer_tools;
unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config());
unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config());
unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config(), lt);
unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config(), lt);
for (const PrintObject* object : print.objects()) {
// Finds this layer:
@ -762,10 +770,11 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
continue;
for (const ExtrusionEntity* ee : layerm->fills.entities) { // iterate through all infill Collections
for (const ExtrusionEntity* ee : layerm->fills()) { // iterate through all infill Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
assert(fill);
if (!is_overriddable(*fill, print.config(), *object, region)
if (!is_overriddable(*fill, lt, print.config(), *object, region)
|| is_entity_overridden(fill, copy) )
continue;
@ -775,8 +784,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
// Either way, we will now force-override it with something suitable:
if (print.config().infill_first
|| object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|| lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|| ! lt.has_extruder(lt.infill_extruder(region)))) // we have to force override - this could violate infill_first (FIXME)
|| lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder) // !infill_first, but perimeter is already printed when last extruder prints
|| ! lt.has_extruder(lt.infill_extruder(region))) // we have to force override - this could violate infill_first (FIXME)
set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
else {
// In this case we can (and should) leave it to be printed normally.
@ -785,9 +794,10 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
}
// Now the same for perimeters - see comments above for explanation:
for (const ExtrusionEntity* ee : layerm->perimeters.entities) { // iterate through all perimeter Collections
for (const ExtrusionEntity* ee : layerm->perimeters()) { // iterate through all perimeter Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, copy))
assert(fill);
if (is_overriddable(*fill, lt, print.config(), *object, region) && ! is_entity_overridden(fill, copy))
set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
}
}
@ -795,24 +805,4 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
}
}
// Following function is called from GCode::process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity.
// If this extrusion does not have any override, nullptr is returned.
// Otherwise it modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known,
// so -1 was used as "print as usual").
// The resulting vector therefore keeps track of which extrusions are the ones that were overridden and which were not. If the extruder used is overridden,
// its number is saved as is (zero-based index). Regular extrusions are saved as -number-1 (unfortunately there is no negative zero).
const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies)
{
ExtruderPerCopy *overrides = nullptr;
auto entity_map_it = entity_map.find(entity);
if (entity_map_it != entity_map.end()) {
overrides = &entity_map_it->second;
overrides->resize(num_of_copies, -1);
// Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information):
std::replace(overrides->begin(), overrides->end(), -1, -correct_extruder_id-1);
}
return overrides;
}
} // namespace Slic3r

View File

@ -6,6 +6,7 @@
#include "../libslic3r.h"
#include <utility>
#include <cstddef>
#include <boost/container/small_vector.hpp>
@ -14,6 +15,7 @@ namespace Slic3r {
class Print;
class PrintObject;
class LayerTools;
class ToolOrdering;
namespace CustomGCode { struct Item; }
class PrintRegion;
@ -24,54 +26,51 @@ class WipingExtrusions
{
public:
bool is_anything_overridden() const { // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case
return something_overridden;
return m_something_overridden;
}
// When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place.
typedef boost::container::small_vector<int32_t, 3> ExtruderPerCopy;
using ExtruderPerCopy =
#ifdef NDEBUG
boost::container::small_vector<int32_t, 3>;
#else // NDEBUG
std::vector<int32_t>;
#endif // NDEBUG
// This is called from GCode::process_layer - see implementation for further comments:
const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies);
// This is called from GCode::process_layer_single_object()
// Returns positive number if the extruder is overridden.
// Returns -1 if not.
int get_extruder_override(const ExtrusionEntity* entity, uint32_t instance_id) const {
auto entity_map_it = m_entity_map.find(entity);
return entity_map_it == m_entity_map.end() ? -1 : entity_map_it->second[instance_id];
}
// This function goes through all infill entities, decides which ones will be used for wiping and
// marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
float mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe);
float mark_wiping_extrusions(const Print& print, const LayerTools& lt, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe);
void ensure_perimeters_infills_order(const Print& print);
void ensure_perimeters_infills_order(const Print& print, const LayerTools& lt);
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) {
bool out = this->is_overriddable(ee, print_config, object, region);
this->something_overridable |= out;
return out;
}
void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
void set_something_overridable() { m_something_overridable = true; }
private:
int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
// This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual)
void set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies);
// Returns true in case that entity is not printed with its usual extruder for a given copy:
bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const {
auto it = entity_map.find(entity);
return it == entity_map.end() ? false : it->second[copy_id] != -1;
auto it = m_entity_map.find(entity);
return it == m_entity_map.end() ? false : it->second[copy_id] != -1;
}
std::map<const ExtrusionEntity*, ExtruderPerCopy> entity_map; // to keep track of who prints what
bool something_overridable = false;
bool something_overridden = false;
const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to
std::map<const ExtrusionEntity*, ExtruderPerCopy> m_entity_map; // to keep track of who prints what
bool m_something_overridable = false;
bool m_something_overridden = false;
};
class LayerTools
{
public:
LayerTools(const coordf_t z) : print_z(z) {}
// Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other.
// In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports).
bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; }
@ -109,12 +108,14 @@ public:
// Custom G-code (color change, extruder switch, pause) to be performed before this layer starts to print.
const CustomGCode::Item *custom_gcode = nullptr;
WipingExtrusions& wiping_extrusions() {
m_wiping_extrusions.set_layer_tools_ptr(this);
return m_wiping_extrusions;
}
WipingExtrusions& wiping_extrusions_nonconst() { return m_wiping_extrusions; }
const WipingExtrusions& wiping_extrusions() const { return m_wiping_extrusions; }
private:
// to access LayerTools private constructor
friend class ToolOrdering;
LayerTools(const coordf_t z) : print_z(z) {}
// This object holds list of extrusion that will be used for extruder wiping
WipingExtrusions m_wiping_extrusions;
};

View File

@ -1,6 +1,7 @@
#include "MedialAxis.hpp"
#include "clipper.hpp"
#include "VoronoiOffset.hpp"
#ifdef SLIC3R_DEBUG
namespace boost { namespace polygon {
@ -392,8 +393,7 @@ inline const typename VD::point_type retrieve_cell_point(const typename VD::cell
}
template<typename VD, typename SEGMENTS>
inline std::pair<typename VD::coord_type, typename VD::coord_type>
measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
inline std::pair<typename VD::coord_type, typename VD::coord_type> measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
{
typedef typename VD::coord_type T;
const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y());
@ -442,15 +442,21 @@ private:
const Lines &lines;
};
void
MedialAxis::build(ThickPolylines* polylines)
MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expolygon) :
m_expolygon(expolygon), m_lines(expolygon.lines()), m_min_width(min_width), m_max_width(max_width)
{}
void MedialAxis::build(ThickPolylines* polylines)
{
construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd);
construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd);
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines);
// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
// std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
/*
// DEBUG: dump all Voronoi edges
{
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); ++edge) {
if (edge->is_infinite()) continue;
ThickPolyline polyline;
@ -463,73 +469,59 @@ MedialAxis::build(ThickPolylines* polylines)
*/
//typedef const VD::vertex_type vert_t;
typedef const VD::edge_type edge_t;
using edge_t = const VD::edge_type;
// collect valid edges (i.e. prune those not belonging to MAT)
// note: this keeps twins, so it inserts twice the number of the valid edges
this->valid_edges.clear();
{
std::set<const VD::edge_type*> seen_edges;
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
// if we only process segments representing closed loops, none if the
// infinite edges (if any) would be part of our MAT anyway
if (edge->is_secondary() || edge->is_infinite()) continue;
// don't re-validate twins
if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed?
seen_edges.insert(&*edge);
seen_edges.insert(edge->twin());
if (!this->validate_edge(&*edge)) continue;
this->valid_edges.insert(&*edge);
this->valid_edges.insert(edge->twin());
m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{});
for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); edge += 2)
if (edge->is_primary() && edge->is_finite() &&
(Voronoi::vertex_category(edge->vertex0()) == Voronoi::VertexCategory::Inside ||
Voronoi::vertex_category(edge->vertex1()) == Voronoi::VertexCategory::Inside) &&
this->validate_edge(&*edge)) {
// Valid skeleton edge.
this->edge_data(*edge).first.active = true;
}
}
this->edges = this->valid_edges;
// iterate through the valid edges to build polylines
while (!this->edges.empty()) {
const edge_t* edge = *this->edges.begin();
ThickPolyline reverse_polyline;
for (VD::const_edge_iterator seed_edge = m_vd.edges().begin(); seed_edge != m_vd.edges().end(); seed_edge += 2)
if (EdgeData &seed_edge_data = this->edge_data(*seed_edge).first; seed_edge_data.active) {
// Mark this edge as visited.
seed_edge_data.active = false;
// start a polyline
// Start a polyline.
ThickPolyline polyline;
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
polyline.width.push_back(this->thickness[edge].first);
polyline.width.push_back(this->thickness[edge].second);
polyline.points.emplace_back(seed_edge->vertex0()->x(), seed_edge->vertex0()->y());
polyline.points.emplace_back(seed_edge->vertex1()->x(), seed_edge->vertex1()->y());
polyline.width.emplace_back(seed_edge_data.width_start);
polyline.width.emplace_back(seed_edge_data.width_end);
// Grow the polyline in a forward direction.
this->process_edge_neighbors(&*seed_edge, &polyline);
// remove this edge and its twin from the available edges
(void)this->edges.erase(edge);
(void)this->edges.erase(edge->twin());
// get next points
this->process_edge_neighbors(edge, &polyline);
// get previous points
{
ThickPolyline rpolyline;
this->process_edge_neighbors(edge->twin(), &rpolyline);
polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend());
polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend());
polyline.endpoints.first = rpolyline.endpoints.second;
}
// Grow the polyline in a backward direction.
reverse_polyline.clear();
this->process_edge_neighbors(seed_edge->twin(), &reverse_polyline);
polyline.points.insert(polyline.points.begin(), reverse_polyline.points.rbegin(), reverse_polyline.points.rend());
polyline.width.insert(polyline.width.begin(), reverse_polyline.width.rbegin(), reverse_polyline.width.rend());
polyline.endpoints.first = reverse_polyline.endpoints.second;
assert(polyline.width.size() == polyline.points.size() * 2 - 2);
// prevent loop endpoints from being extended
// Prevent loop endpoints from being extended.
if (polyline.first_point() == polyline.last_point()) {
polyline.endpoints.first = false;
polyline.endpoints.second = false;
}
// append polyline to result
polylines->push_back(polyline);
// Append polyline to result.
polylines->emplace_back(std::move(polyline));
}
#ifdef SLIC3R_DEBUG
{
static int iRun = 0;
dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str());
dump_voronoi_to_svg(m_lines, m_vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str());
printf("Thick lines: ");
for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) {
ThickLines lines = it->thicklines();
@ -542,56 +534,66 @@ MedialAxis::build(ThickPolylines* polylines)
#endif /* SLIC3R_DEBUG */
}
void
MedialAxis::build(Polylines* polylines)
void MedialAxis::build(Polylines* polylines)
{
ThickPolylines tp;
this->build(&tp);
polylines->insert(polylines->end(), tp.begin(), tp.end());
}
void
MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline)
void MedialAxis::process_edge_neighbors(const VD::edge_type *edge, ThickPolyline* polyline)
{
while (true) {
for (;;) {
// Since rot_next() works on the edge starting point but we want
// to find neighbors on the ending point, we just swap edge with
// its twin.
const VD::edge_type *twin = edge->twin();
// count neighbors for this edge
std::vector<const VD::edge_type*> neighbors;
for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin;
neighbor = neighbor->rot_next()) {
if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor);
size_t num_neighbors = 0;
const VD::edge_type *first_neighbor = nullptr;
for (const VD::edge_type *neighbor = twin->rot_next(); neighbor != twin; neighbor = neighbor->rot_next())
if (this->edge_data(*neighbor).first.active) {
if (num_neighbors == 0)
first_neighbor = neighbor;
++ num_neighbors;
}
// if we have a single neighbor then we can continue recursively
if (neighbors.size() == 1) {
const VD::edge_type* neighbor = neighbors.front();
// break if this is a closed loop
if (this->edges.count(neighbor) == 0) return;
Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y());
polyline->points.push_back(new_point);
polyline->width.push_back(this->thickness[neighbor].first);
polyline->width.push_back(this->thickness[neighbor].second);
(void)this->edges.erase(neighbor);
(void)this->edges.erase(neighbor->twin());
edge = neighbor;
} else if (neighbors.size() == 0) {
if (num_neighbors == 1) {
if (std::pair<EdgeData&, bool> neighbor_data = this->edge_data(*first_neighbor);
neighbor_data.first.active) {
neighbor_data.first.active = false;
polyline->points.emplace_back(first_neighbor->vertex1()->x(), first_neighbor->vertex1()->y());
if (neighbor_data.second) {
polyline->width.push_back(neighbor_data.first.width_end);
polyline->width.push_back(neighbor_data.first.width_start);
} else {
polyline->width.push_back(neighbor_data.first.width_start);
polyline->width.push_back(neighbor_data.first.width_end);
}
edge = first_neighbor;
// Continue chaining.
continue;
}
} else if (num_neighbors == 0) {
polyline->endpoints.second = true;
return;
} else {
// T-shaped or star-shaped joint
return;
}
// Stop chaining.
break;
}
}
bool MedialAxis::validate_edge(const VD::edge_type* edge)
{
auto retrieve_segment = [this](const VD::cell_type* cell) -> const Line& { return m_lines[cell->source_index()]; };
auto retrieve_endpoint = [retrieve_segment](const VD::cell_type* cell) -> const Point& {
const Line &line = retrieve_segment(cell);
return cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT ? line.a : line.b;
};
// prevent overflows and detect almost-infinite edges
#ifndef CLIPPERLIB_INT32
if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
@ -602,32 +604,18 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
#endif // CLIPPERLIB_INT32
// construct the line representing this edge of the Voronoi diagram
const Line line(
Point( edge->vertex0()->x(), edge->vertex0()->y() ),
Point( edge->vertex1()->x(), edge->vertex1()->y() )
);
// discard edge if it lies outside the supplied shape
// this could maybe be optimized (checking inclusion of the endpoints
// might give false positives as they might belong to the contour itself)
if (this->expolygon != NULL) {
if (line.a == line.b) {
// in this case, contains(line) returns a false positive
if (!this->expolygon->contains(line.a)) return false;
} else {
if (!this->expolygon->contains(line)) return false;
}
}
const Line line({ edge->vertex0()->x(), edge->vertex0()->y() },
{ edge->vertex1()->x(), edge->vertex1()->y() });
// retrieve the original line segments which generated the edge we're checking
const VD::cell_type* cell_l = edge->cell();
const VD::cell_type* cell_r = edge->twin()->cell();
const Line &segment_l = this->retrieve_segment(cell_l);
const Line &segment_r = this->retrieve_segment(cell_r);
const Line &segment_l = retrieve_segment(cell_l);
const Line &segment_r = retrieve_segment(cell_r);
/*
SVG svg("edge.svg");
svg.draw(*this->expolygon);
svg.draw(m_expolygon);
svg.draw(line);
svg.draw(segment_l, "red");
svg.draw(segment_r, "blue");
@ -651,16 +639,17 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
coordf_t w0 = cell_r->contains_segment()
? segment_r.distance_to(line.a)*2
: (this->retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2;
: (retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2;
coordf_t w1 = cell_l->contains_segment()
? segment_l.distance_to(line.b)*2
: (this->retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2;
: (retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2;
if (cell_l->contains_segment() && cell_r->contains_segment()) {
// calculate the relative angle between the two boundary segments
double angle = fabs(segment_r.orientation() - segment_l.orientation());
if (angle > PI) angle = 2*PI - angle;
if (angle > PI)
angle = 2. * PI - angle;
assert(angle >= 0 && angle <= PI);
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction)
@ -669,12 +658,11 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
// this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
// we don't run it on edges not generated by two segments (thus generated by one segment
// and the endpoint of another segment), since their orientation would not be meaningful
if (PI - angle > PI/8) {
if (PI - angle > PI / 8.) {
// angle is not narrow enough
// only apply this filter to segments that are not too short otherwise their
// angle could possibly be not meaningful
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width)
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= m_min_width)
return false;
}
} else {
@ -682,31 +670,17 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
return false;
}
if (w0 < this->min_width && w1 < this->min_width)
return false;
if (w0 > this->max_width && w1 > this->max_width)
return false;
this->thickness[edge] = std::make_pair(w0, w1);
this->thickness[edge->twin()] = std::make_pair(w1, w0);
if ((w0 >= m_min_width || w1 >= m_min_width) &&
(w0 <= m_max_width || w1 <= m_max_width)) {
std::pair<EdgeData&, bool> ed = this->edge_data(*edge);
if (ed.second)
std::swap(w0, w1);
ed.first.width_start = w0;
ed.first.width_end = w1;
return true;
}
const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const
{
return this->lines[cell->source_index()];
}
const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const
{
const Line& line = this->retrieve_segment(cell);
if (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
return line.a;
} else {
return line.b;
}
return false;
}
} } // namespace Slicer::Geometry

View File

@ -4,30 +4,43 @@
#include "Voronoi.hpp"
#include "../ExPolygon.hpp"
namespace Slic3r { namespace Geometry {
namespace Slic3r::Geometry {
class MedialAxis {
public:
Lines lines;
const ExPolygon* expolygon;
double max_width;
double min_width;
MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL)
: expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {};
MedialAxis(double min_width, double max_width, const ExPolygon &expolygon);
void build(ThickPolylines* polylines);
void build(Polylines* polylines);
private:
// Input
const ExPolygon &m_expolygon;
Lines m_lines;
// for filtering of the skeleton edges
double m_min_width;
double m_max_width;
// Voronoi Diagram.
using VD = VoronoiDiagram;
VD vd;
std::set<const VD::edge_type*> edges, valid_edges;
std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness;
VD m_vd;
// Annotations of the VD skeleton edges.
struct EdgeData {
bool active { false };
double width_start { 0 };
double width_end { 0 };
};
// Returns a reference to EdgeData and a "reversed" boolean.
std::pair<EdgeData&, bool> edge_data(const VD::edge_type &edge) {
size_t edge_id = &edge - &m_vd.edges().front();
return { m_edge_data[edge_id / 2], (edge_id & 1) != 0 };
}
std::vector<EdgeData> m_edge_data;
void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline);
bool validate_edge(const VD::edge_type* edge);
const Line& retrieve_segment(const VD::cell_type* cell) const;
const Point& retrieve_endpoint(const VD::cell_type* cell) const;
};
} } // namespace Slicer::Geometry
} // namespace Slicer::Geometry
#endif // slic3r_Geometry_MedialAxis_hpp_

View File

@ -1,4 +1,4 @@
// Polygon offsetting using Voronoi diagram prodiced by boost::polygon.
// Polygon offsetting using Voronoi diagram produced by boost::polygon.
#ifndef slic3r_VoronoiOffset_hpp_
#define slic3r_VoronoiOffset_hpp_

View File

@ -1,4 +1,5 @@
#include "Layer.hpp"
#include <clipper/clipper_z.hpp>
#include "ClipperUtils.hpp"
#include "Print.hpp"
#include "Fill/Fill.hpp"
@ -22,7 +23,7 @@ Layer::~Layer()
bool Layer::empty() const
{
for (const LayerRegion *layerm : m_regions)
if (layerm != nullptr && ! layerm->slices.empty())
if (layerm != nullptr && ! layerm->slices().empty())
// Non empty layer.
return false;
return true;
@ -40,11 +41,11 @@ void Layer::make_slices()
ExPolygons slices;
if (m_regions.size() == 1) {
// optimization: if we only have one region, take its slices
slices = to_expolygons(m_regions.front()->slices.surfaces);
slices = to_expolygons(m_regions.front()->slices().surfaces);
} else {
Polygons slices_p;
for (LayerRegion *layerm : m_regions)
polygons_append(slices_p, to_polygons(layerm->slices.surfaces));
polygons_append(slices_p, to_polygons(layerm->slices().surfaces));
slices = union_safety_offset_ex(slices_p);
}
@ -65,6 +66,289 @@ void Layer::make_slices()
this->lslices.emplace_back(std::move(slices[i]));
}
// used by Layer::build_up_down_graph()
[[nodiscard]] static ClipperLib_Z::Paths expolygons_to_zpaths(const ExPolygons &expolygons, coord_t isrc)
{
size_t num_paths = 0;
for (const ExPolygon &expolygon : expolygons)
num_paths += expolygon.num_contours();
ClipperLib_Z::Paths out;
out.reserve(num_paths);
for (const ExPolygon &expolygon : expolygons) {
for (size_t icontour = 0; icontour < expolygon.num_contours(); ++ icontour) {
const Polygon &contour = expolygon.contour_or_hole(icontour);
out.emplace_back();
ClipperLib_Z::Path &path = out.back();
path.reserve(contour.size());
for (const Point &p : contour.points)
path.push_back({ p.x(), p.y(), isrc });
}
++ isrc;
}
return out;
}
// used by Layer::build_up_down_graph()
static void connect_layer_slices(
Layer &below,
Layer &above,
const ClipperLib_Z::PolyTree &polytree,
const std::vector<std::pair<coord_t, coord_t>> &intersections,
const coord_t offset_below,
const coord_t offset_above
#ifndef NDEBUG
, const coord_t offset_end
#endif // NDEBUG
)
{
class Visitor {
public:
Visitor(const std::vector<std::pair<coord_t, coord_t>> &intersections,
Layer &below, Layer &above, const coord_t offset_below, const coord_t offset_above
#ifndef NDEBUG
, const coord_t offset_end
#endif // NDEBUG
) :
m_intersections(intersections), m_below(below), m_above(above), m_offset_below(offset_below), m_offset_above(offset_above)
#ifndef NDEBUG
, m_offset_end(offset_end)
#endif // NDEBUG
{}
void visit(const ClipperLib_Z::PolyNode &polynode)
{
#ifndef NDEBUG
auto assert_intersection_valid = [this](int i, int j) {
assert(i != j);
if (i > j)
std::swap(i, j);
assert(i >= m_offset_below);
assert(i < m_offset_above);
assert(j >= m_offset_above);
assert(j < m_offset_end);
return true;
};
#endif // NDEBUG
if (polynode.Contour.size() >= 3) {
// If there is an intersection point, it should indicate which contours (one from layer below, the other from layer above) intersect.
// Otherwise the contour is fully inside another contour.
int32_t i = 0, j = 0;
for (int icontour = 0; icontour <= polynode.ChildCount(); ++ icontour) {
const bool first = icontour == 0;
const ClipperLib_Z::Path &contour = first ? polynode.Contour : polynode.Childs[icontour - 1]->Contour;
if (contour.size() >= 3) {
if (first) {
i = contour.front().z();
j = i;
if (i < 0) {
std::tie(i, j) = m_intersections[-i - 1];
assert(assert_intersection_valid(i, j));
goto end;
}
}
for (const ClipperLib_Z::IntPoint& pt : contour) {
j = pt.z();
if (j < 0) {
std::tie(i, j) = m_intersections[-j - 1];
assert(assert_intersection_valid(i, j));
goto end;
}
else if (i != j)
goto end;
}
}
}
end:
bool found = false;
if (i == j) {
// The contour is completely inside another contour.
Point pt(polynode.Contour.front().x(), polynode.Contour.front().y());
if (i < m_offset_above) {
// Index of an island below. Look-it up in the island above.
assert(i >= m_offset_below);
i -= m_offset_below;
for (int l = int(m_above.lslices_ex.size()) - 1; l >= 0; -- l) {
LayerSlice &lslice = m_above.lslices_ex[l];
if (lslice.bbox.contains(pt) && m_above.lslices[l].contains(pt)) {
found = true;
j = l;
assert(i >= 0 && i < m_below.lslices_ex.size());
assert(j >= 0 && j < m_above.lslices_ex.size());
break;
}
}
} else {
// Index of an island above. Look-it up in the island below.
assert(j < m_offset_end);
j -= m_offset_above;
for (int l = int(m_below.lslices_ex.size()) - 1; l >= 0; -- l) {
LayerSlice &lslice = m_below.lslices_ex[l];
if (lslice.bbox.contains(pt) && m_below.lslices[l].contains(pt)) {
found = true;
i = l;
assert(i >= 0 && i < m_below.lslices_ex.size());
assert(j >= 0 && j < m_above.lslices_ex.size());
break;
}
}
}
} else {
assert(assert_intersection_valid(i, j));
if (i > j)
std::swap(i, j);
i -= m_offset_below;
j -= m_offset_above;
assert(i >= 0 && i < m_below.lslices_ex.size());
assert(j >= 0 && j < m_above.lslices_ex.size());
found = true;
}
if (found) {
// Subtract area of holes from the area of outer contour.
double area = ClipperLib_Z::Area(polynode.Contour);
for (int icontour = 0; icontour < polynode.ChildCount(); ++ icontour)
area -= ClipperLib_Z::Area(polynode.Childs[icontour]->Contour);
// Store the links and area into the contours.
LayerSlice::Links &links_below = m_below.lslices_ex[i].overlaps_above;
LayerSlice::Links &links_above = m_above.lslices_ex[j].overlaps_below;
LayerSlice::Link key{ j };
auto it_below = std::lower_bound(links_below.begin(), links_below.end(), key, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; });
if (it_below != links_below.end() && it_below->slice_idx == j) {
it_below->area += area;
} else {
auto it_above = std::lower_bound(links_above.begin(), links_above.end(), key, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; });
if (it_above != links_above.end() && it_above->slice_idx == i) {
it_above->area += area;
} else {
// Insert into one of the two vectors.
bool take_below = false;
if (links_below.size() < LayerSlice::LinksStaticSize)
take_below = false;
else if (links_above.size() >= LayerSlice::LinksStaticSize) {
size_t shift_below = links_below.end() - it_below;
size_t shift_above = links_above.end() - it_above;
take_below = shift_below < shift_above;
}
if (take_below)
links_below.insert(it_below, { j, float(area) });
else
links_above.insert(it_above, { i, float(area) });
}
}
}
}
for (int i = 0; i < polynode.ChildCount(); ++ i)
for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++ j)
this->visit(*polynode.Childs[i]->Childs[j]);
}
private:
const std::vector<std::pair<coord_t, coord_t>> &m_intersections;
Layer &m_below;
Layer &m_above;
const coord_t m_offset_below;
const coord_t m_offset_above;
#ifndef NDEBUG
const coord_t m_offset_end;
#endif // NDEBUG
} visitor(intersections, below, above, offset_below, offset_above
#ifndef NDEBUG
, offset_end
#endif // NDEBUG
);
for (int i = 0; i < polytree.ChildCount(); ++ i)
visitor.visit(*polytree.Childs[i]);
#ifndef NDEBUG
// Verify that only one directional link is stored: either from bottom slice up or from upper slice down.
for (int32_t islice = 0; islice < below.lslices_ex.size(); ++ islice) {
LayerSlice::Links &links1 = below.lslices_ex[islice].overlaps_above;
for (LayerSlice::Link &link1 : links1) {
LayerSlice::Links &links2 = above.lslices_ex[link1.slice_idx].overlaps_below;
assert(! std::binary_search(links2.begin(), links2.end(), link1, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; }));
}
}
for (int32_t islice = 0; islice < above.lslices_ex.size(); ++ islice) {
LayerSlice::Links &links1 = above.lslices_ex[islice].overlaps_below;
for (LayerSlice::Link &link1 : links1) {
LayerSlice::Links &links2 = below.lslices_ex[link1.slice_idx].overlaps_above;
assert(! std::binary_search(links2.begin(), links2.end(), link1, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; }));
}
}
#endif // NDEBUG
// Scatter the links, but don't sort them yet.
for (int32_t islice = 0; islice < below.lslices_ex.size(); ++ islice)
for (LayerSlice::Link &link : below.lslices_ex[islice].overlaps_above)
above.lslices_ex[link.slice_idx].overlaps_below.push_back({ islice, link.area });
for (int32_t islice = 0; islice < above.lslices_ex.size(); ++ islice)
for (LayerSlice::Link &link : above.lslices_ex[islice].overlaps_below)
below.lslices_ex[link.slice_idx].overlaps_above.push_back({ islice, link.area });
// Sort the links.
for (LayerSlice &lslice : below.lslices_ex)
std::sort(lslice.overlaps_above.begin(), lslice.overlaps_above.end(), [](const LayerSlice::Link &l, const LayerSlice::Link &r){ return l.slice_idx < r.slice_idx; });
for (LayerSlice &lslice : above.lslices_ex)
std::sort(lslice.overlaps_below.begin(), lslice.overlaps_below.end(), [](const LayerSlice::Link &l, const LayerSlice::Link &r){ return l.slice_idx < r.slice_idx; });
}
void Layer::build_up_down_graph(Layer& below, Layer& above)
{
coord_t paths_below_offset = 0;
ClipperLib_Z::Paths paths_below = expolygons_to_zpaths(below.lslices, paths_below_offset);
coord_t paths_above_offset = paths_below_offset + coord_t(below.lslices.size());
ClipperLib_Z::Paths paths_above = expolygons_to_zpaths(above.lslices, paths_above_offset);
#ifndef NDEBUG
coord_t paths_end = paths_above_offset + coord_t(above.lslices.size());
#endif // NDEBUG
class ZFill {
public:
ZFill() = default;
void reset() { m_intersections.clear(); }
void operator()(
const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top,
const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top,
ClipperLib_Z::IntPoint& pt) {
coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() };
coord_t* begin = srcs;
coord_t* end = srcs + 4;
std::sort(begin, end);
end = std::unique(begin, end);
assert(begin + 2 == end);
if (begin + 1 == end)
pt.z() = *begin;
else if (begin + 2 <= end) {
// store a -1 based negative index into the "intersections" vector here.
m_intersections.emplace_back(srcs[0], srcs[1]);
pt.z() = -coord_t(m_intersections.size());
}
}
const std::vector<std::pair<coord_t, coord_t>>& intersections() const { return m_intersections; }
private:
std::vector<std::pair<coord_t, coord_t>> m_intersections;
} zfill;
ClipperLib_Z::Clipper clipper;
ClipperLib_Z::PolyTree result;
clipper.ZFillFunction(
[&zfill](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top,
const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt)
{ return zfill(e1bot, e1top, e2bot, e2top, pt); });
clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true);
clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true);
clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
connect_layer_slices(below, above, result, zfill.intersections(), paths_below_offset, paths_above_offset
#ifndef NDEBUG
, paths_end
#endif // NDEBUG
);
}
static inline bool layer_needs_raw_backup(const Layer *layer)
{
return ! (layer->regions().size() == 1 && (layer->id() > 0 || layer->object()->config().elefant_foot_compensation.value == 0));
@ -74,10 +358,10 @@ void Layer::backup_untyped_slices()
{
if (layer_needs_raw_backup(this)) {
for (LayerRegion *layerm : m_regions)
layerm->raw_slices = to_expolygons(layerm->slices.surfaces);
layerm->m_raw_slices = to_expolygons(layerm->slices().surfaces);
} else {
assert(m_regions.size() == 1);
m_regions.front()->raw_slices.clear();
m_regions.front()->m_raw_slices.clear();
}
}
@ -85,10 +369,10 @@ void Layer::restore_untyped_slices()
{
if (layer_needs_raw_backup(this)) {
for (LayerRegion *layerm : m_regions)
layerm->slices.set(layerm->raw_slices, stInternal);
layerm->m_slices.set(layerm->m_raw_slices, stInternal);
} else {
assert(m_regions.size() == 1);
m_regions.front()->slices.set(this->lslices, stInternal);
m_regions.front()->m_slices.set(this->lslices, stInternal);
}
}
@ -101,13 +385,13 @@ void Layer::restore_untyped_slices_no_extra_perimeters()
if (layer_needs_raw_backup(this)) {
for (LayerRegion *layerm : m_regions)
if (! layerm->region().config().extra_perimeters.value)
layerm->slices.set(layerm->raw_slices, stInternal);
layerm->m_slices.set(layerm->m_raw_slices, stInternal);
} else {
assert(m_regions.size() == 1);
LayerRegion *layerm = m_regions.front();
// This optimization is correct, as extra_perimeters are only reused by prepare_infill() with multi-regions.
//if (! layerm->region().config().extra_perimeters.value)
layerm->slices.set(this->lslices, stInternal);
layerm->m_slices.set(this->lslices, stInternal);
}
}
@ -125,7 +409,7 @@ ExPolygons Layer::merged(float offset_scaled) const
const PrintRegionConfig &config = layerm->region().config();
// Our users learned to bend Slic3r to produce empty volumes to act as subtracters. Only add the region if it is non-empty.
if (config.bottom_solid_layers > 0 || config.top_solid_layers > 0 || config.fill_density > 0. || config.perimeters > 0)
append(polygons, offset(layerm->slices.surfaces, offset_scaled));
append(polygons, offset(layerm->slices().surfaces, offset_scaled));
}
ExPolygons out = union_ex(polygons);
if (offset_scaled2 != 0.f)
@ -142,25 +426,45 @@ void Layer::make_perimeters()
// keep track of regions whose perimeters we have already generated
std::vector<unsigned char> done(m_regions.size(), false);
std::vector<uint32_t> layer_region_ids;
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> perimeter_and_gapfill_ranges;
ExPolygons fill_expolygons;
std::vector<ExPolygonRange> fill_expolygons_ranges;
SurfacesPtr surfaces_to_merge;
SurfacesPtr surfaces_to_merge_temp;
auto layer_region_reset_perimeters = [](LayerRegion &layerm) {
layerm.m_perimeters.clear();
layerm.m_fills.clear();
layerm.m_thin_fills.clear();
layerm.m_fill_expolygons.clear();
layerm.m_fill_expolygons_bboxes.clear();
layerm.m_fill_expolygons_composite.clear();
layerm.m_fill_expolygons_composite_bboxes.clear();
};
// Remove layer islands, remove references to perimeters and fills from these layer islands to LayerRegion ExtrusionEntities.
for (LayerSlice &lslice : this->lslices_ex)
lslice.islands.clear();
for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm)
if ((*layerm)->slices.empty()) {
(*layerm)->perimeters.clear();
(*layerm)->fills.clear();
(*layerm)->thin_fills.clear();
} else {
size_t region_id = layerm - m_regions.begin();
if (done[region_id])
continue;
if (size_t region_id = layerm - m_regions.begin(); ! done[region_id]) {
layer_region_reset_perimeters(**layerm);
if (! (*layerm)->slices().empty()) {
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
done[region_id] = true;
const PrintRegionConfig &config = (*layerm)->region().config();
perimeter_and_gapfill_ranges.clear();
fill_expolygons.clear();
fill_expolygons_ranges.clear();
surfaces_to_merge.clear();
// find compatible regions
LayerRegionPtrs layerms;
layerms.push_back(*layerm);
layer_region_ids.clear();
layer_region_ids.push_back(region_id);
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it)
if (! (*it)->slices.empty()) {
if (! (*it)->slices().empty()) {
LayerRegion* other_layerm = *it;
const PrintRegionConfig &other_config = other_layerm->region().config();
if (config.perimeter_extruder == other_config.perimeter_extruder
@ -178,59 +482,276 @@ void Layer::make_perimeters()
&& config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness
&& config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist)
{
other_layerm->perimeters.clear();
other_layerm->fills.clear();
other_layerm->thin_fills.clear();
layerms.push_back(other_layerm);
layer_region_reset_perimeters(*other_layerm);
layer_region_ids.push_back(it - m_regions.begin());
done[it - m_regions.begin()] = true;
}
}
if (layerms.size() == 1) { // optimization
(*layerm)->fill_surfaces.surfaces.clear();
(*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces);
(*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces);
if (layer_region_ids.size() == 1) { // optimization
(*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges);
this->sort_perimeters_into_islands((*layerm)->slices(), region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids);
} else {
SurfaceCollection new_slices;
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
LayerRegion *layerm_config = layerms.front();
LayerRegion *layerm_config = m_regions[layer_region_ids.front()];
{
// group slices (surfaces) according to number of extra perimeters
std::map<unsigned short, Surfaces> slices; // extra_perimeters => [ surface, surface... ]
for (LayerRegion *layerm : layerms) {
for (const Surface &surface : layerm->slices.surfaces)
slices[surface.extra_perimeters].emplace_back(surface);
if (layerm->region().config().fill_density > layerm_config->region().config().fill_density)
layerm_config = layerm;
// Merge slices (surfaces) according to number of extra perimeters.
for (uint32_t region_id : layer_region_ids) {
LayerRegion &layerm = *m_regions[region_id];
for (const Surface &surface : layerm.slices())
surfaces_to_merge.emplace_back(&surface);
if (layerm.region().config().fill_density > layerm_config->region().config().fill_density)
layerm_config = &layerm;
}
std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r){ return l->extra_perimeters < r->extra_perimeters; });
for (size_t i = 0; i < surfaces_to_merge.size();) {
size_t j = i;
const Surface &first = *surfaces_to_merge[i];
size_t extra_perimeters = first.extra_perimeters;
for (; j < surfaces_to_merge.size() && surfaces_to_merge[j]->extra_perimeters == extra_perimeters; ++ j) ;
if (i + 1 == j)
// Nothing to merge, just copy.
new_slices.surfaces.emplace_back(*surfaces_to_merge[i]);
else {
surfaces_to_merge_temp.assign(surfaces_to_merge.begin() + i, surfaces_to_merge.begin() + j);
new_slices.append(offset_ex(surfaces_to_merge_temp, ClipperSafetyOffset), first);
}
i = j;
}
// merge the surfaces assigned to each group
for (std::pair<const unsigned short,Surfaces> &surfaces_with_extra_perimeters : slices)
new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front());
}
// make perimeters
SurfaceCollection fill_surfaces;
layerm_config->make_perimeters(new_slices, &fill_surfaces);
// assign fill_surfaces to each layer
if (!fill_surfaces.surfaces.empty()) {
for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) {
// Separate the fill surfaces.
ExPolygons expp = intersection_ex(fill_surfaces.surfaces, (*l)->slices.surfaces);
(*l)->fill_expolygons = expp;
(*l)->fill_surfaces.set(std::move(expp), fill_surfaces.surfaces.front());
}
layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges);
this->sort_perimeters_into_islands(new_slices, region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids);
}
}
}
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done";
}
void Layer::sort_perimeters_into_islands(
// Slices for which perimeters and fill_expolygons were just created.
// The slices may have been created by merging multiple source slices with the same perimeter parameters.
const SurfaceCollection &slices,
// Region where the perimeters, gap fills and fill expolygons are stored.
const uint32_t region_id,
// Perimeters and gap fills produced by the perimeter generator for the slices,
// sorted by the source slices.
const std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
// Fill expolygons produced for all source slices above.
ExPolygons &&fill_expolygons,
// Fill expolygon ranges sorted by the source slices.
const std::vector<ExPolygonRange> &fill_expolygons_ranges,
// If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces.
const std::vector<uint32_t> &layer_region_ids)
{
LayerRegion &this_layer_region = *m_regions[region_id];
// Bounding boxes of fill_expolygons.
BoundingBoxes fill_expolygons_bboxes;
fill_expolygons_bboxes.reserve(fill_expolygons.size());
for (const ExPolygon &expolygon : fill_expolygons)
fill_expolygons_bboxes.emplace_back(get_extents(expolygon));
// Take one sample point for each source slice, to be used to sort source slices into layer slices.
// source slice index + its sample.
std::vector<std::pair<uint32_t, Point>> perimeter_slices_queue;
perimeter_slices_queue.reserve(slices.size());
for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) {
const std::pair<ExtrusionRange, ExtrusionRange> &extrusions = perimeter_and_gapfill_ranges[islice];
Point sample;
bool sample_set = false;
if (! extrusions.first.empty()) {
sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point();
sample_set = true;
} else if (! extrusions.second.empty()) {
sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point();
sample_set = true;
} else {
for (uint32_t iexpoly : fill_expolygons_ranges[islice])
if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) {
sample = expoly.contour.points.front();
sample_set = true;
break;
}
}
// There may be a valid empty island.
// assert(sample_set);
if (sample_set)
perimeter_slices_queue.emplace_back(islice, sample);
}
// Map of source fill_expolygon into region and fill_expolygon of that region.
// -1: not set
std::vector<std::pair<int, int>> map_expolygon_to_region_and_fill;
const bool has_multiple_regions = layer_region_ids.size() > 1;
assert(has_multiple_regions || layer_region_ids.size() == 1);
// assign fill_surfaces to each layer
if (! fill_expolygons.empty()) {
if (has_multiple_regions) {
// Sort the bounding boxes lexicographically.
std::vector<uint32_t> fill_expolygons_bboxes_sorted(fill_expolygons_bboxes.size());
std::iota(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), 0);
std::sort(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), [&fill_expolygons_bboxes](uint32_t lhs, uint32_t rhs){
const BoundingBox &bbl = fill_expolygons_bboxes[lhs];
const BoundingBox &bbr = fill_expolygons_bboxes[rhs];
return bbl.min < bbr.min || (bbl.min == bbr.min && bbl.max < bbr.max);
});
map_expolygon_to_region_and_fill.assign(fill_expolygons.size(), std::make_pair(-1, -1));
for (uint32_t region_idx : layer_region_ids) {
LayerRegion &l = *m_regions[region_idx];
l.m_fill_expolygons = intersection_ex(l.slices().surfaces, fill_expolygons);
l.m_fill_expolygons_bboxes.reserve(l.fill_expolygons().size());
for (const ExPolygon &expolygon : l.fill_expolygons()) {
BoundingBox bbox = get_extents(expolygon);
l.m_fill_expolygons_bboxes.emplace_back(bbox);
auto it_bbox = std::lower_bound(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), bbox, [&fill_expolygons_bboxes](uint32_t lhs, const BoundingBox &bbr){
const BoundingBox &bbl = fill_expolygons_bboxes[lhs];
return bbl.min < bbr.min || (bbl.min == bbr.min && bbl.max < bbr.max);
});
if (it_bbox != fill_expolygons_bboxes_sorted.end())
if (uint32_t fill_id = *it_bbox; fill_expolygons_bboxes[fill_id] == bbox) {
// With a very high probability the two expolygons match exactly. Confirm that.
if (expolygons_match(expolygon, fill_expolygons[fill_id])) {
std::pair<int, int> &ref = map_expolygon_to_region_and_fill[fill_id];
// Only one expolygon produced by intersection with LayerRegion surface may match an expolygon of fill_expolygons.
assert(ref.first == -1);
ref.first = region_idx;
ref.second = int(&expolygon - l.fill_expolygons().data());
}
}
}
}
} else {
this_layer_region.m_fill_expolygons = std::move(fill_expolygons);
this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes);
}
}
// Sort perimeter extrusions, thin fill extrusions and fill expolygons into islands.
std::vector<uint32_t> region_fill_sorted_last;
auto insert_into_island = [
// Region where the perimeters, gap fills and fill expolygons are stored.
region_id,
// Whether there are infills with different regions generated for this LayerSlice.
has_multiple_regions,
// Perimeters and gap fills to be sorted into islands.
&perimeter_and_gapfill_ranges,
// Infill regions to be sorted into islands.
&fill_expolygons, &fill_expolygons_bboxes, &fill_expolygons_ranges,
// Mapping of fill_expolygon to region and its infill.
&map_expolygon_to_region_and_fill,
// Output
&regions = m_regions, &lslices_ex = this->lslices_ex,
// fill_expolygons and fill_expolygons_bboxes need to be sorted into contiguous sequence by island,
// thus region_fill_sorted_last contains last fill_expolygon processed (meaning sorted).
&region_fill_sorted_last]
(int lslice_idx, int source_slice_idx) {
lslices_ex[lslice_idx].islands.push_back({});
LayerIsland &island = lslices_ex[lslice_idx].islands.back();
island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first);
island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second;
if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) {
if (has_multiple_regions) {
// Check whether the fill expolygons of this island were split into multiple regions.
island.fill_region_id = LayerIsland::fill_region_composite_id;
for (uint32_t fill_idx : fill_range) {
const std::pair<int, int> &kvp = map_expolygon_to_region_and_fill[fill_idx];
if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) {
island.fill_region_id = LayerIsland::fill_region_composite_id;
break;
} else
island.fill_region_id = kvp.second;
}
if (island.fill_expolygons_composite()) {
// They were split, thus store the unsplit "composite" expolygons into the region of perimeters.
LayerRegion &this_layer_region = *regions[region_id];
auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size());
this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size());
std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite));
this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(),
fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end());
island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size()));
} else {
if (region_fill_sorted_last.empty())
region_fill_sorted_last.assign(regions.size(), 0);
uint32_t &last = region_fill_sorted_last[island.fill_region_id];
// They were not split and they belong to the same region.
// Sort the region m_fill_expolygons to a continuous span.
uint32_t begin = last;
LayerRegion &layerm = *regions[island.fill_region_id];
for (uint32_t fill_id : fill_range) {
uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second;
assert(region_fill_id >= last);
if (region_fill_id > last) {
std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]);
std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]);
}
++ last;
}
island.fill_expolygons = ExPolygonRange(begin, last);
}
} else {
// Layer island is made of one fill region only.
island.fill_expolygons = fill_range;
island.fill_region_id = region_id;
}
}
};
// First sort into islands using exact fit.
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
auto point_inside_surface = [&lslices = this->lslices, &lslices_ex = this->lslices_ex](size_t lslice_idx, const Point &point) {
const BoundingBox &bbox = lslices_ex[lslice_idx].bbox;
return point.x() >= bbox.min.x() && point.x() < bbox.max.x() &&
point.y() >= bbox.min.y() && point.y() < bbox.max.y() &&
// Exact match: Don't just test whether a point is inside the outer contour of an island,
// test also whether the point is not inside some hole of the same expolygon.
// This is unfortunatelly necessary because the point may be inside an expolygon of one of this expolygon's hole
// and missed due to numerical issues.
lslices[lslice_idx].contains(point);
};
for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx)
for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice)
if (point_inside_surface(lslice_idx, it_source_slice->second)) {
insert_into_island(lslice_idx, it_source_slice->first);
if (std::next(it_source_slice) != perimeter_slices_queue.end())
// Remove the current slice & point pair from the queue.
*it_source_slice = perimeter_slices_queue.back();
perimeter_slices_queue.pop_back();
break;
}
// If anything fails to be sorted in using exact fit, try to find a closest island.
auto point_inside_surface_dist2 =
[&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps = scaled<coord_t>(this->object()->print()->config().gcode_resolution.value) + SCALED_EPSILON]
(const size_t lslice_idx, const Point &point) {
const BoundingBox &bbox = lslices_ex[lslice_idx].bbox;
return
point.x() < bbox.min.x() - bbox_eps || point.x() > bbox.max.x() + bbox_eps ||
point.y() < bbox.min.y() - bbox_eps || point.y() > bbox.max.y() + bbox_eps ?
std::numeric_limits<double>::max() :
(lslices[lslice_idx].point_projection(point) - point).cast<double>().squaredNorm();
};
for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice) {
double d2min = std::numeric_limits<double>::max();
int lslice_idx_min = -1;
for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0; -- lslice_idx)
if (double d2 = point_inside_surface_dist2(lslice_idx, it_source_slice->second); d2 < d2min) {
d2min = d2;
lslice_idx_min = lslice_idx;
}
assert(lslice_idx_min != -1);
insert_into_island(lslice_idx_min, it_source_slice->first);
}
}
void Layer::export_region_slices_to_svg(const char *path) const
{
BoundingBox bbox;
for (const auto *region : m_regions)
for (const auto &surface : region->slices.surfaces)
for (const auto &surface : region->slices())
bbox.merge(get_extents(surface.expolygon));
Point legend_size = export_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
@ -239,7 +760,7 @@ void Layer::export_region_slices_to_svg(const char *path) const
SVG svg(path, bbox);
const float transparency = 0.5f;
for (const auto *region : m_regions)
for (const auto &surface : region->slices.surfaces)
for (const auto &surface : region->slices())
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
export_surface_type_legend_to_svg(svg, legend_pos);
svg.Close();
@ -256,7 +777,7 @@ void Layer::export_region_fill_surfaces_to_svg(const char *path) const
{
BoundingBox bbox;
for (const auto *region : m_regions)
for (const auto &surface : region->slices.surfaces)
for (const auto &surface : region->slices())
bbox.merge(get_extents(surface.expolygon));
Point legend_size = export_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
@ -265,7 +786,7 @@ void Layer::export_region_fill_surfaces_to_svg(const char *path) const
SVG svg(path, bbox);
const float transparency = 0.5f;
for (const auto *region : m_regions)
for (const auto &surface : region->slices.surfaces)
for (const auto &surface : region->slices())
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
export_surface_type_legend_to_svg(svg, legend_pos);
svg.Close();
@ -281,9 +802,9 @@ void Layer::export_region_fill_surfaces_to_svg_debug(const char *name) const
BoundingBox get_extents(const LayerRegion &layer_region)
{
BoundingBox bbox;
if (!layer_region.slices.surfaces.empty()) {
bbox = get_extents(layer_region.slices.surfaces.front());
for (auto it = layer_region.slices.surfaces.cbegin() + 1; it != layer_region.slices.surfaces.cend(); ++it)
if (! layer_region.slices().empty()) {
bbox = get_extents(layer_region.slices().surfaces.front());
for (auto it = layer_region.slices().surfaces.cbegin() + 1; it != layer_region.slices().surfaces.cend(); ++ it)
bbox.merge(get_extents(*it));
}
return bbox;

View File

@ -2,10 +2,13 @@
#define slic3r_Layer_hpp_
#include "libslic3r.h"
#include "BoundingBox.hpp"
#include "Flow.hpp"
#include "SurfaceCollection.hpp"
#include "ExtrusionEntityCollection.hpp"
#include <boost/container/small_vector.hpp>
namespace Slic3r {
class ExPolygon;
@ -25,50 +28,109 @@ namespace FillLightning {
class Generator;
};
namespace FillLightning {
class Generator;
// Range of indices, providing support for range based loops.
template<typename T>
class IndexRange
{
public:
IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {}
IndexRange() = default;
// Just a bare minimum functionality iterator required by range-for loop.
class Iterator {
public:
T operator*() const { return m_idx; }
bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; }
void operator++() { ++ m_idx; }
private:
friend class IndexRange<T>;
Iterator(T idx) : m_idx(idx) {}
T m_idx;
};
Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); };
Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); };
bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; }
T size() const { assert(m_begin <= m_end); return m_end - m_begin; }
private:
// Index of the first extrusion in LayerRegion.
T m_begin { 0 };
// Index of the last extrusion in LayerRegion.
T m_end { 0 };
};
using ExtrusionRange = IndexRange<uint32_t>;
using ExPolygonRange = IndexRange<uint32_t>;
// Range of extrusions, referencing the source region by an index.
class LayerExtrusionRange : public ExtrusionRange
{
public:
LayerExtrusionRange(uint32_t iregion, ExtrusionRange extrusion_range) : m_region(iregion), ExtrusionRange(extrusion_range) {}
LayerExtrusionRange() = default;
// Index of LayerRegion in Layer.
uint32_t region() const { return m_region; };
private:
// Index of LayerRegion in Layer.
uint32_t m_region { 0 };
};
// One LayerIsland may be filled with solid fill, sparse fill, top / bottom fill.
static constexpr const size_t LayerExtrusionRangesStaticSize = 3;
using LayerExtrusionRanges =
#ifdef NDEBUG
// To reduce memory allocation in release mode.
boost::container::small_vector<LayerExtrusionRange, LayerExtrusionRangesStaticSize>;
#else // NDEBUG
// To ease debugging.
std::vector<LayerExtrusionRange>;
#endif // NDEBUG
class LayerRegion
{
public:
Layer* layer() { return m_layer; }
const Layer* layer() const { return m_layer; }
const PrintRegion& region() const { return *m_region; }
[[nodiscard]] Layer* layer() { return m_layer; }
[[nodiscard]] const Layer* layer() const { return m_layer; }
[[nodiscard]] const PrintRegion& region() const { return *m_region; }
// collection of surfaces generated by slicing the original geometry
// divided by type top/bottom/internal
SurfaceCollection slices;
// Backed up slices before they are split into top/bottom/internal.
// Only backed up for multi-region layers or layers with elephant foot compensation.
//FIXME Review whether not to simplify the code by keeping the raw_slices all the time.
ExPolygons raw_slices;
[[nodiscard]] const SurfaceCollection& slices() const { return m_slices; }
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
// and for re-starting of infills.
[[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; }
// and their bounding boxes
[[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; }
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
// Not used for a plain single material print with no infill modifiers.
[[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; }
// and their bounding boxes
[[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; }
// collection of surfaces generated by slicing the original geometry
// divided by type top/bottom/internal
[[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; }
// collection of extrusion paths/loops filling gaps
// These fills are generated by the perimeter generator.
// They are not printed on their own, but they are copied to this->fills during infill generation.
ExtrusionEntityCollection thin_fills;
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
// and for re-starting of infills.
ExPolygons fill_expolygons;
// collection of surfaces for infill generation
SurfaceCollection fill_surfaces;
// collection of expolygons representing the bridged areas (thus not
// needing support material)
// Polygons bridged;
[[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; }
// collection of polylines representing the unsupported bridge edges
Polylines unsupported_bridge_edges;
[[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; }
// ordered collection of extrusion paths/loops to build all perimeters
// (this collection contains only ExtrusionEntityCollection objects)
ExtrusionEntityCollection perimeters;
[[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; }
// ordered collection of extrusion paths to fill surfaces
// (this collection contains only ExtrusionEntityCollection objects)
ExtrusionEntityCollection fills;
[[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; }
Flow flow(FlowRole role) const;
Flow flow(FlowRole role, double layer_height) const;
@ -76,7 +138,17 @@ public:
void slices_to_fill_surfaces_clipped();
void prepare_fill_surfaces();
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces);
// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices.
void make_perimeters(
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
const SurfaceCollection &slices,
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
// newly created extrusions stored at this LayerRegion.
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
// All fill areas produced for all input slices above.
ExPolygons &fill_expolygons,
// Ranges of fill areas above per input slice.
std::vector<ExPolygonRange> &fill_expolygons_ranges);
void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered);
double infill_area_threshold() const;
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
@ -92,20 +164,149 @@ public:
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
// Is there any valid extrusion assigned to this LayerRegion?
bool has_extrusions() const { return ! this->perimeters.entities.empty() || ! this->fills.entities.empty(); }
bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); }
protected:
friend class Layer;
friend class PrintObject;
LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {}
~LayerRegion() {}
~LayerRegion() = default;
private:
// Modifying m_slices
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
template<typename ThrowOnCancel>
friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel);
Layer *m_layer;
const PrintRegion *m_region;
// Backed up slices before they are split into top/bottom/internal.
// Only backed up for multi-region layers or layers with elephant foot compensation.
//FIXME Review whether not to simplify the code by keeping the raw_slices all the time.
ExPolygons m_raw_slices;
//FIXME make m_slices public for unit tests
public:
// collection of surfaces generated by slicing the original geometry
// divided by type top/bottom/internal
SurfaceCollection m_slices;
private:
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
// and for re-starting of infills.
ExPolygons m_fill_expolygons;
// and their bounding boxes
BoundingBoxes m_fill_expolygons_bboxes;
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
// Not used for a plain single material print with no infill modifiers.
ExPolygons m_fill_expolygons_composite;
// and their bounding boxes
BoundingBoxes m_fill_expolygons_composite_bboxes;
// Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons.
SurfaceCollection m_fill_surfaces;
// Collection of extrusion paths/loops filling gaps
// These fills are generated by the perimeter generator.
// They are not printed on their own, but they are copied to this->fills during infill generation.
ExtrusionEntityCollection m_thin_fills;
// collection of polylines representing the unsupported bridge edges
Polylines m_unsupported_bridge_edges;
// ordered collection of extrusion paths/loops to build all perimeters
// (this collection contains only ExtrusionEntityCollection objects)
ExtrusionEntityCollection m_perimeters;
// ordered collection of extrusion paths to fill surfaces
// (this collection contains only ExtrusionEntityCollection objects)
ExtrusionEntityCollection m_fills;
// collection of expolygons representing the bridged areas (thus not
// needing support material)
// Polygons bridged;
};
// LayerSlice contains one or more LayerIsland objects,
// each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters
// and one or multiple
struct LayerIsland
{
private:
friend class Layer;
static constexpr const uint32_t fill_region_composite_id = std::numeric_limits<uint32_t>::max();
public:
// Perimeter extrusions in LayerRegion belonging to this island.
LayerExtrusionRange perimeters;
// Thin fills of the same region as perimeters. Generated by classic perimeter generator, while Arachne puts them into perimeters.
ExtrusionRange thin_fills;
// Infill + gapfill extrusions in LayerRegion belonging to this island.
LayerExtrusionRanges fills;
// Region that is to be filled with the fills above (thin fills, regular fills).
// Pointing to either LayerRegion::fill_expolygons() or LayerRegion::fill_expolygons_composite()
// based on this->fill_expolygons_composite() flag.
ExPolygonRange fill_expolygons;
// Index of LayerRegion with LayerRegion::fill_expolygons() if not fill_expolygons_composite().
uint32_t fill_region_id;
bool fill_expolygons_composite() const { return this->fill_region_id == fill_region_composite_id; }
// Centroid of this island used for path planning.
// Point centroid;
bool has_extrusions() const { return ! this->perimeters.empty() || ! this->fills.empty(); }
void add_fill_range(const LayerExtrusionRange &new_fill_range) {
// Compress ranges.
if (! this->fills.empty() && this->fills.back().region() == new_fill_range.region() && *this->fills.back().end() == *new_fill_range.begin())
this->fills.back() = { new_fill_range.region(), { *this->fills.back().begin(), *new_fill_range.end() } };
else
this->fills.push_back(new_fill_range);
}
};
static constexpr const size_t LayerIslandsStaticSize = 1;
using LayerIslands =
#ifdef NDEBUG
// To reduce memory allocation in release mode.
boost::container::small_vector<LayerIsland, LayerIslandsStaticSize>;
#else // NDEBUG
// To ease debugging.
std::vector<LayerIsland>;
#endif // NDEBUG
// One connected island of a layer. LayerSlice may consist of one or more LayerIslands.
struct LayerSlice
{
struct Link {
int32_t slice_idx;
float area;
};
static constexpr const size_t LinksStaticSize = 4;
using Links =
#ifdef NDEBUG
// To reduce memory allocation in release mode.
boost::container::small_vector<Link, LinksStaticSize>;
#else // NDEBUG
// To ease debugging.
std::vector<Link>;
#endif // NDEBUG
BoundingBox bbox;
Links overlaps_above;
Links overlaps_below;
// One island for each region or region set that generates its own perimeters.
// For multi-material prints or prints with regions of different perimeter parameters,
// a LayerSlice may be split into multiple LayerIslands.
// For most prints there will be just one island.
LayerIslands islands;
bool has_extrusions() const { for (const LayerIsland &island : islands) if (island.has_extrusions()) return true; return false; }
};
using LayerSlices = std::vector<LayerSlice>;
class Layer
{
public:
@ -135,7 +336,7 @@ public:
// These lslices are also used to detect overhangs and overlaps between successive layers, therefore it is important
// that the 1st lslice is not compensated by the Elephant foot compensation algorithm.
ExPolygons lslices;
std::vector<BoundingBox> lslices_bboxes;
LayerSlices lslices_ex;
size_t region_count() const { return m_regions.size(); }
const LayerRegion* get_region(int idx) const { return m_regions[idx]; }
@ -145,6 +346,8 @@ public:
// Test whether whether there are any slices assigned to this layer.
bool empty() const;
void make_slices();
// After creating the slices on all layers, chain the islands overlapping in Z.
static void build_up_down_graph(Layer &below, Layer &above);
// Backup and restore raw sliced regions if needed.
//FIXME Review whether not to simplify the code by keeping the raw_slices all the time.
void backup_untyped_slices();
@ -154,11 +357,11 @@ public:
// Slices merged into islands, to be used by the elephant foot compensation to trim the individual surfaces with the shrunk merged slices.
ExPolygons merged(float offset) const;
template <class T> bool any_internal_region_slice_contains(const T &item) const {
for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_internal_contains(item)) return true;
for (const LayerRegion *layerm : m_regions) if (layerm->slices().any_internal_contains(item)) return true;
return false;
}
template <class T> bool any_bottom_region_slice_contains(const T &item) const {
for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_bottom_contains(item)) return true;
for (const LayerRegion *layerm : m_regions) if (layerm->slices().any_bottom_contains(item)) return true;
return false;
}
void make_perimeters();
@ -175,6 +378,7 @@ public:
// Is there any valid extrusion assigned to this LayerRegion?
virtual bool has_extrusions() const { for (auto layerm : m_regions) if (layerm->has_extrusions()) return true; return false; }
// virtual bool has_extrusions() const { for (const LayerSlice &lslice : lslices_ex) if (lslice.has_extrusions()) return true; return false; }
protected:
friend class PrintObject;
@ -186,8 +390,26 @@ protected:
slice_z(slice_z), print_z(print_z), height(height),
m_id(id), m_object(object) {}
virtual ~Layer();
// Clear fill extrusions, remove them from layer islands.
void clear_fills();
private:
void sort_perimeters_into_islands(
// Slices for which perimeters and fill_expolygons were just created.
// The slices may have been created by merging multiple source slices with the same perimeter parameters.
const SurfaceCollection &slices,
// Region where the perimeters, gap fills and fill expolygons are stored.
const uint32_t region_id,
// Perimeters and gap fills produced by the perimeter generator for the slices,
// sorted by the source slices.
const std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
// Fill expolygons produced for all source slices above.
ExPolygons &&fill_expolygons,
// Fill expolygon ranges sorted by the source slices.
const std::vector<ExPolygonRange> &fill_expolygons_ranges,
// If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces.
const std::vector<uint32_t> &layer_region_ids);
// Sequential index of layer, 0-based, offsetted by number of raft layers.
size_t m_id;
PrintObject *m_object;
@ -201,7 +423,7 @@ public:
// Used to suppress retraction if moving for a support extrusion over these support_islands.
ExPolygons support_islands;
// Slightly inflated bounding boxes of the above, for faster intersection query.
std::vector<BoundingBox> support_islands_bboxes;
BoundingBoxes support_islands_bboxes;
// Extrusion paths for the support base and for the support interface and contacts.
ExtrusionEntityCollection support_fills;

View File

@ -44,30 +44,41 @@ Flow LayerRegion::bridging_flow(FlowRole role) const
}
}
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
// Fill in layerm->fill_surfaces by trimming the layerm->slices by layerm->fill_expolygons.
void LayerRegion::slices_to_fill_surfaces_clipped()
{
// Note: this method should be idempotent, but fill_surfaces gets modified
// in place. However we're now only using its boundaries (which are invariant)
// so we're safe. This guarantees idempotence of prepare_infill() also in case
// that combine_infill() turns some fill_surface into VOID surfaces.
// Collect polygons per surface type.
std::array<SurfacesPtr, size_t(stCount)> by_surface;
for (Surface &surface : this->slices.surfaces)
std::array<std::vector<const Surface*>, size_t(stCount)> by_surface;
for (const Surface &surface : this->slices())
by_surface[size_t(surface.surface_type)].emplace_back(&surface);
// Trim surfaces by the fill_boundaries.
this->fill_surfaces.surfaces.clear();
m_fill_surfaces.surfaces.clear();
for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) {
const SurfacesPtr &this_surfaces = by_surface[surface_type];
const std::vector<const Surface*> &this_surfaces = by_surface[surface_type];
if (! this_surfaces.empty())
this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type));
m_fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons()), SurfaceType(surface_type));
}
}
void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices.
void LayerRegion::make_perimeters(
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
const SurfaceCollection &slices,
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
// newly created extrusions stored at this LayerRegion.
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
// All fill areas produced for all input slices above.
ExPolygons &fill_expolygons,
// Ranges of fill areas above per input slice.
std::vector<ExPolygonRange> &fill_expolygons_ranges)
{
this->perimeters.clear();
this->thin_fills.clear();
m_perimeters.clear();
m_thin_fills.clear();
perimeter_and_gapfill_ranges.reserve(perimeter_and_gapfill_ranges.size() + slices.size());
// There may be more expolygons produced per slice, thus this reserve is conservative.
fill_expolygons.reserve(fill_expolygons.size() + slices.size());
fill_expolygons_ranges.reserve(fill_expolygons_ranges.size() + slices.size());
const PrintConfig &print_config = this->layer()->object()->print()->config();
const PrintRegionConfig &region_config = this->region().config();
@ -77,35 +88,55 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec
(this->layer()->id() >= size_t(region_config.bottom_solid_layers.value) &&
this->layer()->print_z >= region_config.bottom_solid_min_thickness - EPSILON);
PerimeterGenerator g(
// input:
&slices,
PerimeterGenerator::Parameters params(
this->layer()->height,
int(this->layer()->id()),
this->flow(frPerimeter),
&region_config,
&this->layer()->object()->config(),
&print_config,
spiral_vase,
// output:
&this->perimeters,
&this->thin_fills,
fill_surfaces
this->flow(frExternalPerimeter),
this->bridging_flow(frPerimeter),
this->flow(frSolidInfill),
region_config,
this->layer()->object()->config(),
print_config,
spiral_vase
);
if (this->layer()->lower_layer != nullptr)
// Cummulative sum of polygons over all the regions.
g.lower_slices = &this->layer()->lower_layer->lslices;
g.layer_id = (int)this->layer()->id();
g.ext_perimeter_flow = this->flow(frExternalPerimeter);
g.overhang_flow = this->bridging_flow(frPerimeter);
g.solid_infill_flow = this->flow(frSolidInfill);
const ExPolygons *lower_slices = this->layer()->lower_layer ? &this->layer()->lower_layer->lslices : nullptr;
// Cache for offsetted lower_slices
Polygons lower_layer_polygons_cache;
for (const Surface &surface : slices) {
auto perimeters_begin = uint32_t(m_perimeters.size());
auto gap_fills_begin = uint32_t(m_thin_fills.size());
auto fill_expolygons_begin = uint32_t(fill_expolygons.size());
if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase)
g.process_arachne();
PerimeterGenerator::process_arachne(
// input:
params,
surface,
lower_slices,
lower_layer_polygons_cache,
// output:
m_perimeters,
m_thin_fills,
fill_expolygons);
else
g.process_classic();
PerimeterGenerator::process_classic(
// input:
params,
surface,
lower_slices,
lower_layer_polygons_cache,
// output:
m_perimeters,
m_thin_fills,
fill_expolygons);
perimeter_and_gapfill_ranges.emplace_back(
ExtrusionRange{ perimeters_begin, uint32_t(m_perimeters.size()) },
ExtrusionRange{ gap_fills_begin, uint32_t(m_thin_fills.size()) });
fill_expolygons_ranges.emplace_back(ExtrusionRange{ fill_expolygons_begin, uint32_t(fill_expolygons.size()) });
}
}
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
@ -132,7 +163,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// Internal surfaces, not grown.
Surfaces internal;
// Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed.
Polygons fill_boundaries = to_polygons(this->fill_expolygons);
Polygons fill_boundaries = to_polygons(this->fill_expolygons());
Polygons lower_layer_covered_tmp;
// Collect top surfaces and internal surfaces.
@ -142,7 +173,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
{
// Voids are sparse infills if infill rate is zero.
Polygons voids;
for (const Surface &surface : this->fill_surfaces.surfaces) {
for (const Surface &surface : this->fill_surfaces()) {
if (surface.is_top()) {
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
// This gives the priority to bottom surfaces.
@ -314,7 +345,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
bridges[idx_last].bridge_angle = bd.angle;
if (this->layer()->object()->has_support()) {
// polygons_append(this->bridged, bd.coverage());
append(this->unsupported_bridge_edges, bd.unsupported_edges());
append(m_unsupported_bridge_edges, bd.unsupported_edges());
}
} else if (custom_angle > 0) {
// Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in
@ -388,7 +419,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
surfaces_append(new_surfaces, std::move(new_expolys), s1);
}
this->fill_surfaces.surfaces = std::move(new_surfaces);
m_fill_surfaces.surfaces = std::move(new_surfaces);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final");
@ -412,12 +443,12 @@ void LayerRegion::prepare_fill_surfaces()
// For Lightning infill, infill_only_where_needed is ignored because both
// do a similar thing, and their combination doesn't make much sense.
if (! spiral_vase && this->region().config().top_solid_layers == 0) {
for (Surface &surface : this->fill_surfaces.surfaces)
for (Surface &surface : m_fill_surfaces)
if (surface.is_top())
surface.surface_type = this->layer()->object()->config().infill_only_where_needed && this->region().config().fill_pattern != ipLightning ? stInternalVoid : stInternal;
}
if (this->region().config().bottom_solid_layers == 0) {
for (Surface &surface : this->fill_surfaces.surfaces)
for (Surface &surface : m_fill_surfaces)
if (surface.is_bottom()) // (surface.surface_type == stBottom)
surface.surface_type = stInternal;
}
@ -426,7 +457,7 @@ void LayerRegion::prepare_fill_surfaces()
if (! spiral_vase && this->region().config().fill_density.value > 0) {
// scaling an area requires two calls!
double min_area = scale_(scale_(this->region().config().solid_infill_below_area.value));
for (Surface &surface : this->fill_surfaces.surfaces)
for (Surface &surface : m_fill_surfaces)
if (surface.surface_type == stInternal && surface.area() <= min_area)
surface.surface_type = stInternalSolid;
}
@ -446,38 +477,38 @@ double LayerRegion::infill_area_threshold() const
void LayerRegion::trim_surfaces(const Polygons &trimming_polygons)
{
#ifndef NDEBUG
for (const Surface &surface : this->slices.surfaces)
for (const Surface &surface : this->slices())
assert(surface.surface_type == stInternal);
#endif /* NDEBUG */
this->slices.set(intersection_ex(this->slices.surfaces, trimming_polygons), stInternal);
m_slices.set(intersection_ex(this->slices().surfaces, trimming_polygons), stInternal);
}
void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons)
{
#ifndef NDEBUG
for (const Surface &surface : this->slices.surfaces)
for (const Surface &surface : this->slices())
assert(surface.surface_type == stInternal);
#endif /* NDEBUG */
Polygons tmp = intersection(this->slices.surfaces, trimming_polygons);
append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step)));
this->slices.set(union_ex(tmp), stInternal);
Polygons tmp = intersection(this->slices().surfaces, trimming_polygons);
append(tmp, diff(this->slices().surfaces, opening(this->slices().surfaces, elephant_foot_compensation_perimeter_step)));
m_slices.set(union_ex(tmp), stInternal);
}
void LayerRegion::export_region_slices_to_svg(const char *path) const
{
BoundingBox bbox;
for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface)
bbox.merge(get_extents(surface->expolygon));
for (const Surface &surface : this->slices())
bbox.merge(get_extents(surface.expolygon));
Point legend_size = export_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
SVG svg(path, bbox);
const float transparency = 0.5f;
for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface)
svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency);
for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface)
svg.draw(surface->expolygon.lines(), surface_type_to_color_name(surface->surface_type));
for (const Surface &surface : this->slices())
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
for (const Surface &surface : this->fill_surfaces())
svg.draw(surface.expolygon.lines(), surface_type_to_color_name(surface.surface_type));
export_surface_type_legend_to_svg(svg, legend_pos);
svg.Close();
}
@ -493,15 +524,15 @@ void LayerRegion::export_region_slices_to_svg_debug(const char *name) const
void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const
{
BoundingBox bbox;
for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface)
bbox.merge(get_extents(surface->expolygon));
for (const Surface &surface : this->fill_surfaces())
bbox.merge(get_extents(surface.expolygon));
Point legend_size = export_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
SVG svg(path, bbox);
const float transparency = 0.5f;
for (const Surface &surface : this->fill_surfaces.surfaces) {
for (const Surface &surface : this->fill_surfaces()) {
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05));
}

View File

@ -54,11 +54,11 @@ double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, V
// We find projection of this point onto the line.
// It falls where t = [(this-a) . (b-a)] / |b-a|^2
const double t = va.dot(v) / l2;
if (t < 0.0) {
if (t <= 0.0) {
// beyond the 'a' end of the segment
*nearest_point = get_a(line);
return va.squaredNorm();
} else if (t > 1.0) {
} else if (t >= 1.0) {
// beyond the 'b' end of the segment
*nearest_point = get_b(line);
return (point - get_b(line)).template cast<double>().squaredNorm();

View File

@ -6,6 +6,8 @@
#include "libslic3r/SurfaceMesh.hpp"
#include <numeric>
#include <numeric>
namespace Slic3r {
namespace Measure {

View File

@ -713,7 +713,7 @@ struct MMU_Graph
[[nodiscard]] const Vec2d &point_double() const { return m_point_double; }
[[nodiscard]] const Point &point() const { return m_point; }
bool operator==(const CPoint &rhs) const { return this->m_point_double == rhs.m_point_double && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; }
bool operator==(const CPoint &rhs) const { return m_point_double == rhs.m_point_double && m_contour_idx == rhs.m_contour_idx && m_point_idx == rhs.m_point_idx; }
};
struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }};
typedef ClosestPointInRadiusLookup<CPoint, CPointAccessor> CPointLookupType;
@ -1697,7 +1697,7 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
throw_on_cancel_callback();
ExPolygons ex_polygons;
for (LayerRegion *region : layers[layer_idx]->regions())
for (const Surface &surface : region->slices.surfaces)
for (const Surface &surface : region->slices())
Slic3r::append(ex_polygons, offset_ex(surface.expolygon, float(10 * SCALED_EPSILON)));
// All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON
// to ensure that very close polygons will be merged.

View File

@ -45,16 +45,6 @@ void MultiPoint::rotate(double angle, const Point &center)
}
}
double MultiPoint::length() const
{
const Lines& lines = this->lines();
double len = 0;
for (auto it = lines.cbegin(); it != lines.cend(); ++it) {
len += it->length();
}
return len;
}
int MultiPoint::find_point(const Point &point) const
{
for (const Point &pt : this->points)
@ -81,12 +71,6 @@ int MultiPoint::find_point(const Point &point, double scaled_epsilon) const
return dist2_min < eps2 ? idx_min : -1;
}
bool MultiPoint::has_boundary_point(const Point &point) const
{
double dist = (point.projection_onto(*this) - point).cast<double>().norm();
return dist < SCALED_EPSILON;
}
BoundingBox MultiPoint::bounding_box() const
{
return BoundingBox(this->points);
@ -119,49 +103,6 @@ bool MultiPoint::remove_duplicate_points()
return false;
}
bool MultiPoint::intersection(const Line& line, Point* intersection) const
{
Lines lines = this->lines();
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {
if (it->intersection(line, intersection)) return true;
}
return false;
}
bool MultiPoint::first_intersection(const Line& line, Point* intersection) const
{
bool found = false;
double dmin = 0.;
for (const Line &l : this->lines()) {
Point ip;
if (l.intersection(line, &ip)) {
if (! found) {
found = true;
dmin = (line.a - ip).cast<double>().norm();
*intersection = ip;
} else {
double d = (line.a - ip).cast<double>().norm();
if (d < dmin) {
dmin = d;
*intersection = ip;
}
}
}
}
return found;
}
bool MultiPoint::intersections(const Line &line, Points *intersections) const
{
size_t intersections_size = intersections->size();
for (const Line &polygon_line : this->lines()) {
Point intersection;
if (polygon_line.intersection(line, &intersection))
intersections->emplace_back(std::move(intersection));
}
return intersections->size() > intersections_size;
}
std::vector<Point> MultiPoint::_douglas_peucker(const std::vector<Point>& pts, const double tolerance)
{
std::vector<Point> result_pts;
@ -363,14 +304,6 @@ void MultiPoint3::translate(const Point& vector)
this->translate(vector(0), vector(1));
}
double MultiPoint3::length() const
{
double len = 0.0;
for (const Line3& line : this->lines())
len += line.length();
return len;
}
BoundingBox3 MultiPoint3::bounding_box() const
{
return BoundingBox3(points);

View File

@ -18,7 +18,6 @@ public:
Points points;
MultiPoint() = default;
virtual ~MultiPoint() = default;
MultiPoint(const MultiPoint &other) : points(other.points) {}
MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {}
MultiPoint(std::initializer_list<Point> list) : points(list) {}
@ -37,11 +36,8 @@ public:
const Point& front() const { return this->points.front(); }
const Point& back() const { return this->points.back(); }
const Point& first_point() const { return this->front(); }
virtual const Point& last_point() const = 0;
virtual Lines lines() const = 0;
size_t size() const { return points.size(); }
bool empty() const { return points.empty(); }
double length() const;
bool is_valid() const { return this->points.size() >= 2; }
// Return index of a polygon point exactly equal to point.
@ -50,7 +46,6 @@ public:
// Return index of the closest point to point closer than scaled_epsilon.
// Return -1 if no such point exists.
int find_point(const Point &point, const double scaled_epsilon) const;
bool has_boundary_point(const Point &point) const;
int closest_point_index(const Point &point) const {
int idx = -1;
if (! this->points.empty()) {
@ -86,10 +81,6 @@ public:
}
}
bool intersection(const Line& line, Point* intersection) const;
bool first_intersection(const Line& line, Point* intersection) const;
bool intersections(const Line &line, Points *intersections) const;
static Points _douglas_peucker(const Points &points, const double tolerance);
static Points visivalingam(const Points& pts, const double& tolerance);
@ -110,8 +101,6 @@ public:
void translate(double x, double y);
void translate(const Point& vector);
virtual Lines3 lines() const = 0;
double length() const;
bool is_valid() const { return this->points.size() >= 2; }
BoundingBox3 bounding_box() const;

View File

@ -53,7 +53,7 @@
namespace Slic3r {
ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance)
ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance)
{
ExtrusionMultiPath multi_path;
ExtrusionPath path(role);
@ -146,14 +146,14 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyl
return multi_path;
}
static void variable_width(const ThickPolylines &polylines, ExtrusionRole role, const Flow &flow, std::vector<ExtrusionEntity *> &out)
static void variable_width_classic(const ThickPolylines &polylines, ExtrusionRole role, const Flow &flow, std::vector<ExtrusionEntity *> &out)
{
// This value determines granularity of adaptive width, as G-code does not allow
// variable extrusion within a single move; this value shall only affect the amount
// of segments, and any pruning shall be performed before we apply this tolerance.
const auto tolerance = float(scale_(0.05));
for (const ThickPolyline &p : polylines) {
ExtrusionMultiPath multi_path = thick_polyline_to_multi_path(p, role, flow, tolerance, tolerance);
ExtrusionMultiPath multi_path = PerimeterGenerator::thick_polyline_to_multi_path(p, role, flow, tolerance, tolerance);
// Append paths to collection.
if (!multi_path.paths.empty()) {
for (auto it = std::next(multi_path.paths.begin()); it != multi_path.paths.end(); ++it) {
@ -189,7 +189,15 @@ public:
// External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
bool is_external() const { return this->depth == 0; }
// An island, which may have holes, but it does not have another internal island.
bool is_internal_contour() const;
bool is_internal_contour() const {
// An internal contour is a contour containing no other contours
if (! this->is_contour)
return false;
for (const PerimeterGeneratorLoop &loop : this->children)
if (loop.is_contour)
return false;
return true;
}
};
// Thanks Cura developers for this function.
@ -275,7 +283,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy
using PerimeterGeneratorLoops = std::vector<PerimeterGeneratorLoop>;
static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls)
static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator::Parameters &params, const Polygons &lower_slices_polygons_cache, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls)
{
// loops is an arrayref of ::Loop objects
// turn each one into an ExtrusionLoop object
@ -301,30 +309,33 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
const Polygon &polygon = loop.fuzzify ? fuzzified : loop.polygon;
if (loop.fuzzify) {
fuzzified = loop.polygon;
fuzzy_polygon(fuzzified, scaled<float>(perimeter_generator.config->fuzzy_skin_thickness.value), scaled<float>(perimeter_generator.config->fuzzy_skin_point_dist.value));
fuzzy_polygon(fuzzified, scaled<float>(params.config.fuzzy_skin_thickness.value), scaled<float>(params.config.fuzzy_skin_point_dist.value));
}
if (perimeter_generator.config->overhangs && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers
&& ! ((perimeter_generator.object_config->support_material || perimeter_generator.object_config->support_material_enforce_layers > 0) &&
perimeter_generator.object_config->support_material_contact_distance.value == 0)) {
if (params.config.overhangs && params.layer_id > params.object_config.raft_layers
&& ! ((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) &&
params.object_config.support_material_contact_distance.value == 0)) {
BoundingBox bbox(polygon.points);
bbox.offset(SCALED_EPSILON);
Polygons lower_slices_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons_cache, bbox);
// get non-overhang paths by intersecting this loop with the grown lower slices
extrusion_paths_append(
paths,
intersection_pl({ polygon }, perimeter_generator.lower_slices_polygons()),
intersection_pl({ polygon }, lower_slices_polygons_clipped),
role,
is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(),
is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(),
(float)perimeter_generator.layer_height);
is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(),
float(params.layer_height));
// get overhang paths by checking what parts of this loop fall
// outside the grown lower slices (thus where the distance between
// the loop centerline and original lower slices is >= half nozzle diameter
extrusion_paths_append(
paths,
diff_pl({ polygon }, perimeter_generator.lower_slices_polygons()),
diff_pl({ polygon }, lower_slices_polygons_clipped),
erOverhangPerimeter,
perimeter_generator.mm3_per_mm_overhang(),
perimeter_generator.overhang_flow.width(),
perimeter_generator.overhang_flow.height());
params.mm3_per_mm_overhang,
params.overhang_flow.width(),
params.overhang_flow.height());
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
@ -332,9 +343,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
} else {
ExtrusionPath path(role);
path.polyline = polygon.split_at_first_point();
path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm();
path.width = is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width();
path.height = (float)perimeter_generator.layer_height;
path.mm3_per_mm = is_external ? params.ext_mm3_per_mm : params.mm3_per_mm;
path.width = is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width();
path.height = float(params.layer_height);
paths.push_back(path);
}
@ -343,7 +354,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
// Append thin walls to the nearest-neighbor search (only for first iteration)
if (! thin_walls.empty()) {
variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities);
variable_width_classic(thin_walls, erExternalPerimeter, params.ext_perimeter_flow, coll.entities);
thin_walls.clear();
}
@ -363,7 +374,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
} else {
const PerimeterGeneratorLoop &loop = loops[idx.first];
assert(thin_walls.empty());
ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls);
ExtrusionEntityCollection children = traverse_loops_classic(params, lower_slices_polygons_cache, loop.children, thin_walls);
out.entities.reserve(out.entities.size() + children.entities.size() + 1);
ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]);
coll.entities[idx.first] = nullptr;
@ -429,8 +440,8 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con
Point prev(subject.front().x(), subject.front().y());
for (auto it = std::next(subject.begin()); it != subject.end(); ++it) {
Point curr(it->x(), it->y());
Point projected_pt = pt.projection_onto(Line(prev, curr));
if (double dist_sqr = (projected_pt - pt).cast<double>().squaredNorm(); dist_sqr < dist_sqr_min) {
Point projected_pt;
if (double dist_sqr = line_alg::distance_to_squared(Line(prev, curr), pt, &projected_pt); dist_sqr < dist_sqr_min) {
dist_sqr_min = dist_sqr;
projected_pt_min = projected_pt;
it_min = std::prev(it);
@ -468,7 +479,7 @@ struct PerimeterGeneratorArachneExtrusion
bool fuzzify = false;
};
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &perimeter_generator, std::vector<PerimeterGeneratorArachneExtrusion> &pg_extrusions)
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters &params, const Polygons &lower_slices_polygons_cache, std::vector<PerimeterGeneratorArachneExtrusion> &pg_extrusions)
{
ExtrusionEntityCollection extrusion_coll;
for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) {
@ -480,38 +491,49 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &p
ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter;
if (pg_extrusion.fuzzify)
fuzzy_extrusion_line(*extrusion, scaled<float>(perimeter_generator.config->fuzzy_skin_thickness.value), scaled<float>(perimeter_generator.config->fuzzy_skin_point_dist.value));
fuzzy_extrusion_line(*extrusion, scaled<float>(params.config.fuzzy_skin_thickness.value), scaled<float>(params.config.fuzzy_skin_point_dist.value));
ExtrusionPaths paths;
// detect overhanging/bridging perimeters
if (perimeter_generator.config->overhangs && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers
&& ! ((perimeter_generator.object_config->support_material || perimeter_generator.object_config->support_material_enforce_layers > 0) &&
perimeter_generator.object_config->support_material_contact_distance.value == 0)) {
if (params.config.overhangs && params.layer_id > params.object_config.raft_layers
&& ! ((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) &&
params.object_config.support_material_contact_distance.value == 0)) {
ClipperLib_Z::Path extrusion_path;
extrusion_path.reserve(extrusion->size());
for (const Arachne::ExtrusionJunction &ej : extrusion->junctions)
BoundingBox extrusion_path_bbox;
for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) {
extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w);
extrusion_path_bbox.merge(Point{ej.p.x(), ej.p.y()});
}
ClipperLib_Z::Paths lower_slices_paths;
lower_slices_paths.reserve(perimeter_generator.lower_slices_polygons().size());
for (const Polygon &poly : perimeter_generator.lower_slices_polygons()) {
lower_slices_paths.reserve(lower_slices_polygons_cache.size());
{
Points clipped;
extrusion_path_bbox.offset(SCALED_EPSILON);
for (const Polygon &poly : lower_slices_polygons_cache) {
clipped.clear();
ClipperUtils::clip_clipper_polygon_with_subject_bbox(poly.points, extrusion_path_bbox, clipped);
if (! clipped.empty()) {
lower_slices_paths.emplace_back();
ClipperLib_Z::Path &out = lower_slices_paths.back();
out.reserve(poly.points.size());
for (const Point &pt : poly.points)
out.reserve(clipped.size());
for (const Point &pt : clipped)
out.emplace_back(pt.x(), pt.y(), 0);
}
}
}
// get non-overhang paths by intersecting this loop with the grown lower slices
extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctIntersection), role,
is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow);
is_external ? params.ext_perimeter_flow : params.perimeter_flow);
// get overhang paths by checking what parts of this loop fall
// outside the grown lower slices (thus where the distance between
// the loop centerline and original lower slices is >= half nozzle diameter
extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctDifference), erOverhangPerimeter,
perimeter_generator.overhang_flow);
params.overhang_flow);
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
@ -551,7 +573,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &p
chain_and_reorder_extrusion_paths(paths, &start_point);
}
} else {
extrusion_paths_append(paths, *extrusion, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow);
extrusion_paths_append(paths, *extrusion, role, is_external ? params.ext_perimeter_flow : params.perimeter_flow);
}
// Append paths to collection.
@ -991,42 +1013,47 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
// Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper
// "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling"
void PerimeterGenerator::process_arachne()
void PerimeterGenerator::process_arachne(
// Inputs:
const Parameters &params,
const Surface &surface,
const ExPolygons *lower_slices,
// Cache:
Polygons &lower_slices_polygons_cache,
// Output:
// Loops with the external thin walls
ExtrusionEntityCollection &out_loops,
// Gaps without the thin walls
ExtrusionEntityCollection & /* out_gap_fill */,
// Infills without the gap fills
ExPolygons &out_fill_expolygons)
{
// other perimeters
m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing();
// external perimeters
m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
coord_t ext_perimeter_spacing2 = scaled<coord_t>(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing()));
// overhang perimeters
m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
coord_t ext_perimeter_width = params.ext_perimeter_flow.scaled_width();
coord_t ext_perimeter_spacing = params.ext_perimeter_flow.scaled_spacing();
coord_t ext_perimeter_spacing2 = scaled<coord_t>(0.5f * (params.ext_perimeter_flow.spacing() + params.perimeter_flow.spacing()));
// solid infill
coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing();
coord_t solid_infill_spacing = params.solid_infill_flow.scaled_spacing();
// prepare grown lower layer slices for overhang detection
if (this->lower_slices != nullptr && this->config->overhangs) {
if (params.config.overhangs && lower_slices != nullptr && lower_slices_polygons_cache.empty()) {
// We consider overhang any part where the entire nozzle diameter is not supported by the
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
// in the current layer
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1);
m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2)));
double nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder-1);
lower_slices_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter/2)));
}
// we need to process each island separately because we might have different
// extra perimeters for each one
for (const Surface &surface : this->slices->surfaces) {
// detect how many perimeters must be generated for this island
int loop_number = this->config->perimeters + surface.extra_perimeters - 1; // 0-indexed loops
ExPolygons last = offset_ex(surface.expolygon.simplify_p(m_scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops
ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
Polygons last_p = to_polygons(last);
Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, layer_height, *this->object_config, *this->print_config);
Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config);
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
loop_number = int(perimeters.size()) - 1;
@ -1052,7 +1079,7 @@ void PerimeterGenerator::process_arachne()
int end_perimeter = -1;
int direction = -1;
if (this->config->external_perimeters_first) {
if (params.config.external_perimeters_first) {
start_perimeter = 0;
end_perimeter = int(perimeters.size());
direction = 1;
@ -1073,7 +1100,7 @@ void PerimeterGenerator::process_arachne()
for (size_t idx = 0; idx < all_extrusions.size(); idx++)
map_extrusion_to_idx.emplace(all_extrusions[idx], idx);
auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, this->config->external_perimeters_first);
auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first);
for (auto [before, after] : extrusions_constrains) {
auto after_it = map_extrusion_to_idx.find(after);
++blocked[after_it->second];
@ -1137,18 +1164,18 @@ void PerimeterGenerator::process_arachne()
}
}
if (this->layer_id > 0 && this->config->fuzzy_skin != FuzzySkinType::None) {
if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) {
std::vector<PerimeterGeneratorArachneExtrusion *> closed_loop_extrusions;
for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions)
if (extrusion.extrusion->inset_idx == 0) {
if (extrusion.extrusion->is_closed && this->config->fuzzy_skin == FuzzySkinType::External) {
if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) {
closed_loop_extrusions.emplace_back(&extrusion);
} else {
extrusion.fuzzify = true;
}
}
if (this->config->fuzzy_skin == FuzzySkinType::External) {
if (params.config.fuzzy_skin == FuzzySkinType::External) {
ClipperLib_Z::Paths loops_paths;
loops_paths.reserve(closed_loop_extrusions.size());
for (const auto &cl_extrusion : closed_loop_extrusions) {
@ -1177,8 +1204,8 @@ void PerimeterGenerator::process_arachne()
}
}
if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions); !extrusion_coll.empty())
this->loops->append(extrusion_coll);
if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty())
out_loops.append(extrusion_coll);
ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour());
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
@ -1197,62 +1224,64 @@ void PerimeterGenerator::process_arachne()
// two or more loops?
perimeter_spacing;
inset = coord_t(scale_(this->config->get_abs_value("infill_overlap", unscale<double>(inset))));
inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(inset))));
Polygons pp;
for (ExPolygon &ex : infill_contour)
ex.simplify_p(m_scaled_resolution, &pp);
ex.simplify_p(params.scaled_resolution, &pp);
// collapse too narrow infill areas
const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
// append infill areas to fill_surfaces
// append infill areas to fill_surfaces
ExPolygons infill_areas = offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.),
ExPolygons infill_areas =
offset2_ex(
union_ex(pp),
float(- min_perimeter_infill_spacing / 2.),
float(inset + min_perimeter_infill_spacing / 2.));
this->fill_surfaces->append(infill_areas, stInternal);
if (this->lower_slices != nullptr && this->config->overhangs && this->config->extra_perimeters_on_overhangs &&
this->config->perimeters > 0 && this->layer_id > this->object_config->raft_layers) {
if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs &&
params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) {
// Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material
auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas,
this->lower_slices_polygons(),
this->overhang_flow, this->m_scaled_resolution,
*this->object_config, *this->print_config);
lower_slices_polygons_cache,
params.overhang_flow, params.scaled_resolution,
params.object_config, params.print_config);
if (!extra_perimeters.empty()) {
ExtrusionEntityCollection *this_islands_perimeters = static_cast<ExtrusionEntityCollection *>(this->loops->entities.back());
ExtrusionEntityCollection new_perimeters{};
new_perimeters.no_sort = this_islands_perimeters->no_sort;
for (const ExtrusionPaths &paths : extra_perimeters) { new_perimeters.append(paths); }
new_perimeters.append(this_islands_perimeters->entities);
this_islands_perimeters->swap(new_perimeters);
SurfaceCollection orig_surfaces = *this->fill_surfaces;
this->fill_surfaces->clear();
for (const auto &surface : orig_surfaces.surfaces) {
auto new_surfaces = diff_ex({surface.expolygon}, filled_area);
this->fill_surfaces->append(new_surfaces, surface);
}
}
}
ExtrusionEntityCollection &this_islands_perimeters = static_cast<ExtrusionEntityCollection&>(*out_loops.entities.back());
ExtrusionEntitiesPtr old_entities;
old_entities.swap(this_islands_perimeters.entities);
for (ExtrusionPaths &paths : extra_perimeters)
this_islands_perimeters.append(std::move(paths));
append(this_islands_perimeters.entities, old_entities);
infill_areas = diff_ex(infill_areas, filled_area);
}
}
void PerimeterGenerator::process_classic()
append(out_fill_expolygons, std::move(infill_areas));
}
void PerimeterGenerator::process_classic(
// Inputs:
const Parameters &params,
const Surface &surface,
const ExPolygons *lower_slices,
// Cache:
Polygons &lower_slices_polygons_cache,
// Output:
// Loops with the external thin walls
ExtrusionEntityCollection &out_loops,
// Gaps without the thin walls
ExtrusionEntityCollection &out_gap_fill,
// Infills without the gap fills
ExPolygons &out_fill_expolygons)
{
// other perimeters
m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
coord_t perimeter_width = this->perimeter_flow.scaled_width();
coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
coord_t perimeter_width = params.perimeter_flow.scaled_width();
coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing();
// external perimeters
m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
coord_t ext_perimeter_spacing2 = scaled<coord_t>(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing()));
// overhang perimeters
m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
coord_t ext_perimeter_width = params.ext_perimeter_flow.scaled_width();
coord_t ext_perimeter_spacing = params.ext_perimeter_flow.scaled_spacing();
coord_t ext_perimeter_spacing2 = scaled<coord_t>(0.5f * (params.ext_perimeter_flow.spacing() + params.perimeter_flow.spacing()));
// solid infill
coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing();
coord_t solid_infill_spacing = params.solid_infill_flow.scaled_spacing();
// Calculate the minimum required spacing between two adjacent traces.
// This should be equal to the nominal flow spacing but we experiment
@ -1266,23 +1295,22 @@ void PerimeterGenerator::process_classic()
// internal flow which is unrelated.
coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
bool has_gap_fill = this->config->gap_fill_enabled.value && this->config->gap_fill_speed.value > 0;
bool has_gap_fill = params.config.gap_fill_enabled.value && params.config.gap_fill_speed.value > 0;
// prepare grown lower layer slices for overhang detection
if (this->lower_slices != NULL && this->config->overhangs) {
if (params.config.overhangs && lower_slices != nullptr && lower_slices_polygons_cache.empty()) {
// We consider overhang any part where the entire nozzle diameter is not supported by the
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
// in the current layer
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1);
m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2)));
double nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder-1);
lower_slices_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter/2)));
}
// we need to process each island separately because we might have different
// extra perimeters for each one
for (const Surface &surface : this->slices->surfaces) {
// detect how many perimeters must be generated for this island
int loop_number = this->config->perimeters + surface.extra_perimeters - 1; // 0-indexed loops
ExPolygons last = union_ex(surface.expolygon.simplify_p(m_scaled_resolution));
int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops
ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution));
ExPolygons gaps;
if (loop_number >= 0) {
// In case no perimeters are to be generated, loop_number will equal to -1.
@ -1296,26 +1324,26 @@ void PerimeterGenerator::process_classic()
if (i == 0) {
// the minimum thickness of a single loop is:
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
offsets = this->config->thin_walls ?
offsets = params.config.thin_walls ?
offset2_ex(
last,
- float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1),
+ float(ext_min_spacing / 2. - 1)) :
offset_ex(last, - float(ext_perimeter_width / 2.));
// look for thin walls
if (this->config->thin_walls) {
if (params.config.thin_walls) {
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
// (actually, something larger than that still may exist due to mitering or other causes)
coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3));
coord_t min_width = coord_t(scale_(params.ext_perimeter_flow.nozzle_diameter() / 3));
ExPolygons expp = opening_ex(
// medial axis requires non-overlapping geometry
diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)),
float(min_width / 2.));
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
for (ExPolygon &ex : expp)
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
ex.medial_axis(min_width, ext_perimeter_width + ext_perimeter_spacing2, &thin_walls);
}
if (m_spiral_vase && offsets.size() > 1) {
if (params.spiral_vase && offsets.size() > 1) {
// Remove all but the largest area polygon.
keep_largest_contour_only(offsets);
}
@ -1323,7 +1351,7 @@ void PerimeterGenerator::process_classic()
//FIXME Is this offset correct if the line width of the inner perimeters differs
// from the line width of the infill?
coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
offsets = this->config->thin_walls ?
offsets = params.config.thin_walls ?
// This path will ensure, that the perimeters do not overfill, as in
// prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters
// excessively, creating gaps, which then need to be filled in by the not very
@ -1356,8 +1384,8 @@ void PerimeterGenerator::process_classic()
break;
}
{
const bool fuzzify_contours = this->config->fuzzy_skin != FuzzySkinType::None && i == 0 && this->layer_id > 0;
const bool fuzzify_holes = fuzzify_contours && this->config->fuzzy_skin == FuzzySkinType::All;
const bool fuzzify_contours = params.config.fuzzy_skin != FuzzySkinType::None && i == 0 && params.layer_id > 0;
const bool fuzzify_holes = fuzzify_contours && params.config.fuzzy_skin == FuzzySkinType::All;
for (const ExPolygon &expolygon : offsets) {
// Outer contour may overlap with an inner contour,
// inner contour may overlap with another inner contour,
@ -1374,7 +1402,7 @@ void PerimeterGenerator::process_classic()
}
}
last = std::move(offsets);
if (i == loop_number && (! has_gap_fill || this->config->fill_density.value == 0)) {
if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) {
// The last run of this loop is executed to collect gaps for gap fill.
// As the gap fill is either disabled or not
break;
@ -1436,16 +1464,16 @@ void PerimeterGenerator::process_classic()
}
}
// at this point, all loops should be in contours[0]
ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls);
ExtrusionEntityCollection entities = traverse_loops_classic(params, lower_slices_polygons_cache, contours.front(), thin_walls);
// if brim will be printed, reverse the order of perimeters so that
// we continue inwards after having finished the brim
// TODO: add test for perimeter order
if (this->config->external_perimeters_first ||
(this->layer_id == 0 && this->object_config->brim_width.value > 0))
if (params.config.external_perimeters_first ||
(params.layer_id == 0 && params.object_config.brim_width.value > 0))
entities.reverse();
// append perimeters for this slice as a collection
if (! entities.empty())
this->loops->append(entities);
out_loops.append(entities);
} // for each loop of an island
// fill gaps
@ -1459,10 +1487,10 @@ void PerimeterGenerator::process_classic()
offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
ThickPolylines polylines;
for (const ExPolygon &ex : gaps_ex)
ex.medial_axis(max, min, &polylines);
ex.medial_axis(min, max, &polylines);
if (! polylines.empty()) {
ExtrusionEntityCollection gap_fill;
variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities);
variable_width_classic(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities);
/* Make sure we don't infill narrow parts that are already gap-filled
(we only consider this surface's gaps to reduce the diff() complexity).
Growing actual extrusions ensures that gaps not filled by medial axis
@ -1472,7 +1500,7 @@ void PerimeterGenerator::process_classic()
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
// therefore it may cover the area, but no the volume.
last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f));
this->gap_fill->append(std::move(gap_fill.entities));
out_gap_fill.append(std::move(gap_fill.entities));
}
}
@ -1489,54 +1517,39 @@ void PerimeterGenerator::process_classic()
perimeter_spacing / 2;
// only apply infill overlap if we actually have one perimeter
if (inset > 0)
inset -= coord_t(scale_(this->config->get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2))));
inset -= coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2))));
// simplify infill contours according to resolution
Polygons pp;
for (ExPolygon &ex : last)
ex.simplify_p(m_scaled_resolution, &pp);
ex.simplify_p(params.scaled_resolution, &pp);
// collapse too narrow infill areas
coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
// append infill areas to fill_surfaces
ExPolygons infill_areas = offset2_ex(union_ex(pp), float(-inset - min_perimeter_infill_spacing / 2.),
ExPolygons infill_areas =
offset2_ex(
union_ex(pp),
float(- inset - min_perimeter_infill_spacing / 2.),
float(min_perimeter_infill_spacing / 2.));
this->fill_surfaces->append(infill_areas, stInternal);
if (this->lower_slices != nullptr && this->config->overhangs && this->config->extra_perimeters_on_overhangs &&
this->config->perimeters > 0 && this->layer_id > this->object_config->raft_layers) {
if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs &&
params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) {
// Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material
auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas,
this->lower_slices_polygons(),
this->overhang_flow, this->m_scaled_resolution,
*this->object_config, *this->print_config);
lower_slices_polygons_cache,
params.overhang_flow, params.scaled_resolution,
params.object_config, params.print_config);
if (!extra_perimeters.empty()) {
ExtrusionEntityCollection *this_islands_perimeters = static_cast<ExtrusionEntityCollection *>(this->loops->entities.back());
ExtrusionEntityCollection new_perimeters{};
new_perimeters.no_sort = this_islands_perimeters->no_sort;
for (const ExtrusionPaths &paths : extra_perimeters) { new_perimeters.append(paths); }
new_perimeters.append(this_islands_perimeters->entities);
this_islands_perimeters->swap(new_perimeters);
SurfaceCollection orig_surfaces = *this->fill_surfaces;
this->fill_surfaces->clear();
for (const auto &surface : orig_surfaces.surfaces) {
auto new_surfaces = diff_ex({surface.expolygon}, filled_area);
this->fill_surfaces->append(new_surfaces, surface);
}
ExtrusionEntityCollection &this_islands_perimeters = static_cast<ExtrusionEntityCollection&>(*out_loops.entities.back());
ExtrusionEntitiesPtr old_entities;
old_entities.swap(this_islands_perimeters.entities);
for (ExtrusionPaths &paths : extra_perimeters)
this_islands_perimeters.append(std::move(paths));
append(this_islands_perimeters.entities, old_entities);
infill_areas = diff_ex(infill_areas, filled_area);
}
}
} // for each island
}
bool PerimeterGeneratorLoop::is_internal_contour() const
{
// An internal contour is a contour containing no other contours
if (! this->is_contour)
return false;
for (const PerimeterGeneratorLoop &loop : this->children)
if (loop.is_contour)
return false;
return true;
append(out_fill_expolygons, std::move(infill_areas));
}
}

View File

@ -10,70 +10,93 @@
namespace Slic3r {
class PerimeterGenerator {
public:
// Inputs:
const SurfaceCollection *slices;
const ExPolygons *lower_slices;
namespace PerimeterGenerator
{
struct Parameters {
Parameters(
double layer_height,
int layer_id,
Flow perimeter_flow,
Flow ext_perimeter_flow,
Flow overhang_flow,
Flow solid_infill_flow,
const PrintRegionConfig &config,
const PrintObjectConfig &object_config,
const PrintConfig &print_config,
const bool spiral_vase) :
layer_height(layer_height),
layer_id(layer_id),
perimeter_flow(perimeter_flow),
ext_perimeter_flow(ext_perimeter_flow),
overhang_flow(overhang_flow),
solid_infill_flow(solid_infill_flow),
config(config),
object_config(object_config),
print_config(print_config),
spiral_vase(spiral_vase),
scaled_resolution(scaled<double>(print_config.gcode_resolution.value)),
mm3_per_mm(perimeter_flow.mm3_per_mm()),
ext_mm3_per_mm(ext_perimeter_flow.mm3_per_mm()),
mm3_per_mm_overhang(overhang_flow.mm3_per_mm())
{
}
// Input parameters
double layer_height;
int layer_id;
Flow perimeter_flow;
Flow ext_perimeter_flow;
Flow overhang_flow;
Flow solid_infill_flow;
const PrintRegionConfig *config;
const PrintObjectConfig *object_config;
const PrintConfig *print_config;
// Outputs:
ExtrusionEntityCollection *loops;
ExtrusionEntityCollection *gap_fill;
SurfaceCollection *fill_surfaces;
const PrintRegionConfig &config;
const PrintObjectConfig &object_config;
const PrintConfig &print_config;
PerimeterGenerator(
// Input:
const SurfaceCollection* slices,
double layer_height,
Flow flow,
const PrintRegionConfig* config,
const PrintObjectConfig* object_config,
const PrintConfig* print_config,
const bool spiral_vase,
// Output:
// Loops with the external thin walls
ExtrusionEntityCollection* loops,
// Gaps without the thin walls
ExtrusionEntityCollection* gap_fill,
// Infills without the gap fills
SurfaceCollection* fill_surfaces)
: slices(slices), lower_slices(nullptr), layer_height(layer_height),
layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow),
overhang_flow(flow), solid_infill_flow(flow),
config(config), object_config(object_config), print_config(print_config),
m_spiral_vase(spiral_vase),
m_scaled_resolution(scaled<double>(print_config->gcode_resolution.value)),
loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces),
m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1)
{}
void process_classic();
void process_arachne();
double ext_mm3_per_mm() const { return m_ext_mm3_per_mm; }
double mm3_per_mm() const { return m_mm3_per_mm; }
double mm3_per_mm_overhang() const { return m_mm3_per_mm_overhang; }
Polygons lower_slices_polygons() const { return m_lower_slices_polygons; }
// Derived parameters
bool spiral_vase;
double scaled_resolution;
double ext_mm3_per_mm;
double mm3_per_mm;
double mm3_per_mm_overhang;
private:
bool m_spiral_vase;
double m_scaled_resolution;
double m_ext_mm3_per_mm;
double m_mm3_per_mm;
double m_mm3_per_mm_overhang;
Polygons m_lower_slices_polygons;
Parameters() = delete;
};
void process_classic(
// Inputs:
const Parameters &params,
const Surface &surface,
const ExPolygons *lower_slices,
// Cache:
Polygons &lower_slices_polygons_cache,
// Output:
// Loops with the external thin walls
ExtrusionEntityCollection &out_loops,
// Gaps without the thin walls
ExtrusionEntityCollection &out_gap_fill,
// Infills without the gap fills
ExPolygons &out_fill_expolygons);
void process_arachne(
// Inputs:
const Parameters &params,
const Surface &surface,
const ExPolygons *lower_slices,
// Cache:
Polygons &lower_slices_polygons_cache,
// Output:
// Loops with the external thin walls
ExtrusionEntityCollection &out_loops,
// Gaps without the thin walls
ExtrusionEntityCollection &out_gap_fill,
// Infills without the gap fills
ExPolygons &out_fill_expolygons);
ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, float tolerance, float merge_tolerance);
}
} // namespace PerimeterGenerator
} // namespace Slic3r
#endif

View File

@ -57,98 +57,6 @@ void Point::rotate(double angle, const Point &center)
(*this)(1) = (coord_t)round( (double)center(1) + c * dy + s * dx );
}
int Point::nearest_point_index(const Points &points) const
{
PointConstPtrs p;
p.reserve(points.size());
for (Points::const_iterator it = points.begin(); it != points.end(); ++it)
p.push_back(&*it);
return this->nearest_point_index(p);
}
int Point::nearest_point_index(const PointConstPtrs &points) const
{
int idx = -1;
double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough
for (PointConstPtrs::const_iterator it = points.begin(); it != points.end(); ++it) {
/* If the X distance of the candidate is > than the total distance of the
best previous candidate, we know we don't want it */
double d = sqr<double>((*this)(0) - (*it)->x());
if (distance != -1 && d > distance) continue;
/* If the Y distance of the candidate is > than the total distance of the
best previous candidate, we know we don't want it */
d += sqr<double>((*this)(1) - (*it)->y());
if (distance != -1 && d > distance) continue;
idx = it - points.begin();
distance = d;
if (distance < EPSILON) break;
}
return idx;
}
int Point::nearest_point_index(const PointPtrs &points) const
{
PointConstPtrs p;
p.reserve(points.size());
for (PointPtrs::const_iterator it = points.begin(); it != points.end(); ++it)
p.push_back(*it);
return this->nearest_point_index(p);
}
bool Point::nearest_point(const Points &points, Point* point) const
{
int idx = this->nearest_point_index(points);
if (idx == -1) return false;
*point = points.at(idx);
return true;
}
Point Point::projection_onto(const MultiPoint &poly) const
{
Point running_projection = poly.first_point();
double running_min = (running_projection - *this).cast<double>().norm();
Lines lines = poly.lines();
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) {
Point point_temp = this->projection_onto(*line);
if ((point_temp - *this).cast<double>().norm() < running_min) {
running_projection = point_temp;
running_min = (running_projection - *this).cast<double>().norm();
}
}
return running_projection;
}
Point Point::projection_onto(const Line &line) const
{
if (line.a == line.b) return line.a;
/*
(Ported from VisiLibity by Karl J. Obermeyer)
The projection of point_temp onto the line determined by
line_segment_temp can be represented as an affine combination
expressed in the form projection of
Point = theta*line_segment_temp.first + (1.0-theta)*line_segment_temp.second.
If theta is outside the interval [0,1], then one of the Line_Segment's endpoints
must be closest to calling Point.
*/
double lx = (double)(line.b(0) - line.a(0));
double ly = (double)(line.b(1) - line.a(1));
double theta = ( (double)(line.b(0) - (*this)(0))*lx + (double)(line.b(1)- (*this)(1))*ly )
/ ( sqr<double>(lx) + sqr<double>(ly) );
if (0.0 <= theta && theta <= 1.0)
return (theta * line.a.cast<coordf_t>() + (1.0-theta) * line.b.cast<coordf_t>()).cast<coord_t>();
// Else pick closest endpoint.
return ((line.a - *this).cast<double>().squaredNorm() < (line.b - *this).cast<double>().squaredNorm()) ? line.a : line.b;
}
bool has_duplicate_points(std::vector<Point> &&pts)
{
std::sort(pts.begin(), pts.end());
@ -197,6 +105,29 @@ BoundingBoxf get_extents(const std::vector<Vec2d> &pts)
return bbox;
}
int nearest_point_index(const Points &points, const Point &pt)
{
int64_t distance = std::numeric_limits<int64_t>::max();
int idx = -1;
for (const Point &pt2 : points) {
// If the X distance of the candidate is > than the total distance of the
// best previous candidate, we know we don't want it.
int64_t d = sqr<int64_t>(pt2.x() - pt.x());
if (d < distance) {
// If the Y distance of the candidate is > than the total distance of the
// best previous candidate, we know we don't want it.
d += sqr<int64_t>(pt2.y() - pt.y());
if (d < distance) {
idx = &pt2 - points.data();
distance = d;
}
}
}
return idx;
}
std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf)
{
return stm << pointf(0) << "," << pointf(1);

View File

@ -17,8 +17,6 @@ namespace Slic3r {
class BoundingBox;
class BoundingBoxf;
class Line;
class MultiPoint;
class Point;
using Vector = Point;
@ -183,13 +181,6 @@ public:
Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; }
Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; }
Point rotated(double angle, const Point &center) const { Point res(*this); res.rotate(angle, center); return res; }
Point rotate_90_degree_ccw() const { return Point(-this->y(), this->x()); }
int nearest_point_index(const Points &points) const;
int nearest_point_index(const PointConstPtrs &points) const;
int nearest_point_index(const PointPtrs &points) const;
bool nearest_point(const Points &points, Point* point) const;
Point projection_onto(const MultiPoint &poly) const;
Point projection_onto(const Line &line) const;
};
inline bool operator<(const Point &l, const Point &r)
@ -242,6 +233,14 @@ BoundingBox get_extents(const Points &pts);
BoundingBox get_extents(const std::vector<Points> &pts);
BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
int nearest_point_index(const Points &points, const Point &pt);
inline std::pair<Point, bool> nearest_point(const Points &points, const Point &pt)
{
int idx = nearest_point_index(points, pt);
return idx == -1 ? std::make_pair(Point(), false) : std::make_pair(points[idx], true);
}
// Test for duplicate points in a vector of points.
// The points are copied, sorted and checked for duplicates globally.
bool has_duplicate_points(std::vector<Point> &&pts);

View File

@ -6,6 +6,17 @@
namespace Slic3r {
double Polygon::length() const
{
double l = 0;
if (this->points.size() > 1) {
l = (this->points.back() - this->points.front()).cast<double>().norm();
for (size_t i = 1; i < this->points.size(); ++ i)
l += (this->points[i] - this->points[i - 1]).cast<double>().norm();
}
return l;
}
Lines Polygon::lines() const
{
return to_lines(*this);
@ -88,32 +99,11 @@ void Polygon::douglas_peucker(double tolerance)
this->points = std::move(p);
}
// Does an unoriented polygon contain a point?
// Tested by counting intersections along a horizontal line.
bool Polygon::contains(const Point &p) const
{
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
bool result = false;
Points::const_iterator i = this->points.begin();
Points::const_iterator j = this->points.end() - 1;
for (; i != this->points.end(); j = i ++)
if (i->y() > p.y() != j->y() > p.y())
#if 1
if (Vec2d v = (*j - *i).cast<double>();
// p.x() is below the line
p.x() - i->x() < double(p.y() - i->y()) * v.x() / v.y())
#else
// Orientation predicated relative to i-th point.
if (double orient = (double)(p.x() - i->x()) * (double)(j->y() - i->y()) - (double)(p.y() - i->y()) * (double)(j->x() - i->x());
(i->y() > j->y()) ? (orient > 0.) : (orient < 0.))
#endif
result = !result;
return result;
}
// this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons()
Polygons Polygon::simplify(double tolerance) const
{
// Works on CCW polygons only, CW contour will be reoriented to CCW by Clipper's simplify_polygons()!
assert(this->is_counter_clockwise());
// repeat first point at the end in order to apply Douglas-Peucker
// on the whole polygon
Points points = this->points;
@ -126,13 +116,6 @@ Polygons Polygon::simplify(double tolerance) const
return simplify_polygons(pp);
}
void Polygon::simplify(double tolerance, Polygons &polygons) const
{
Polygons pp = this->simplify(tolerance);
polygons.reserve(polygons.size() + pp.size());
polygons.insert(polygons.end(), pp.begin(), pp.end());
}
// Only call this on convex polygons or it will return invalid results
void Polygon::triangulate_convex(Polygons* polygons) const
{
@ -167,6 +150,64 @@ Point Polygon::centroid() const
return Point(Vec2d(c / (3. * area_sum)));
}
bool Polygon::intersection(const Line &line, Point *intersection) const
{
if (this->points.size() < 2)
return false;
if (Line(this->points.front(), this->points.back()).intersection(line, intersection))
return true;
for (size_t i = 1; i < this->points.size(); ++ i)
if (Line(this->points[i - 1], this->points[i]).intersection(line, intersection))
return true;
return false;
}
bool Polygon::first_intersection(const Line& line, Point* intersection) const
{
if (this->points.size() < 2)
return false;
bool found = false;
double dmin = 0.;
Line l(this->points.back(), this->points.front());
for (size_t i = 0; i < this->points.size(); ++ i) {
l.b = this->points[i];
Point ip;
if (l.intersection(line, &ip)) {
if (! found) {
found = true;
dmin = (line.a - ip).cast<double>().squaredNorm();
*intersection = ip;
} else {
double d = (line.a - ip).cast<double>().squaredNorm();
if (d < dmin) {
dmin = d;
*intersection = ip;
}
}
}
l.a = l.b;
}
return found;
}
bool Polygon::intersections(const Line &line, Points *intersections) const
{
if (this->points.size() < 2)
return false;
size_t intersections_size = intersections->size();
Line l(this->points.back(), this->points.front());
for (size_t i = 0; i < this->points.size(); ++ i) {
l.b = this->points[i];
Point intersection;
if (l.intersection(line, &intersection))
intersections->emplace_back(std::move(intersection));
l.a = l.b;
}
return intersections->size() > intersections_size;
}
// Filter points from poly to the output with the help of FilterFn.
// filter function receives two vectors:
// v1: this_point - previous_point
@ -516,6 +557,56 @@ void remove_collinear(Polygons &polys)
remove_collinear(poly);
}
Polygons polygons_simplify(const Polygons &source_polygons, double tolerance)
{
Polygons out;
out.reserve(source_polygons.size());
for (const Polygon &source_polygon : source_polygons) {
// Run Douglas / Peucker simplification algorithm on an open polyline (by repeating the first point at the end of the polyline),
Points simplified = MultiPoint::_douglas_peucker(to_polyline(source_polygon).points, tolerance);
// then remove the last (repeated) point.
simplified.pop_back();
// Simplify the decimated contour by ClipperLib.
bool ccw = ClipperLib::Area(simplified) > 0.;
for (Points &path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero)) {
if (! ccw)
// ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW.
std::reverse(path.begin(), path.end());
out.emplace_back(std::move(path));
}
}
return out;
}
// Do polygons match? If they match, they must have the same topology,
// however their contours may be rotated.
bool polygons_match(const Polygon &l, const Polygon &r)
{
if (l.size() != r.size())
return false;
auto it_l = std::find(l.points.begin(), l.points.end(), r.points.front());
if (it_l == l.points.end())
return false;
auto it_r = r.points.begin();
for (; it_l != l.points.end(); ++ it_l, ++ it_r)
if (*it_l != *it_r)
return false;
it_l = l.points.begin();
for (; it_r != r.points.end(); ++ it_l, ++ it_r)
if (*it_l != *it_r)
return false;
return true;
}
bool contains(const Polygon &polygon, const Point &p, bool border_result)
{
if (const int poly_count_inside = ClipperLib::PointInPolygon(p, polygon.points);
poly_count_inside == -1)
return border_result;
else
return (poly_count_inside % 2) == 1;
}
bool contains(const Polygons &polygons, const Point &p, bool border_result)
{
int poly_count_inside = 0;

View File

@ -15,11 +15,14 @@ using Polygons = std::vector<Polygon>;
using PolygonPtrs = std::vector<Polygon*>;
using ConstPolygonPtrs = std::vector<const Polygon*>;
// Returns true if inside. Returns border_result if on boundary.
bool contains(const Polygon& polygon, const Point& p, bool border_result = true);
bool contains(const Polygons& polygons, const Point& p, bool border_result = true);
class Polygon : public MultiPoint
{
public:
Polygon() = default;
~Polygon() override = default;
explicit Polygon(const Points &points) : MultiPoint(points) {}
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
Polygon(const Polygon &other) : MultiPoint(other.points) {}
@ -38,9 +41,10 @@ public:
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
// last point == first point for polygons
const Point& last_point() const override { return this->points.front(); }
const Point& last_point() const { return this->points.front(); }
Lines lines() const override;
double length() const;
Lines lines() const;
Polyline split_at_vertex(const Point &point) const;
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline split_at_index(int index) const;
@ -58,13 +62,21 @@ public:
void douglas_peucker(double tolerance);
// Does an unoriented polygon contain a point?
// Tested by counting intersections along a horizontal line.
bool contains(const Point &point) const;
bool contains(const Point &point) const { return Slic3r::contains(*this, point, true); }
// Approximate on boundary test.
bool on_boundary(const Point &point, double eps) const
{ return (this->point_projection(point) - point).cast<double>().squaredNorm() < eps * eps; }
// Works on CCW polygons only, CW contour will be reoriented to CCW by Clipper's simplify_polygons()!
Polygons simplify(double tolerance) const;
void simplify(double tolerance, Polygons &polygons) const;
void densify(float min_length, std::vector<float>* lengths = nullptr);
void triangulate_convex(Polygons* polygons) const;
Point centroid() const;
bool intersection(const Line& line, Point* intersection) const;
bool first_intersection(const Line& line, Point* intersection) const;
bool intersections(const Line &line, Points *intersections) const;
// Considering CCW orientation of this polygon, find all convex resp. concave points
// with the angle at the vertex larger than a threshold.
// Zero angle_threshold means to accept all convex resp. concave points.
@ -136,14 +148,7 @@ inline void polygons_append(Polygons &dst, Polygons &&src)
}
}
inline Polygons polygons_simplify(const Polygons &polys, double tolerance)
{
Polygons out;
out.reserve(polys.size());
for (const Polygon &p : polys)
polygons_append(out, p.simplify(tolerance));
return out;
}
Polygons polygons_simplify(const Polygons &polys, double tolerance);
inline void polygons_rotate(Polygons &polys, double angle)
{
@ -204,18 +209,22 @@ inline Lines to_lines(const Polygons &polys)
return lines;
}
inline Polylines to_polylines(const Polygons &polys)
inline Polyline to_polyline(const Polygon &polygon)
{
Polylines polylines;
polylines.assign(polys.size(), Polyline());
size_t idx = 0;
for (Polygons::const_iterator it = polys.begin(); it != polys.end(); ++ it) {
Polyline &pl = polylines[idx ++];
pl.points = it->points;
pl.points.push_back(it->points.front());
Polyline out;
out.points.reserve(polygon.size() + 1);
out.points.assign(polygon.points.begin(), polygon.points.end());
out.points.push_back(polygon.points.front());
return out;
}
assert(idx == polylines.size());
return polylines;
inline Polylines to_polylines(const Polygons &polygons)
{
Polylines out;
out.reserve(polygons.size());
for (const Polygon &polygon : polygons)
out.emplace_back(to_polyline(polygon));
return out;
}
inline Polylines to_polylines(Polygons &&polys)
@ -250,8 +259,9 @@ inline Polygons to_polygons(std::vector<Points> &&paths)
return out;
}
// Returns true if inside. Returns border_result if on boundary.
bool contains(const Polygons& polygons, const Point& p, bool border_result = true);
// Do polygons match? If they match, they must have the same topology,
// however their contours may be rotated.
bool polygons_match(const Polygon &l, const Polygon &r);
Polygon make_circle(double radius, double error);
Polygon make_circle_num_segments(double radius, size_t num_segments);

View File

@ -19,6 +19,14 @@ const Point& Polyline::leftmost_point() const
return *p;
}
double Polyline::length() const
{
double l = 0;
for (size_t i = 1; i < this->points.size(); ++ i)
l += (this->points[i] - this->points[i - 1]).cast<double>().norm();
return l;
}
Lines Polyline::lines() const
{
Lines lines;
@ -146,9 +154,8 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
auto min_point_it = this->points.cbegin();
Point prev = this->points.front();
for (auto it = this->points.cbegin() + 1; it != this->points.cend(); ++ it) {
Point proj = point.projection_onto(Line(prev, *it));
auto d2 = (proj - point).cast<double>().squaredNorm();
if (d2 < min_dist2) {
Point proj;
if (double d2 = line_alg::distance_to_squared(Line(prev, *it), point, &proj); d2 < min_dist2) {
min_dist2 = d2;
min_point_it = it;
}
@ -235,9 +242,8 @@ std::pair<int, Point> foot_pt(const Points &polyline, const Point &pt)
auto it = polyline.begin();
auto it_proj = polyline.begin();
for (++ it; it != polyline.end(); ++ it) {
Point foot_pt = pt.projection_onto(Line(prev, *it));
double d2 = (foot_pt - pt).cast<double>().squaredNorm();
if (d2 < d2_min) {
Point foot_pt;
if (double d2 = line_alg::distance_to_squared(Line(prev, *it), pt, &foot_pt); d2 < d2_min) {
d2_min = d2;
foot_pt_min = foot_pt;
it_proj = it;
@ -286,6 +292,14 @@ void ThickPolyline::clip_end(double distance)
assert(this->width.size() == (this->points.size() - 1) * 2);
}
double Polyline3::length() const
{
double l = 0;
for (size_t i = 1; i < this->points.size(); ++ i)
l += (this->points[i] - this->points[i - 1]).cast<double>().norm();
return l;
}
Lines3 Polyline3::lines() const
{
Lines3 lines;

View File

@ -17,7 +17,6 @@ typedef std::vector<ThickPolyline> ThickPolylines;
class Polyline : public MultiPoint {
public:
Polyline() = default;
~Polyline() override = default;
Polyline(const Polyline &other) : MultiPoint(other.points) {}
Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {}
Polyline(std::initializer_list<Point> list) : MultiPoint(list) {}
@ -64,11 +63,12 @@ public:
Point& operator[](Points::size_type idx) { return this->points[idx]; }
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
const Point& last_point() const override { return this->points.back(); }
double length() const;
const Point& last_point() const { return this->points.back(); }
const Point& leftmost_point() const;
Lines lines() const override;
Lines lines() const;
virtual void clip_end(double distance);
void clip_end(double distance);
void clip_start(double distance);
void extend_end(double distance);
void extend_start(double distance);
@ -170,7 +170,7 @@ public:
std::swap(this->endpoints.first, this->endpoints.second);
}
void clip_end(double distance) override;
void clip_end(double distance);
std::vector<coordf_t> width;
std::pair<bool,bool> endpoints;
@ -191,7 +191,8 @@ inline ThickPolylines to_thick_polylines(Polylines &&polylines, const coordf_t w
class Polyline3 : public MultiPoint3
{
public:
virtual Lines3 lines() const;
double length() const;
Lines3 lines() const;
};
typedef std::vector<Polyline3> Polylines3;

View File

@ -418,7 +418,7 @@ public:
size_t first_compatible_idx(PreferedCondition prefered_condition) const
{
size_t i = m_default_suppressed ? m_num_default_presets : 0;
size_t n = this->m_presets.size();
size_t n = m_presets.size();
size_t i_compatible = n;
int match_quality = -1;
for (; i < n; ++ i)

View File

@ -1223,7 +1223,7 @@ void Print::_make_wipe_tower()
volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
// try to assign some infills/objects for the wiping:
volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe);
volume_to_wipe = layer_tools.wiping_extrusions_nonconst().mark_wiping_extrusions(*this, layer_tools, current_extruder_id, extruder_id, volume_to_wipe);
// add back the minimal amount toforce on the wipe tower:
volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
@ -1234,7 +1234,7 @@ void Print::_make_wipe_tower()
current_extruder_id = extruder_id;
}
}
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
layer_tools.wiping_extrusions_nonconst().ensure_perimeters_infills_order(*this, layer_tools);
if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0)
break;
}

View File

@ -103,7 +103,7 @@ std::string PrintBase::output_filepath(const std::string &path, const std::strin
void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel /* warning_level */, const std::string &message, const PrintObjectBase* print_object)
{
if (this->m_status_callback) {
if (m_status_callback) {
auto status = print_object ? SlicingStatus(*print_object, step) : SlicingStatus(*this, step);
m_status_callback(status);
}

View File

@ -131,6 +131,7 @@ void PrintObject::make_perimeters()
// Revert the typed slices into untyped slices.
if (m_typed_slices) {
for (Layer *layer : m_layers) {
layer->clear_fills();
layer->restore_untyped_slices();
m_print->throw_if_canceled();
}
@ -157,7 +158,7 @@ void PrintObject::make_perimeters()
m_print->throw_if_canceled();
LayerRegion &layerm = *m_layers[layer_idx]->get_region(region_id);
const LayerRegion &upper_layerm = *m_layers[layer_idx+1]->get_region(region_id);
const Polygons upper_layerm_polygons = to_polygons(upper_layerm.slices.surfaces);
const Polygons upper_layerm_polygons = to_polygons(upper_layerm.slices().surfaces);
// Filter upper layer polygons in intersection_ppl by their bounding boxes?
// my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
const double total_loop_length = total_length(upper_layerm_polygons);
@ -166,7 +167,8 @@ void PrintObject::make_perimeters()
const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width();
const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing();
for (Surface &slice : layerm.slices.surfaces) {
// slice is not const because slice.extra_perimeters is being incremented.
for (Surface &slice : layerm.m_slices.surfaces) {
for (;;) {
// compute the total thickness of perimeters
const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2
@ -418,12 +420,12 @@ void PrintObject::generate_support_spots()
BOOST_LOG_TRIVIAL(debug)
<< "Searching support spots - start";
m_print->set_status(75, L("Searching support spots"));
if (this->m_config.support_material && !this->m_config.support_material_auto &&
if (m_config.support_material && !m_config.support_material_auto &&
std::all_of(this->model_object()->volumes.begin(), this->model_object()->volumes.end(),
[](const ModelVolume* mv){return mv->supported_facets.empty();})
) {
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values};
auto [issues, malformations] = SupportSpotsGenerator::full_search(this, params);
SupportSpotsGenerator::Issues issues = SupportSpotsGenerator::full_search(this, params);
auto obj_transform = this->trafo_centered();
for (ModelVolume *model_volume : this->model_object()->volumes) {
@ -521,7 +523,7 @@ std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare
m_print->throw_if_canceled();
const Layer *layer = this->layers()[idx_layer];
for (const LayerRegion *layerm : layer->regions())
for (const Surface &surface : layerm->fill_surfaces.surfaces)
for (const Surface &surface : layerm->fill_surfaces())
if (surface.surface_type == stInternalBridge)
append(out, triangulate_expolygon_3d(surface.expolygon, layer->bottom_z()));
}
@ -906,13 +908,13 @@ void PrintObject::detect_surfaces_type()
Surfaces top;
if (upper_layer) {
ExPolygons upper_slices = interface_shells ?
diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
diff_ex(layerm->slices().surfaces, upper_layer->m_regions[region_id]->slices().surfaces, ApplySafetyOffset::Yes) :
diff_ex(layerm->slices().surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
surfaces_append(top, opening_ex(upper_slices, offset), stTop);
} else {
// if no upper layer, all surfaces of this one are solid
// we clone surfaces because we're going to clear the slices collection
top = layerm->slices.surfaces;
top = layerm->slices().surfaces;
for (Surface &surface : top)
surface.surface_type = stTop;
}
@ -933,7 +935,7 @@ void PrintObject::detect_surfaces_type()
surfaces_append(
bottom,
opening_ex(
diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
diff_ex(layerm->slices().surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
offset),
surface_type_bottom_other);
// if user requested internal shells, we need to identify surfaces
@ -945,8 +947,8 @@ void PrintObject::detect_surfaces_type()
bottom,
opening_ex(
diff_ex(
intersection(layerm->slices.surfaces, lower_layer->lslices), // supported
lower_layer->m_regions[region_id]->slices.surfaces,
intersection(layerm->slices().surfaces, lower_layer->lslices), // supported
lower_layer->m_regions[region_id]->slices().surfaces,
ApplySafetyOffset::Yes),
offset),
stBottom);
@ -955,7 +957,7 @@ void PrintObject::detect_surfaces_type()
} else {
// if no lower layer, all surfaces of this one are solid
// we clone surfaces because we're going to clear the slices collection
bottom = layerm->slices.surfaces;
bottom = layerm->slices().surfaces;
for (Surface &surface : bottom)
surface.surface_type = stBottom;
}
@ -984,13 +986,13 @@ void PrintObject::detect_surfaces_type()
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// save surfaces to layer
Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->slices.surfaces;
Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->m_slices.surfaces;
Surfaces surfaces_backup;
if (! interface_shells) {
surfaces_backup = std::move(surfaces_out);
surfaces_out.clear();
}
const Surfaces &surfaces_prev = interface_shells ? layerm->slices.surfaces : surfaces_backup;
const Surfaces &surfaces_prev = interface_shells ? layerm->slices().surfaces : surfaces_backup;
// find internal surfaces (difference between top/bottom surfaces and others)
{
@ -1016,15 +1018,15 @@ void PrintObject::detect_surfaces_type()
if (interface_shells) {
// Move surfaces_new to layerm->slices.surfaces
for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer)
m_layers[idx_layer]->m_regions[region_id]->slices.surfaces = std::move(surfaces_new[idx_layer]);
m_layers[idx_layer]->m_regions[region_id]->m_slices.set(std::move(surfaces_new[idx_layer]));
}
if (spiral_vase) {
if (num_layers > 1)
// Turn the last bottom layer infill to a top infill, so it will be extruded with a proper pattern.
m_layers[num_layers - 1]->m_regions[region_id]->slices.set_type(stTop);
m_layers[num_layers - 1]->m_regions[region_id]->m_slices.set_type(stTop);
for (size_t i = num_layers; i < m_layers.size(); ++ i)
m_layers[i]->m_regions[region_id]->slices.set_type(stInternal);
m_layers[i]->m_regions[region_id]->m_slices.set_type(stInternal);
}
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start";
@ -1071,7 +1073,7 @@ void PrintObject::process_external_surfaces()
bool expansions = false;
bool voids = false;
for (const LayerRegion *layerm : layer->regions()) {
for (const Surface &surface : layerm->fill_surfaces.surfaces) {
for (const Surface &surface : layerm->fill_surfaces()) {
if (surface.surface_type == stInternal)
voids = true;
else
@ -1096,7 +1098,7 @@ void PrintObject::process_external_surfaces()
Polygons voids;
for (const LayerRegion *layerm : m_layers[layer_idx]->regions()) {
if (layerm->region().config().fill_density.value == 0.)
for (const Surface &surface : layerm->fill_surfaces.surfaces)
for (const Surface &surface : layerm->fill_surfaces())
// Shrink the holes, let the layer above expand slightly inside the unsupported areas.
polygons_append(voids, offset(surface.expolygon, unsupported_width));
}
@ -1125,7 +1127,7 @@ void PrintObject::process_external_surfaces()
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end";
}
}
} // void PrintObject::process_external_surfaces()
void PrintObject::discover_vertical_shells()
{
@ -1193,15 +1195,15 @@ void PrintObject::discover_vertical_shells()
LayerRegion &layerm = *layer.m_regions[region_id];
float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f;
// Top surfaces.
append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing));
append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), min_perimeter_infill_spacing));
append(cache.top_surfaces, offset(layerm.slices().filter_by_type(stTop), min_perimeter_infill_spacing));
append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), min_perimeter_infill_spacing));
// Bottom surfaces.
append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing));
append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing));
append(cache.bottom_surfaces, offset(layerm.slices().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing));
append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing));
// Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only.
// First find the maxium number of perimeters per region slice.
unsigned int perimeters = 0;
for (Surface &s : layerm.slices.surfaces)
for (const Surface &s : layerm.slices())
perimeters = std::max<unsigned int>(perimeters, s.extra_perimeters);
perimeters += layerm.region().config().perimeters.value;
// Then calculate the infill offset.
@ -1212,7 +1214,7 @@ void PrintObject::discover_vertical_shells()
0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing());
perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing())));
}
polygons_append(cache.holes, to_polygons(layerm.fill_expolygons));
polygons_append(cache.holes, to_polygons(layerm.fill_expolygons()));
}
// Save some computing time by reducing the number of polygons.
cache.top_surfaces = union_(cache.top_surfaces);
@ -1265,15 +1267,15 @@ void PrintObject::discover_vertical_shells()
float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f;
// Top surfaces.
auto &cache = cache_top_botom_regions[idx_layer];
cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing);
append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), min_perimeter_infill_spacing));
cache.top_surfaces = offset(layerm.slices().filter_by_type(stTop), min_perimeter_infill_spacing);
append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), min_perimeter_infill_spacing));
// Bottom surfaces.
cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing);
append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing));
cache.bottom_surfaces = offset(layerm.slices().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing);
append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing));
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
if (cache.holes.empty()) {
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons()));
}
}
});
@ -1430,14 +1432,14 @@ void PrintObject::discover_vertical_shells()
// Trim the shells region by the internal & internal void surfaces.
const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid };
const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 3));
const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces().filter_by_types(surfaceTypesInternal, 3));
shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes);
polygons_append(shell, diff(polygonsInternal, holes));
if (shell.empty())
continue;
// Append the internal solids, so they will be merged with the new ones.
polygons_append(shell, to_polygons(layerm->fill_surfaces.filter_by_type(stInternalSolid)));
polygons_append(shell, to_polygons(layerm->fill_surfaces().filter_by_type(stInternalSolid)));
// These regions will be filled by a rectilinear full infill. Currently this type of infill
// only fills regions, which fit at least a single line. To avoid gaps in the sparse infill,
@ -1484,8 +1486,8 @@ void PrintObject::discover_vertical_shells()
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Trim the internal & internalvoid by the shell.
Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), shell);
Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), shell);
Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces().filter_by_type(stInternal), shell);
Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces().filter_by_type(stInternalVoid), shell);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
@ -1497,10 +1499,10 @@ void PrintObject::discover_vertical_shells()
// Assign resulting internal surfaces to layer.
const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge };
layerm->fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType));
layerm->fill_surfaces.append(new_internal, stInternal);
layerm->fill_surfaces.append(new_internal_void, stInternalVoid);
layerm->fill_surfaces.append(new_internal_solid, stInternalSolid);
layerm->m_fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType));
layerm->m_fill_surfaces.append(new_internal, stInternal);
layerm->m_fill_surfaces.append(new_internal_void, stInternalVoid);
layerm->m_fill_surfaces.append(new_internal_solid, stInternalSolid);
} // for each layer
});
m_print->throw_if_canceled();
@ -1514,7 +1516,7 @@ void PrintObject::discover_vertical_shells()
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
} // for each region
}
} // void PrintObject::discover_vertical_shells()
// This method applies bridge flow to the first internal solid layer above sparse infill.
void PrintObject::bridge_over_infill()
@ -1537,7 +1539,7 @@ void PrintObject::bridge_over_infill()
for (Layer *layer : m_layers) {
Polygons sum;
for (const LayerRegion *layerm : layer->m_regions)
layerm->fill_surfaces.filter_by_type(stInternal, &sum);
layerm->fill_surfaces().filter_by_type(stInternal, &sum);
internals.emplace_back(std::move(sum));
}
@ -1554,7 +1556,7 @@ void PrintObject::bridge_over_infill()
// Extract the stInternalSolid surfaces that might be transformed into bridges.
ExPolygons internal_solid;
layerm->fill_surfaces.remove_type(stInternalSolid, &internal_solid);
layerm->m_fill_surfaces.remove_type(stInternalSolid, &internal_solid);
if (internal_solid.empty())
// No internal solid -> no new bridges for this layer region.
continue;
@ -1586,7 +1588,7 @@ void PrintObject::bridge_over_infill()
if (to_bridge_pp.empty()) {
// Restore internal_solid surfaces.
for (ExPolygon &ex : internal_solid)
layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex)));
layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex)));
continue;
}
// convert into ExPolygons
@ -1602,9 +1604,9 @@ void PrintObject::bridge_over_infill()
to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes);
// build the new collection of fill_surfaces
for (ExPolygon &ex : to_bridge)
layerm->fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex)));
layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex)));
for (ExPolygon &ex : not_to_bridge)
layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex)));
layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex)));
/*
# exclude infill from the layers below if needed
# see discussion at https://github.com/alexrj/Slic3r/issues/240
@ -1645,7 +1647,7 @@ void PrintObject::bridge_over_infill()
m_print->throw_if_canceled();
}
});
}
} // void PrintObject::bridge_over_infill()
static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders)
{
@ -1852,7 +1854,7 @@ void PrintObject::clip_fill_surfaces()
// Solid surfaces to be supported.
Polygons overhangs;
for (const LayerRegion *layerm : layer->m_regions)
for (const Surface &surface : layerm->fill_surfaces.surfaces) {
for (const Surface &surface : layerm->fill_surfaces()) {
Polygons polygons = to_polygons(surface.expolygon);
if (surface.is_solid())
polygons_append(overhangs, polygons);
@ -1861,7 +1863,7 @@ void PrintObject::clip_fill_surfaces()
Polygons lower_layer_fill_surfaces;
Polygons lower_layer_internal_surfaces;
for (const LayerRegion *layerm : lower_layer->m_regions)
for (const Surface &surface : layerm->fill_surfaces.surfaces) {
for (const Surface &surface : layerm->fill_surfaces()) {
Polygons polygons = to_polygons(surface.expolygon);
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
polygons_append(lower_layer_internal_surfaces, polygons);
@ -1896,12 +1898,12 @@ void PrintObject::clip_fill_surfaces()
continue;
SurfaceType internal_surface_types[] = { stInternal, stInternalVoid };
Polygons internal;
for (Surface &surface : layerm->fill_surfaces.surfaces)
for (Surface &surface : layerm->m_fill_surfaces.surfaces)
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
polygons_append(internal, std::move(surface.expolygon));
layerm->fill_surfaces.remove_types(internal_surface_types, 2);
layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, ApplySafetyOffset::Yes), stInternal);
layerm->fill_surfaces.append(diff_ex (internal, upper_internal, ApplySafetyOffset::Yes), stInternalVoid);
layerm->m_fill_surfaces.remove_types(internal_surface_types, 2);
layerm->m_fill_surfaces.append(intersection_ex(internal, upper_internal, ApplySafetyOffset::Yes), stInternal);
layerm->m_fill_surfaces.append(diff_ex (internal, upper_internal, ApplySafetyOffset::Yes), stInternalVoid);
// If there are voids it means that our internal infill is not adjacent to
// perimeters. In this case it would be nice to add a loop around infill to
// make it more robust and nicer. TODO.
@ -1911,7 +1913,7 @@ void PrintObject::clip_fill_surfaces()
}
m_print->throw_if_canceled();
}
}
} // void PrintObject::clip_fill_surfaces()
void PrintObject::discover_horizontal_shells()
{
@ -1927,7 +1929,7 @@ void PrintObject::discover_horizontal_shells()
(i % region_config.solid_infill_every_layers) == 0) {
// Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge.
SurfaceType type = (region_config.fill_density == 100 || region_config.solid_infill_every_layers == 1) ? stInternalSolid : stInternalBridge;
for (Surface &surface : layerm->fill_surfaces.surfaces)
for (Surface &surface : layerm->m_fill_surfaces.surfaces)
if (surface.surface_type == stInternal)
surface.surface_type = type;
}
@ -1958,11 +1960,11 @@ void PrintObject::discover_horizontal_shells()
// (not covered by a layer above / below).
// This does not contain the areas covered by perimeters!
Polygons solid;
for (const Surface &surface : layerm->slices.surfaces)
for (const Surface &surface : layerm->slices())
if (surface.surface_type == type)
polygons_append(solid, to_polygons(surface.expolygon));
// Infill areas (slices without the perimeters).
for (const Surface &surface : layerm->fill_surfaces.surfaces)
for (const Surface &surface : layerm->fill_surfaces())
if (surface.surface_type == type)
polygons_append(solid, to_polygons(surface.expolygon));
if (solid.empty())
@ -1995,7 +1997,7 @@ void PrintObject::discover_horizontal_shells()
Polygons new_internal_solid;
{
Polygons internal;
for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces)
for (const Surface &surface : neighbor_layerm->fill_surfaces())
if (surface.surface_type == stInternal || surface.surface_type == stInternalSolid)
polygons_append(internal, to_polygons(surface.expolygon));
new_internal_solid = intersection(solid, internal, ApplySafetyOffset::Yes);
@ -2051,7 +2053,7 @@ void PrintObject::discover_horizontal_shells()
// additional area in the next shell too
// make sure our grown surfaces don't exceed the fill area
Polygons internal;
for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces)
for (const Surface &surface : neighbor_layerm->fill_surfaces())
if (surface.is_internal() && !surface.is_bridge())
polygons_append(internal, to_polygons(surface.expolygon));
polygons_append(new_internal_solid,
@ -2070,16 +2072,16 @@ void PrintObject::discover_horizontal_shells()
// internal-solid are the union of the existing internal-solid surfaces
// and new ones
SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces);
SurfaceCollection backup = std::move(neighbor_layerm->m_fill_surfaces);
polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stInternalSolid)));
ExPolygons internal_solid = union_ex(new_internal_solid);
// assign new internal-solid surfaces to layer
neighbor_layerm->fill_surfaces.set(internal_solid, stInternalSolid);
neighbor_layerm->m_fill_surfaces.set(internal_solid, stInternalSolid);
// subtract intersections from layer surfaces to get resulting internal surfaces
Polygons polygons_internal = to_polygons(std::move(internal_solid));
ExPolygons internal = diff_ex(backup.filter_by_type(stInternal), polygons_internal, ApplySafetyOffset::Yes);
// assign resulting internal surfaces to layer
neighbor_layerm->fill_surfaces.append(internal, stInternal);
neighbor_layerm->m_fill_surfaces.append(internal, stInternal);
polygons_append(polygons_internal, to_polygons(std::move(internal)));
// assign top and bottom surfaces to layer
SurfaceType surface_types_solid[] = { stTop, stBottom, stBottomBridge };
@ -2087,7 +2089,7 @@ void PrintObject::discover_horizontal_shells()
std::vector<SurfacesPtr> top_bottom_groups;
backup.group(&top_bottom_groups);
for (SurfacesPtr &group : top_bottom_groups)
neighbor_layerm->fill_surfaces.append(
neighbor_layerm->m_fill_surfaces.append(
diff_ex(group, polygons_internal),
// Use an existing surface as a template, it carries the bridge angle etc.
*group.front());
@ -2106,7 +2108,7 @@ void PrintObject::discover_horizontal_shells()
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
} // void PrintObject::discover_horizontal_shells()
// combine fill surfaces across layers to honor the "infill every N layers" option
// Idempotence of this method is guaranteed by the fact that we don't remove things from
@ -2164,10 +2166,10 @@ void PrintObject::combine_infill()
layerms.emplace_back(m_layers[i]->regions()[region_id]);
// We need to perform a multi-layer intersection, so let's split it in pairs.
// Initialize the intersection with the candidates of the lowest layer.
ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces.filter_by_type(stInternal));
ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces().filter_by_type(stInternal));
// Start looping from the second layer and intersect the current intersection with it.
for (size_t i = 1; i < layerms.size(); ++ i)
intersection = intersection_ex(layerms[i]->fill_surfaces.filter_by_type(stInternal), intersection);
intersection = intersection_ex(layerms[i]->fill_surfaces().filter_by_type(stInternal), intersection);
double area_threshold = layerms.front()->infill_area_threshold();
if (! intersection.empty() && area_threshold > 0.)
intersection.erase(std::remove_if(intersection.begin(), intersection.end(),
@ -2196,9 +2198,9 @@ void PrintObject::combine_infill()
for (ExPolygon &expoly : intersection)
polygons_append(intersection_with_clearance, offset(expoly, clearance_offset));
for (LayerRegion *layerm : layerms) {
Polygons internal = to_polygons(std::move(layerm->fill_surfaces.filter_by_type(stInternal)));
layerm->fill_surfaces.remove_type(stInternal);
layerm->fill_surfaces.append(diff_ex(internal, intersection_with_clearance), stInternal);
Polygons internal = to_polygons(std::move(layerm->fill_surfaces().filter_by_type(stInternal)));
layerm->m_fill_surfaces.remove_type(stInternal);
layerm->m_fill_surfaces.append(diff_ex(internal, intersection_with_clearance), stInternal);
if (layerm == layerms.back()) {
// Apply surfaces back with adjusted depth to the uppermost layer.
Surface templ(stInternal, ExPolygon());
@ -2206,17 +2208,17 @@ void PrintObject::combine_infill()
for (LayerRegion *layerm2 : layerms)
templ.thickness += layerm2->layer()->height;
templ.thickness_layers = (unsigned short)layerms.size();
layerm->fill_surfaces.append(intersection, templ);
layerm->m_fill_surfaces.append(intersection, templ);
} else {
// Save void surfaces.
layerm->fill_surfaces.append(
layerm->m_fill_surfaces.append(
intersection_ex(internal, intersection_with_clearance),
stInternalVoid);
}
}
}
}
}
} // void PrintObject::combine_infill()
void PrintObject::_generate_support_material()
{

View File

@ -432,12 +432,12 @@ std::string fix_slicing_errors(LayerPtrs &layers, const std::function<void()> &t
const Surfaces *lower_surfaces = nullptr;
for (size_t j = idx_layer + 1; j < layers.size(); ++ j)
if (! layers[j]->slicing_errors) {
upper_surfaces = &layers[j]->regions()[region_id]->slices.surfaces;
upper_surfaces = &layers[j]->regions()[region_id]->slices().surfaces;
break;
}
for (int j = int(idx_layer) - 1; j >= 0; -- j)
if (! layers[j]->slicing_errors) {
lower_surfaces = &layers[j]->regions()[region_id]->slices.surfaces;
lower_surfaces = &layers[j]->regions()[region_id]->slices().surfaces;
break;
}
// Collect outer contours and holes from the valid layers above & below.
@ -464,7 +464,7 @@ std::string fix_slicing_errors(LayerPtrs &layers, const std::function<void()> &t
if (lower_surfaces)
for (const auto &surface : *lower_surfaces)
polygons_append(holes, surface.expolygon.holes);
layerm->slices.set(diff_ex(union_(outer), holes), stInternal);
layerm->m_slices.set(diff_ex(union_(outer), holes), stInternal);
}
// Update layer slices after repairing the single regions.
layer->make_slices();
@ -521,20 +521,29 @@ void PrintObject::slice()
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
m_print->throw_if_canceled();
Layer &layer = *m_layers[layer_idx];
layer.lslices_bboxes.clear();
layer.lslices_bboxes.reserve(layer.lslices.size());
layer.lslices_ex.clear();
layer.lslices_ex.reserve(layer.lslices.size());
for (const ExPolygon &expoly : layer.lslices)
layer.lslices_bboxes.emplace_back(get_extents(expoly));
layer.lslices_ex.push_back({ get_extents(expoly) });
layer.backup_untyped_slices();
}
});
// Interlink the lslices into a Z graph.
tbb::parallel_for(
tbb::blocked_range<size_t>(1, m_layers.size()),
[this](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
m_print->throw_if_canceled();
Layer::build_up_down_graph(*m_layers[layer_idx - 1], *m_layers[layer_idx]);
}
});
if (m_layers.empty())
throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
this->set_done(posSlice);
}
template<typename ThrowOnCancel>
static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
{
// Returns MMU segmentation based on painting in MMU segmentation gizmo
std::vector<std::vector<ExPolygons>> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel);
@ -579,9 +588,9 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
auto it_painted_region = layer_range.painted_regions.begin();
for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id)
if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices.surfaces.empty()) {
if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices().empty()) {
assert(layerm.region().print_object_region_id() == region_id);
const BoundingBox bbox = get_extents(layerm.slices.surfaces);
const BoundingBox bbox = get_extents(layerm.slices().surfaces);
assert(it_painted_region < layer_range.painted_regions.end());
// Find the first it_painted_region which overrides this region.
for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region)
@ -604,7 +613,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
}
// Steal from this region.
int target_region_id = it_painted_region->region->print_object_region_id();
ExPolygons stolen = intersection_ex(layerm.slices.surfaces, segmented.expolygons);
ExPolygons stolen = intersection_ex(layerm.slices().surfaces, segmented.expolygons);
if (! stolen.empty()) {
ByRegion &dst = by_region[target_region_id];
if (dst.expolygons.empty()) {
@ -622,7 +631,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
}
if (! self_trimmed) {
// Trim slices of this LayerRegion with all the MMU regions.
Polygons mine = to_polygons(std::move(layerm.slices.surfaces));
Polygons mine = to_polygons(std::move(layerm.slices().surfaces));
for (auto &segmented : by_extruder)
if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && bbox.overlap(segmented.bbox)) {
mine = diff(mine, segmented.expolygons);
@ -653,7 +662,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
if (src.needs_merge)
// Multiple regions were merged into one.
src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON)));
layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
layer->get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal);
}
}
});
@ -693,7 +702,7 @@ void PrintObject::slice_volumes()
for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) {
std::vector<ExPolygons> &by_layer = region_slices[region_id];
for (size_t layer_id = 0; layer_id < by_layer.size(); ++ layer_id)
m_layers[layer_id]->regions()[region_id]->slices.append(std::move(by_layer[layer_id]), stInternal);
m_layers[layer_id]->regions()[region_id]->m_slices.append(std::move(by_layer[layer_id]), stInternal);
}
region_slices.clear();
@ -754,14 +763,14 @@ void PrintObject::slice_volumes()
LayerRegion *layerm = layer->m_regions.front();
if (elfoot > 0) {
// Apply the elephant foot compensation and store the 1st layer slices without the Elephant foot compensation applied.
lslices_1st_layer = to_expolygons(std::move(layerm->slices.surfaces));
lslices_1st_layer = to_expolygons(std::move(layerm->m_slices.surfaces));
float delta = xy_compensation_scaled;
if (delta > elfoot) {
delta -= elfoot;
elfoot = 0.f;
} else if (delta > 0)
elfoot -= delta;
layerm->slices.set(
layerm->m_slices.set(
union_ex(
Slic3r::elephant_foot_compensation(
(delta == 0.f) ? lslices_1st_layer : offset_ex(lslices_1st_layer, delta),
@ -771,8 +780,8 @@ void PrintObject::slice_volumes()
lslices_1st_layer = offset_ex(std::move(lslices_1st_layer), xy_compensation_scaled);
} else if (xy_compensation_scaled < 0.f) {
// Apply the XY compensation.
layerm->slices.set(
offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), xy_compensation_scaled),
layerm->m_slices.set(
offset_ex(to_expolygons(std::move(layerm->m_slices.surfaces)), xy_compensation_scaled),
stInternal);
}
} else {

View File

@ -180,7 +180,7 @@ bool DefaultSupportTree::interconnect(const Pillar &pillar,
Vec3d eupper = pillar.endpoint();
Vec3d elower = nextpillar.endpoint();
double zmin = ground_level(this->m_sm) + m_sm.cfg.base_height_mm;
double zmin = ground_level(m_sm) + m_sm.cfg.base_height_mm;
eupper.z() = std::max(eupper.z(), zmin);
elower.z() = std::max(elower.z(), zmin);

View File

@ -84,8 +84,7 @@ public:
float overhangs_area = 0.f;
bool overlaps(const Structure &rhs) const {
//FIXME ExPolygon::overlaps() shall be commutative, it is not!
return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon));
return this->bbox.overlap(rhs.bbox) && this->polygon->overlaps(*rhs.polygon);
}
float overlap_area(const Structure &rhs) const {
double out = 0.;

View File

@ -650,14 +650,14 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t
// 1) Count the new polygons first.
size_t n_polygons_new = 0;
for (const LayerRegion *region : layer.regions())
for (const Surface &surface : region->slices.surfaces)
for (const Surface &surface : region->slices())
if (surface.surface_type == surface_type)
n_polygons_new += surface.expolygon.holes.size() + 1;
// 2) Collect the new polygons.
Polygons out;
out.reserve(n_polygons_new);
for (const LayerRegion *region : layer.regions())
for (const Surface &surface : region->slices.surfaces)
for (const Surface &surface : region->slices())
if (surface.surface_type == surface_type)
polygons_append(out, surface.expolygon);
return out;
@ -1213,9 +1213,9 @@ namespace SupportMaterialInternal {
static bool has_bridging_extrusions(const Layer &layer)
{
for (const LayerRegion *region : layer.regions()) {
if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters))
if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters()))
return true;
if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills))
if (region->fill_surfaces().has(stBottomBridge) && has_bridging_fills(region->fills()))
return true;
}
return false;
@ -1287,7 +1287,7 @@ namespace SupportMaterialInternal {
// Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters.
overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices);
#else
Polylines overhang_perimeters = diff_pl(layerm.perimeters.as_polylines(), lower_grown_slices);
Polylines overhang_perimeters = diff_pl(layerm.perimeters().as_polylines(), lower_grown_slices);
#endif
// only consider straight overhangs
@ -1311,7 +1311,7 @@ namespace SupportMaterialInternal {
bool supported[2] = { false, false };
for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i)
for (int j = 0; j < 2; ++ j)
if (! supported[j] && lower_layer.lslices_bboxes[i].contains(pts[j]) && lower_layer.lslices[i].contains(pts[j]))
if (! supported[j] && lower_layer.lslices_ex[i].bbox.contains(pts[j]) && lower_layer.lslices[i].contains(pts[j]))
supported[j] = true;
if (supported[0] && supported[1])
// Offset a polyline into a thick line.
@ -1321,7 +1321,7 @@ namespace SupportMaterialInternal {
}
// remove the entire bridges and only support the unsupported edges
//FIXME the brided regions are already collected as layerm.bridged. Use it?
for (const Surface &surface : layerm.fill_surfaces.surfaces)
for (const Surface &surface : layerm.fill_surfaces())
if (surface.surface_type == stBottomBridge && surface.bridge_angle < 0.0)
polygons_append(bridges, surface.expolygon);
//FIXME add the gap filled areas. Extrude the gaps with a bridge flow?
@ -1329,14 +1329,14 @@ namespace SupportMaterialInternal {
//FIXME add supports at regular intervals to support long bridges!
bridges = diff(bridges,
// Offset unsupported edges into polygons.
offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS));
offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS));
// Remove bridged areas from the supported areas.
contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes);
#ifdef SLIC3R_DEBUG
static int iRun = 0;
SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++),
{ { { union_ex(offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } },
{ { { union_ex(offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } },
{ { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } },
{ { union_ex(bridges) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */
@ -1487,7 +1487,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
0.5f * fw);
// Overhang polygons for this layer and region.
Polygons diff_polygons;
Polygons layerm_polygons = to_polygons(layerm->slices.surfaces);
Polygons layerm_polygons = to_polygons(layerm->slices().surfaces);
if (lower_layer_offset == 0.f) {
// Support everything.
diff_polygons = diff(layerm_polygons, lower_layer_polygons);
@ -2858,10 +2858,10 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
break;
some_region_overlaps = true;
polygons_append(polygons_trimming,
offset(region->fill_surfaces.filter_by_type(stBottomBridge), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
offset(region->fill_surfaces().filter_by_type(stBottomBridge), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (region->region().config().overhangs.value)
// Add bridging perimeters.
SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming);
SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters(), gap_xy_scaled, polygons_trimming);
}
if (! some_region_overlaps)
break;

File diff suppressed because it is too large Load Diff

View File

@ -32,8 +32,7 @@ struct Params {
const float min_distance_between_support_points = 3.0f; //mm
const float support_points_interface_radius = 1.5f; // mm
const float connections_min_considerable_area = 1.5f; //mm^2
const float small_parts_threshold = 5.0f; //mm^3
const float small_parts_support_points_interface_radius = 3.0f; // mm
const float min_distance_to_allow_local_supports = 2.0f; //mm
std::string filament_type;
const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position.
@ -61,11 +60,11 @@ struct Params {
};
struct SupportPoint {
SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec3f &direction);
SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction);
Vec3f position;
float force;
float spot_radius;
Vec3f direction;
Vec2f direction;
};
struct Issues {
@ -77,7 +76,7 @@ struct Malformations {
};
// std::vector<size_t> quick_search(const PrintObject *po, const Params &params);
std::tuple<Issues, Malformations> full_search(const PrintObject *po, const Params &params);
Issues full_search(const PrintObject *po, const Params &params);
void estimate_supports_malformations(SupportLayerPtrs &layers, float supports_flow_width, const Params &params);
void estimate_malformations(LayerPtrs &layers, const Params &params);

View File

@ -104,7 +104,7 @@ public:
};
typedef std::vector<Surface> Surfaces;
typedef std::vector<Surface*> SurfacesPtr;
typedef std::vector<const Surface*> SurfacesPtr;
inline Polygons to_polygons(const Surface &surface)
{
@ -221,6 +221,7 @@ inline void polygons_append(Polygons &dst, const SurfacesPtr &src)
}
}
/*
inline void polygons_append(Polygons &dst, SurfacesPtr &&src)
{
dst.reserve(dst.size() + number_polygons(src));
@ -230,6 +231,7 @@ inline void polygons_append(Polygons &dst, SurfacesPtr &&src)
(*it)->expolygon.holes.clear();
}
}
*/
// Append a vector of Surfaces at the end of another vector of polygons.
inline void surfaces_append(Surfaces &dst, const ExPolygons &src, SurfaceType surfaceType)

View File

@ -22,46 +22,45 @@ void SurfaceCollection::simplify(double tolerance)
}
/* group surfaces by common properties */
void SurfaceCollection::group(std::vector<SurfacesPtr> *retval)
void SurfaceCollection::group(std::vector<SurfacesPtr> *retval) const
{
for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) {
for (const Surface &surface : this->surfaces) {
// find a group with the same properties
SurfacesPtr* group = NULL;
SurfacesPtr *group = nullptr;
for (std::vector<SurfacesPtr>::iterator git = retval->begin(); git != retval->end(); ++git)
if (! git->empty() && surfaces_could_merge(*git->front(), *it)) {
if (! git->empty() && surfaces_could_merge(*git->front(), surface)) {
group = &*git;
break;
}
// if no group with these properties exists, add one
if (group == NULL) {
if (group == nullptr) {
retval->resize(retval->size() + 1);
group = &retval->back();
}
// append surface to group
group->push_back(&*it);
group->push_back(&surface);
}
}
SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type)
SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) const
{
SurfacesPtr ss;
for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
if (surface->surface_type == type) ss.push_back(&*surface);
}
for (const Surface &surface : this->surfaces)
if (surface.surface_type == type)
ss.push_back(&surface);
return ss;
}
SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes)
SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) const
{
SurfacesPtr ss;
for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
for (const Surface &surface : this->surfaces)
for (int i = 0; i < ntypes; ++ i) {
if (surface->surface_type == types[i]) {
ss.push_back(&*surface);
if (surface.surface_type == types[i]) {
ss.push_back(&surface);
break;
}
}
}
return ss;
}

View File

@ -17,7 +17,7 @@ public:
SurfaceCollection(Surfaces &&surfaces) : surfaces(std::move(surfaces)) {};
void simplify(double tolerance);
void group(std::vector<SurfacesPtr> *retval);
void group(std::vector<SurfacesPtr> *retval) const;
template <class T> bool any_internal_contains(const T &item) const {
for (const Surface &surface : this->surfaces) if (surface.is_internal() && surface.expolygon.contains(item)) return true;
return false;
@ -26,8 +26,8 @@ public:
for (const Surface &surface : this->surfaces) if (surface.is_bottom() && surface.expolygon.contains(item)) return true;
return false;
}
SurfacesPtr filter_by_type(const SurfaceType type);
SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes);
SurfacesPtr filter_by_type(const SurfaceType type) const;
SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes) const;
void keep_type(const SurfaceType type);
void keep_types(const SurfaceType *types, int ntypes);
void remove_type(const SurfaceType type);
@ -48,6 +48,13 @@ public:
return false;
}
Surfaces::const_iterator cbegin() const { return this->surfaces.cbegin(); }
Surfaces::const_iterator cend() const { return this->surfaces.cend(); }
Surfaces::const_iterator begin() const { return this->surfaces.cbegin(); }
Surfaces::const_iterator end() const { return this->surfaces.cend(); }
Surfaces::iterator begin() { return this->surfaces.begin(); }
Surfaces::iterator end() { return this->surfaces.end(); }
void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; }
void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); }
void set(const ExPolygons &src, SurfaceType surfaceType) { clear(); this->append(src, surfaceType); }

View File

@ -291,6 +291,42 @@ void TreeModelVolumes::precalculate(const coord_t max_layer)
// m_precalculated = true;
BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms.";
#if 0
// Paint caches into SVGs:
auto paint_cache_into_SVGs = [this](const RadiusLayerPolygonCache &cache, std::string_view name) {
const std::vector<std::pair<RadiusLayerPair, std::reference_wrapper<const Polygons>>> sorted = cache.sorted();
static constexpr const std::string_view colors[] = {
"red", "green", "blue", "magenta", "orange"
};
static constexpr const size_t num_colors = sizeof(colors) / sizeof(colors[0]);
for (size_t i = 0; i < sorted.size();) {
// Find range of cache items with the same layer index.
size_t j = i;
for (++ j; j < sorted.size() && sorted[i].first.second == sorted[j].first.second; ++ j) ;
// Collect expolygons in reverse order (largest to smallest).
std::vector<std::pair<Slic3r::ExPolygons, SVG::ExPolygonAttributes>> expolygons_with_attributes;
for (int k = int(j - 1); k >= int(i); -- k) {
std::string legend = format("radius-%1%", unscaled<float>(sorted[k].first.first));
expolygons_with_attributes.push_back({ union_ex(sorted[k].second), SVG::ExPolygonAttributes(legend, std::string(colors[(k - int(i)) % num_colors]), 1.) });
}
// Render the range of per radius collision polygons into a common SVG.
SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d.svg", name.data(), sorted[i].first.second), expolygons_with_attributes);
i = j;
}
};
paint_cache_into_SVGs(m_collision_cache, "collision_cache");
paint_cache_into_SVGs(m_collision_cache_holefree, "collision_cache_holefree");
paint_cache_into_SVGs(m_avoidance_cache, "avoidance_cache");
paint_cache_into_SVGs(m_avoidance_cache_slow, "avoidance_cache_slow");
paint_cache_into_SVGs(m_avoidance_cache_to_model, "avoidance_cache_to_model");
paint_cache_into_SVGs(m_avoidance_cache_to_model_slow, "avoidance_cache_to_model_slow");
paint_cache_into_SVGs(m_placeable_areas_cache, "placable_areas_cache");
paint_cache_into_SVGs(m_avoidance_cache_holefree, "avoidance_cache_holefree");
paint_cache_into_SVGs(m_avoidance_cache_holefree_to_model, "avoidance_cache_holefree_to_model");
paint_cache_into_SVGs(m_wall_restrictions_cache, "wall_restrictions_cache");
paint_cache_into_SVGs(m_wall_restrictions_cache_min, "wall_restrictions_cache_min");
#endif
}
const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const
@ -783,4 +819,14 @@ coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const
return out;
}
// For debugging purposes, sorted by layer index, then by radius.
std::vector<std::pair<TreeModelVolumes::RadiusLayerPair, std::reference_wrapper<const Polygons>>> TreeModelVolumes::RadiusLayerPolygonCache::sorted() const
{
std::vector<std::pair<RadiusLayerPair, std::reference_wrapper<const Polygons>>> out;
for (auto it = this->data.begin(); it != this->data.end(); ++ it)
out.emplace_back(it->first, it->second);
std::sort(out.begin(), out.end(), [](auto &l, auto &r){ return l.first.second < r.first.second || (l.first.second == r.first.second) && l.first.first < r.first.first; });
return out;
}
} // namespace Slic3r::FFFTreeSupport

View File

@ -366,6 +366,9 @@ private:
return max_layer;
}
// For debugging purposes, sorted by layer index, then by radius.
[[nodiscard]] std::vector<std::pair<RadiusLayerPair, std::reference_wrapper<const Polygons>>> sorted() const;
private:
RadiusLayerPolygonCacheData data;
mutable std::mutex mutex;

View File

@ -115,76 +115,6 @@ static inline void check_self_intersections(const ExPolygon &expoly, const std::
#endif // _WIN32
}
static inline void clip_for_diff(const Polygon &src, const BoundingBox &bbox, Polygon &out)
{
out.clear();
const size_t cnt = src.points.size();
if (cnt < 3)
return;
enum class Side {
Left = 1,
Right = 2,
Top = 4,
Bottom = 8
};
auto sides = [bbox](const Point &p) {
return int(p.x() < bbox.min.x()) * int(Side::Left) +
int(p.x() > bbox.max.x()) * int(Side::Right) +
int(p.y() < bbox.min.y()) * int(Side::Bottom) +
int(p.y() > bbox.max.y()) * int(Side::Top);
};
int sides_prev = sides(src.points.back());
int sides_this = sides(src.points.front());
const size_t last = cnt - 1;
for (size_t i = 0; i < last; ++ i) {
int sides_next = sides(src.points[i + 1]);
if (// This point is inside. Take it.
sides_this == 0 ||
// Either this point is outside and previous or next is inside, or
// the edge possibly cuts corner of the bounding box.
(sides_prev & sides_this & sides_next) == 0) {
out.points.emplace_back(src.points[i]);
sides_prev = sides_this;
} else {
// All the three points (this, prev, next) are outside at the same side.
// Ignore this point.
}
sides_this = sides_next;
}
// For the last point, if src is completely outside bbox, then out.points will be empty. Just use the first point instead.
int sides_next = sides(out.points.empty() ? src.points.front() : out.points.front());
if (// The last point is inside. Take it.
sides_this == 0 ||
// Either this point is outside and previous or next is inside, or
// the edge possibly cuts corner of the bounding box.
(sides_prev & sides_this & sides_next) == 0)
out.points.emplace_back(src.points.back());
}
[[nodiscard]] static inline Polygon clip_for_diff(const Polygon &src, const BoundingBox &bbox)
{
Polygon out;
clip_for_diff(src, bbox, out);
return out;
}
[[nodiscard]] static inline Polygons clip_for_diff(const Polygons &src, const BoundingBox &bbox)
{
Polygons out;
out.reserve(src.size());
for (const Polygon &p : src)
out.emplace_back(clip_for_diff(p, bbox));
return out;
}
[[nodiscard]] static inline Polygons diff_clipped(const Polygons &src, const Polygons &clipping)
{
return diff(src, clip_for_diff(clipping, get_extents(src).inflated(SCALED_EPSILON)));
}
static constexpr const auto tiny_area_threshold = sqr(scaled<double>(0.001));
static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_meshes(const Print &print, const std::vector<size_t> &print_object_ids)
@ -821,7 +751,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
Polygons collision_trimmed_buffer;
auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons& {
if (collision_trimmed_buffer.empty() && ! collision.empty())
collision_trimmed_buffer = clip_for_diff(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON));
collision_trimmed_buffer = ClipperUtils::clip_clipper_polygons_with_subject_bbox(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON));
return collision_trimmed_buffer;
};
@ -3798,10 +3728,10 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume
for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) {
Polygons polys;
for (auto& area : move_bounds[layer_idx])
append(polys, area->influence_area);
append(polys, area.influence_area);
if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end())
SVG::export_expolygons(debug_out_path("treesupport-initial_areas-%d.svg", layer_idx),
{ { { union_ex(volumes.getWallRestriction(config.getCollisionRadius((*begin)->state), layer_idx, (*begin)->state.use_min_xy_dist)) },
{ { { union_ex(volumes.getWallRestriction(config.getCollisionRadius(begin->state), layer_idx, begin->state.use_min_xy_dist)) },
{ "wall_restricrictions", "gray", 0.5f } },
{ { union_ex(polys) }, { "parent", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
}

View File

@ -284,7 +284,7 @@ void TriangleMesh::rotate(float angle, const Axis &axis)
case Z: its_rotate_z(this->its, angle); break;
default: assert(false); return;
}
update_bounding_box(this->its, this->m_stats);
update_bounding_box(this->its, m_stats);
}
}
@ -295,7 +295,7 @@ void TriangleMesh::rotate(float angle, const Vec3d& axis)
Transform3d m = Transform3d::Identity();
m.rotate(Eigen::AngleAxisd(angle, axis_norm));
its_transform(its, m);
update_bounding_box(this->its, this->m_stats);
update_bounding_box(this->its, m_stats);
}
}
@ -334,7 +334,7 @@ void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed)
det = -det;
}
m_stats.volume *= det;
update_bounding_box(this->its, this->m_stats);
update_bounding_box(this->its, m_stats);
}
void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed)
@ -346,7 +346,7 @@ void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed)
det = -det;
}
m_stats.volume *= det;
update_bounding_box(this->its, this->m_stats);
update_bounding_box(this->its, m_stats);
}
void TriangleMesh::flip_triangles()
@ -512,7 +512,7 @@ std::vector<ExPolygons> TriangleMesh::slice(const std::vector<double> &z) const
size_t TriangleMesh::memsize() const
{
size_t memsize = 8 + this->its.memsize() + sizeof(this->m_stats);
size_t memsize = 8 + this->its.memsize() + sizeof(m_stats);
return memsize;
}

View File

@ -92,7 +92,7 @@ public:
TriangleMesh(std::vector<Vec3f> &&vertices, const std::vector<Vec3i> &&faces);
explicit TriangleMesh(const indexed_triangle_set &M);
explicit TriangleMesh(indexed_triangle_set &&M, const RepairedMeshErrors& repaired_errors = RepairedMeshErrors());
void clear() { this->its.clear(); this->m_stats.clear(); }
void clear() { this->its.clear(); m_stats.clear(); }
bool ReadSTLFile(const char* input_file, bool repair = true);
bool write_ascii(const char* output_file);
bool write_binary(const char* output_file);

View File

@ -371,7 +371,7 @@ std::pair<std::vector<Vec3i>, std::vector<Vec3i>> TriangleSelector::precompute_a
{
std::vector<Vec3i> neighbors(m_triangles.size(), Vec3i(-1, -1, -1));
std::vector<Vec3i> neighbors_propagated(m_triangles.size(), Vec3i(-1, -1, -1));
for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) {
for (int facet_idx = 0; facet_idx < m_orig_size_indices; ++facet_idx) {
neighbors[facet_idx] = m_neighbors[facet_idx];
neighbors_propagated[facet_idx] = neighbors[facet_idx];
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors[facet_idx]));
@ -1480,7 +1480,7 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const
std::vector<Vec2i> TriangleSelector::get_seed_fill_contour() const {
std::vector<Vec2i> edges_out;
for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) {
for (int facet_idx = 0; facet_idx < m_orig_size_indices; ++facet_idx) {
const Vec3i neighbors = m_neighbors[facet_idx];
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
this->get_seed_fill_contour_recursive(facet_idx, neighbors, neighbors, edges_out);

View File

@ -33,12 +33,13 @@
#include "Technologies.hpp"
#include "Semver.hpp"
using coord_t =
#if 1
// Saves around 32% RAM after slicing step, 6.7% after G-code export (tested on PrusaSlicer 2.2.0 final).
using coord_t = int32_t;
int32_t;
#else
//FIXME At least FillRectilinear2 and std::boost Voronoi require coord_t to be 32bit.
typedef int64_t coord_t;
int64_t;
#endif
using coordf_t = double;
@ -381,4 +382,4 @@ inline IntegerOnly<I, I> fast_round_up(double a)
} // namespace Slic3r
#endif
#endif // _libslic3r_h_

View File

@ -1984,7 +1984,7 @@ std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru
std::set<int> used_extruders;
auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(print_z));
auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), print_z, [](const LayerTools &lhs, double rhs){ return lhs.print_z < rhs; });
for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools) {
const std::vector<unsigned>& extruders = it_layer_tools->extruders;
for (const auto& extruder : extruders)

View File

@ -7226,7 +7226,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
if (is_selected_separate_extruder) {
bool at_least_one_has_correct_extruder = false;
for (const LayerRegion* layerm : layer->regions()) {
if (layerm->slices.surfaces.empty())
if (layerm->slices().empty())
continue;
const PrintRegionConfig& cfg = layerm->region().config();
if (cfg.perimeter_extruder.value == m_selected_extruder ||
@ -7269,14 +7269,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
}
if (ctxt.has_perimeters)
#if ENABLE_LEGACY_OPENGL_REMOVAL
_3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
_3DScene::extrusionentity_to_verts(layerm->perimeters(), float(layer->print_z), copy,
select_geometry(idx_layer, layerm->region().config().perimeter_extruder.value, 0));
#else
_3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
_3DScene::extrusionentity_to_verts(layerm->perimeters(), float(layer->print_z), copy,
volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0));
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
if (ctxt.has_infill) {
for (const ExtrusionEntity *ee : layerm->fills.entities) {
for (const ExtrusionEntity *ee : layerm->fills()) {
// fill represents infill extrusions of a single island.
const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (! fill->entities.empty())

View File

@ -1158,18 +1158,18 @@ void TriangleSelectorGUI::update_render_data()
#if !ENABLE_LEGACY_OPENGL_REMOVAL
void GLPaintContour::render() const
{
assert(this->m_contour_VBO_id != 0);
assert(this->m_contour_EBO_id != 0);
assert(m_contour_VBO_id != 0);
assert(m_contour_EBO_id != 0);
glsafe(::glLineWidth(4.0f));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_contour_VBO_id));
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
if (this->contour_indices_size > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_contour_EBO_id));
glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
@ -1181,20 +1181,20 @@ void GLPaintContour::render() const
void GLPaintContour::finalize_geometry()
{
assert(this->m_contour_VBO_id == 0);
assert(this->m_contour_EBO_id == 0);
assert(m_contour_VBO_id == 0);
assert(m_contour_EBO_id == 0);
if (!this->contour_vertices.empty()) {
glsafe(::glGenBuffers(1, &this->m_contour_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id));
glsafe(::glGenBuffers(1, &m_contour_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_contour_VBO_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
this->contour_vertices.clear();
}
if (!this->contour_indices.empty()) {
glsafe(::glGenBuffers(1, &this->m_contour_EBO_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id));
glsafe(::glGenBuffers(1, &m_contour_EBO_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_contour_EBO_id));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
this->contour_indices.clear();
@ -1203,13 +1203,13 @@ void GLPaintContour::finalize_geometry()
void GLPaintContour::release_geometry()
{
if (this->m_contour_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id));
this->m_contour_VBO_id = 0;
if (m_contour_VBO_id) {
glsafe(::glDeleteBuffers(1, &m_contour_VBO_id));
m_contour_VBO_id = 0;
}
if (this->m_contour_EBO_id) {
glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id));
this->m_contour_EBO_id = 0;
if (m_contour_EBO_id) {
glsafe(::glDeleteBuffers(1, &m_contour_EBO_id));
m_contour_EBO_id = 0;
}
this->clear();
}

View File

@ -41,7 +41,7 @@ public:
void render() const;
inline bool has_VBO() const { return this->m_contour_EBO_id != 0; }
inline bool has_VBO() const { return m_contour_EBO_id != 0; }
// Release the geometry data, release OpenGL VBOs.
void release_geometry();

View File

@ -772,11 +772,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
// get index of the last VolumeItem in CildrenList
size_t vol_idx = GetItemIndexForFirstVolume(node_parent);
ObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx);
// if last volume is text then don't delete it
if (last_child_node->is_text_volume())
return parent;
// delete this last volume
DeleteSettings(wxDataViewItem(last_child_node));

View File

@ -101,7 +101,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
this->back_to_initial_value(opt_id);
};
field->m_back_to_sys_value = [this](std::string opt_id) {
if (!this->m_disabled)
if (!m_disabled)
this->back_to_sys_value(opt_id);
};

View File

@ -168,7 +168,7 @@ bool AppUpdater::priv::http_get_file(const std::string& url, size_t size_limit,
.size_limit(size_limit)
.on_progress([&, progress_fn](Http::Progress progress, bool& cancel) {
// progress function returns true as success (to continue)
cancel = (this->m_cancel ? true : !progress_fn(std::move(progress)));
cancel = (m_cancel ? true : !progress_fn(std::move(progress)));
if (cancel) {
error_message = GUI::format("Error getting: `%1%`: Download was canceled.", //lm:typo //dk: am i blind? :)
url);
@ -259,7 +259,7 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d
);
if (!res)
{
if (this->m_cancel)
if (m_cancel)
{
BOOST_LOG_TRIVIAL(info) << error_message; //lm:Is this an error? // dk: changed to info
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); // FAILED with empty msg only closes progress notification

View File

@ -1,110 +0,0 @@
use Test::More;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use List::Util qw(first);
use Slic3r;
use Slic3r::Surface ':types';
use Slic3r::Test;
plan tests => 8;
{
my $test = sub {
my ($config) = @_;
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
ok my $gcode = Slic3r::Test::gcode($print), "infill_every_layers does not crash";
my $tool = undef;
my %layers = (); # layer_z => 1
my %layer_infill = (); # layer_z => has_infill
Slic3r::GCode::Reader->new->parse($gcode, sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$tool = $1;
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0 && $tool != $config->support_material_extruder-1) {
$layer_infill{$self->Z} //= 0;
if ($tool == $config->infill_extruder-1) {
$layer_infill{$self->Z} = 1;
}
}
# Previously, all G-code commands had a fixed number of decimal points with means with redundant zeros after decimal points.
# We changed this behavior and got rid of these redundant padding zeros, which caused this test to fail
# because the position in Z-axis is compared as a string, and previously, G-code contained the following two commands:
# "G1 Z5 F5000 ; lift nozzle"
# "G1 Z5.000 F7800.000"
# That has a different Z-axis position from the view of string comparisons of floating-point numbers.
# To correct the computation of the number of printed layers, even in the case of string comparisons of floating-point numbers,
# we filtered out the G-code command with the commend 'lift nozzle'.
$layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} && index($info->{comment}, 'lift nozzle') == -1;
});
my $layers_with_perimeters = scalar(keys %layer_infill);
my $layers_with_infill = grep $_ > 0, values %layer_infill;
is scalar(keys %layers), $layers_with_perimeters+$config->raft_layers, 'expected number of layers';
if ($config->raft_layers == 0) {
# first infill layer printed directly on print bed is not combined, so we don't consider it.
$layers_with_infill--;
$layers_with_perimeters--;
}
# we expect that infill is generated for half the number of combined layers
# plus for each single layer that was not combined (remainder)
is $layers_with_infill,
int($layers_with_perimeters/$config->infill_every_layers) + ($layers_with_perimeters % $config->infill_every_layers),
'infill is only present in correct number of layers';
};
my $config = Slic3r::Config::new_from_defaults;
$config->set('layer_height', 0.2);
$config->set('first_layer_height', 0.2);
$config->set('nozzle_diameter', [0.5,0.5,0.5,0.5]);
$config->set('infill_every_layers', 2);
$config->set('perimeter_extruder', 1);
$config->set('infill_extruder', 2);
$config->set('wipe_into_infill', 0);
$config->set('support_material_extruder', 3);
$config->set('support_material_interface_extruder', 3);
$config->set('top_solid_layers', 0);
$config->set('bottom_solid_layers', 0);
$test->($config);
$config->set('skirts', 0); # prevent usage of perimeter_extruder in raft layers
$config->set('raft_layers', 5);
$test->($config);
}
{
my $config = Slic3r::Config::new_from_defaults;
$config->set('layer_height', 0.2);
$config->set('first_layer_height', 0.2);
$config->set('nozzle_diameter', [0.5]);
$config->set('infill_every_layers', 2);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
$print->process;
ok defined(first { @{$_->get_region(0)->fill_surfaces->filter_by_type(S_TYPE_INTERNALVOID)} > 0 }
@{$print->print->get_object(0)->layers}),
'infill combination produces internal void surfaces';
# we disable combination after infill has been generated
$config->set('infill_every_layers', 1);
$print->apply($print->print->model->clone, $config);
$print->process;
ok !(defined first { @{$_->get_region(0)->fill_surfaces} == 0 }
@{$print->print->get_object(0)->layers}),
'infill combination is idempotent';
}
__END__

View File

@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
plan tests => 11;
plan tests => 10;
BEGIN {
use FindBin;
@ -57,11 +57,11 @@ my $polygons = [
#==========================================================
{
my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), [0, 1], [10, 2], [20, 2] ]);
$bb->scale(2);
is_deeply [ $bb->min_point->pp, $bb->max_point->pp ], [ [0,2], [40,4] ], 'bounding box is scaled correctly';
}
#{
# my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), [0, 1], [10, 2], [20, 2] ]);
# $bb->scale(2);
# is_deeply [ $bb->min_point->pp, $bb->max_point->pp ], [ [0,2], [40,4] ], 'bounding box is scaled correctly';
#}
#==========================================================

View File

@ -1,4 +1,4 @@
use Test::More tests => 20;
use Test::More tests => 17;
use strict;
use warnings;
@ -13,73 +13,6 @@ use Slic3r;
use Slic3r::Geometry qw(epsilon);
use Slic3r::Test;
{
my $config = Slic3r::Config::new_from_defaults;
$config->set('skirts', 0);
$config->set('perimeters', 0);
$config->set('solid_infill_speed', 99);
$config->set('top_solid_infill_speed', 99);
$config->set('bridge_speed', 72);
$config->set('first_layer_speed', '100%');
$config->set('cooling', [ 0 ]);
my $test = sub {
my ($conf) = @_;
$conf ||= $config;
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my %z = (); # Z => 1
my %layers_with_solid_infill = (); # Z => $count
my %layers_with_bridge_infill = (); # Z => $count
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($self->Z > 0) {
$z{ $self->Z } = 1;
if ($info->{extruding} && $info->{dist_XY} > 0) {
my $F = $args->{F} // $self->F;
$layers_with_solid_infill{$self->Z} = 1
if $F == $config->solid_infill_speed*60;
$layers_with_bridge_infill{$self->Z} = 1
if $F == $config->bridge_speed*60;
}
}
});
my @z = sort { $a <=> $b } keys %z;
my @shells = map $layers_with_solid_infill{$_} || $layers_with_bridge_infill{$_}, @z;
fail "insufficient number of bottom solid layers"
unless !defined(first { !$_ } @shells[0..$config->bottom_solid_layers-1]);
fail "excessive number of bottom solid layers"
unless scalar(grep $_, @shells[0 .. $#shells/2]) == $config->bottom_solid_layers;
fail "insufficient number of top solid layers"
unless !defined(first { !$_ } @shells[-$config->top_solid_layers..-1]);
fail "excessive number of top solid layers"
unless scalar(grep $_, @shells[($#shells/2)..$#shells]) == $config->top_solid_layers;
if ($config->top_solid_layers > 0) {
fail "unexpected solid infill speed in first solid layer over sparse infill"
if $layers_with_solid_infill{ $z[-$config->top_solid_layers] };
die "bridge speed not used in first solid layer over sparse infill"
if !$layers_with_bridge_infill{ $z[-$config->top_solid_layers] };
}
1;
};
$config->set('top_solid_layers', 3);
$config->set('bottom_solid_layers', 3);
ok $test->(), "proper number of shells is applied";
$config->set('top_solid_layers', 0);
$config->set('bottom_solid_layers', 0);
ok $test->(), "no shells are applied when both top and bottom are set to zero";
$config->set('perimeters', 1);
$config->set('top_solid_layers', 3);
$config->set('bottom_solid_layers', 3);
$config->set('fill_density', 0);
ok $test->(), "proper number of shells is applied even when fill density is none";
}
# issue #1161
{
my $config = Slic3r::Config::new_from_defaults;

View File

@ -21,6 +21,7 @@ add_executable(${_TEST_NAME}_tests
test_print.cpp
test_printgcode.cpp
test_printobject.cpp
test_shells.cpp
test_skirt_brim.cpp
test_support_material.cpp
test_thin_walls.cpp

View File

@ -2,25 +2,115 @@
#include "test_data.hpp"
#include "clipper/clipper_z.hpp"
#include "libslic3r/clipper.hpp"
using namespace Slic3r;
// Test case for an issue with duplicity vertices (same XY coordinates but differ in Z coordinates) in Clipper 6.2.9,
// (related to https://sourceforge.net/p/polyclipping/bugs/160/) that was fixed in Clipper 6.4.2.
// tests for ExPolygon::overlaps(const ExPolygon &other)
SCENARIO("Clipper intersection with polyline", "[Clipper]")
{
struct TestData {
ClipperLib::Path subject;
ClipperLib::Path clip;
ClipperLib::Paths result;
};
auto run_test = [](const TestData &data) {
ClipperLib::Clipper clipper;
clipper.AddPath(data.subject, ClipperLib::ptSubject, false);
clipper.AddPath(data.clip, ClipperLib::ptClip, true);
ClipperLib::PolyTree polytree;
ClipperLib::Paths paths;
clipper.Execute(ClipperLib::ctIntersection, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
ClipperLib::PolyTreeToPaths(polytree, paths);
REQUIRE(paths == data.result);
};
WHEN("Open polyline completely inside stays inside") {
run_test({
{ { 10, 0 }, { 20, 0 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ { { 20, 0 }, { 10, 0 } } }
});
};
WHEN("Closed polyline completely inside stays inside") {
run_test({
{ { 10, 0 }, { 20, 0 }, { 20, 20 }, { 10, 20 }, { 10, 0 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ { { 10, 0 }, { 20, 0 }, { 20, 20 }, { 10, 20 }, { 10, 0 } } }
});
};
WHEN("Polyline which crosses right rectangle boundary is trimmed") {
run_test({
{ { 10, 0 }, { 2000, 0 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ { { 1000, 0 }, { 10, 0 } } }
});
};
WHEN("Polyline which is outside clipping region is removed") {
run_test({
{ { 1500, 0 }, { 2000, 0 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ }
});
};
WHEN("Polyline on left vertical boundary is kept") {
run_test({
{ { -1000, -1000 }, { -1000, 1000 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ { { -1000, -1000 }, { -1000, 1000 } } }
});
run_test({
{ { -1000, 1000 }, { -1000, -1000 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ { { -1000, 1000 }, { -1000, -1000 } } }
});
};
WHEN("Polyline on right vertical boundary is kept") {
run_test({
{ { 1000, -1000 }, { 1000, 1000 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ { { 1000, -1000 }, { 1000, 1000 } } }
});
run_test({
{ { 1000, 1000 }, { 1000, -1000 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ { { 1000, 1000 }, { 1000, -1000 } } }
});
};
WHEN("Polyline on bottom horizontal boundary is removed") {
run_test({
{ { -1000, -1000 }, { 1000, -1000 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ }
});
run_test({
{ { 1000, -1000 }, { -1000, -1000 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ }
});
};
WHEN("Polyline on top horizontal boundary is removed") {
run_test({
{ { -1000, 1000 }, { 1000, 1000 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ }
});
run_test({
{ { 1000, 1000 }, { -1000, 1000 } },
{ { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } },
{ }
});
};
}
SCENARIO("Clipper Z", "[ClipperZ]")
{
ClipperLib_Z::Path subject;
subject.emplace_back(-2000, -1000, 10);
subject.emplace_back(-2000, 1000, 10);
subject.emplace_back( 2000, 1000, 10);
subject.emplace_back( 2000, -1000, 10);
ClipperLib_Z::Path clip;
clip.emplace_back(-1000, -2000, -5);
clip.emplace_back(-1000, 2000, -5);
clip.emplace_back( 1000, 2000, -5);
clip.emplace_back( 1000, -2000, -5);
ClipperLib_Z::Path subject { { -2000, -1000, 10 }, { -2000, 1000, 10 }, { 2000, 1000, 10 }, { 2000, -1000, 10 } };
ClipperLib_Z::Path clip{ { -1000, -2000, -5 }, { -1000, 2000, -5 }, { 1000, 2000, -5 }, { 1000, -2000, -5 } };
ClipperLib_Z::Clipper clipper;
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot,
@ -41,3 +131,4 @@ SCENARIO("Clipper Z", "[ClipperZ]")
for (const ClipperLib_Z::IntPoint &pt : paths.front())
REQUIRE(pt.z() == 1);
}

View File

@ -35,7 +35,254 @@ static Slic3r::ExtrusionPaths random_paths(size_t count = 10, size_t length = 20
return p;
}
SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]") {
SCENARIO("ExtrusionPath", "[ExtrusionEntity]") {
GIVEN("Simple path") {
Slic3r::ExtrusionPath path{ erExternalPerimeter };
path.polyline = { { 100, 100 }, { 200, 100 }, { 200, 200 } };
path.mm3_per_mm = 1.;
THEN("first point") {
REQUIRE(path.first_point() == path.polyline.front());
}
THEN("cloned") {
auto cloned = std::unique_ptr<ExtrusionEntity>(path.clone());
REQUIRE(cloned->role() == path.role());
}
}
}
static ExtrusionPath new_extrusion_path(const Polyline &polyline, ExtrusionRole role, double mm3_per_mm)
{
ExtrusionPath path(role);
path.polyline = polyline;
path.mm3_per_mm = 1.;
return path;
}
SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
{
GIVEN("Square") {
Polygon square { { 100, 100 }, { 200, 100 }, { 200, 200 }, { 100, 200 } };
ExtrusionLoop loop;
loop.paths.emplace_back(new_extrusion_path(square.split_at_first_point(), erExternalPerimeter, 1.));
THEN("polygon area") {
REQUIRE(loop.polygon().area() == Approx(square.area()));
}
THEN("loop length") {
REQUIRE(loop.length() == Approx(square.length()));
}
WHEN("cloned") {
auto loop2 = std::unique_ptr<ExtrusionLoop>(dynamic_cast<ExtrusionLoop*>(loop.clone()));
THEN("cloning worked") {
REQUIRE(loop2 != nullptr);
}
THEN("loop contains one path") {
REQUIRE(loop2->paths.size() == 1);
}
THEN("cloned role") {
REQUIRE(loop2->paths.front().role() == erExternalPerimeter);
}
}
WHEN("cloned and split") {
auto loop2 = std::unique_ptr<ExtrusionLoop>(dynamic_cast<ExtrusionLoop*>(loop.clone()));
loop2->split_at_vertex(square.points[2]);
THEN("splitting a single-path loop results in a single path") {
REQUIRE(loop2->paths.size() == 1);
}
THEN("path has correct number of points") {
REQUIRE(loop2->paths.front().size() == 5);
}
THEN("expected point order") {
REQUIRE(loop2->paths.front().polyline[0] == square.points[2]);
REQUIRE(loop2->paths.front().polyline[1] == square.points[3]);
REQUIRE(loop2->paths.front().polyline[2] == square.points[0]);
REQUIRE(loop2->paths.front().polyline[3] == square.points[1]);
REQUIRE(loop2->paths.front().polyline[4] == square.points[2]);
}
}
}
GIVEN("Loop with two pieces") {
Polyline polyline1 { { 100, 100 }, { 200, 100 }, { 200, 200 } };
Polyline polyline2 { { 200, 200 }, { 100, 200 }, { 100, 100 } };
ExtrusionLoop loop;
loop.paths.emplace_back(new_extrusion_path(polyline1, erExternalPerimeter, 1.));
loop.paths.emplace_back(new_extrusion_path(polyline2, erOverhangPerimeter, 1.));
double tot_len = polyline1.length() + polyline2.length();
THEN("length") {
REQUIRE(loop.length() == Approx(tot_len));
}
WHEN("splitting at intermediate point") {
auto loop2 = std::unique_ptr<ExtrusionLoop>(dynamic_cast<ExtrusionLoop*>(loop.clone()));
loop2->split_at_vertex(polyline1.points[1]);
THEN("length after splitting is unchanged") {
REQUIRE(loop2->length() == Approx(tot_len));
}
THEN("loop contains three paths after splitting") {
REQUIRE(loop2->paths.size() == 3);
}
THEN("expected starting point") {
REQUIRE(loop2->paths.front().polyline.front() == polyline1.points[1]);
}
THEN("expected ending point") {
REQUIRE(loop2->paths.back().polyline.back() == polyline1.points[1]);
}
THEN("paths have common point") {
REQUIRE(loop2->paths.front().polyline.back() == loop2->paths[1].polyline.front());
REQUIRE(loop2->paths[1].polyline.back() == loop2->paths[2].polyline.front());
}
THEN("expected order after splitting") {
REQUIRE(loop2->paths.front().role() == erExternalPerimeter);
REQUIRE(loop2->paths[1].role() == erOverhangPerimeter);
REQUIRE(loop2->paths[2].role() == erExternalPerimeter);
}
THEN("path has correct number of points") {
REQUIRE(loop2->paths.front().polyline.size() == 2);
REQUIRE(loop2->paths[1].polyline.size() == 3);
REQUIRE(loop2->paths[2].polyline.size() == 2);
}
THEN("clipped path has expected length") {
double l = loop2->length();
ExtrusionPaths paths;
loop2->clip_end(3, &paths);
double l2 = 0;
for (const ExtrusionPath &p : paths)
l2 += p.length();
REQUIRE(l2 == Approx(l - 3.));
}
}
WHEN("splitting at endpoint") {
auto loop2 = std::unique_ptr<ExtrusionLoop>(dynamic_cast<ExtrusionLoop*>(loop.clone()));
loop2->split_at_vertex(polyline2.points.front());
THEN("length after splitting is unchanged") {
REQUIRE(loop2->length() == Approx(tot_len));
}
THEN("loop contains two paths after splitting") {
REQUIRE(loop2->paths.size() == 2);
}
THEN("expected starting point") {
REQUIRE(loop2->paths.front().polyline.front() == polyline2.points.front());
}
THEN("expected ending point") {
REQUIRE(loop2->paths.back().polyline.back() == polyline2.points.front());
}
THEN("paths have common point") {
REQUIRE(loop2->paths.front().polyline.back() == loop2->paths[1].polyline.front());
REQUIRE(loop2->paths[1].polyline.back() == loop2->paths.front().polyline.front());
}
THEN("expected order after splitting") {
REQUIRE(loop2->paths.front().role() == erOverhangPerimeter);
REQUIRE(loop2->paths[1].role() == erExternalPerimeter);
}
THEN("path has correct number of points") {
REQUIRE(loop2->paths.front().polyline.size() == 3);
REQUIRE(loop2->paths[1].polyline.size() == 3);
}
}
WHEN("splitting at an edge") {
Point point(250, 150);
auto loop2 = std::unique_ptr<ExtrusionLoop>(dynamic_cast<ExtrusionLoop*>(loop.clone()));
loop2->split_at(point, false, 0);
THEN("length after splitting is unchanged") {
REQUIRE(loop2->length() == Approx(tot_len));
}
Point expected_start_point(200, 150);
THEN("expected starting point") {
REQUIRE(loop2->paths.front().polyline.front() == expected_start_point);
}
THEN("expected ending point") {
REQUIRE(loop2->paths.back().polyline.back() == expected_start_point);
}
}
}
GIVEN("Loop with four pieces") {
Polyline polyline1 { { 59312736, 4821067 }, { 64321068, 4821067 }, { 64321068, 4821067 }, { 64321068, 9321068 }, { 59312736, 9321068 } };
Polyline polyline2 { { 59312736, 9321068 }, { 9829401, 9321068 } };
Polyline polyline3 { { 9829401, 9321068 }, { 4821067, 9321068 }, { 4821067, 4821067 }, { 9829401, 4821067 } };
Polyline polyline4 { { 9829401, 4821067 }, { 59312736,4821067 } };
ExtrusionLoop loop;
loop.paths.emplace_back(new_extrusion_path(polyline1, erExternalPerimeter, 1.));
loop.paths.emplace_back(new_extrusion_path(polyline2, erOverhangPerimeter, 1.));
loop.paths.emplace_back(new_extrusion_path(polyline3, erExternalPerimeter, 1.));
loop.paths.emplace_back(new_extrusion_path(polyline4, erOverhangPerimeter, 1.));
double len = loop.length();
WHEN("splitting at vertex") {
Point point(4821067, 9321068);
if (! loop.split_at_vertex(point))
loop.split_at(point, false, 0);
THEN("total length is preserved after splitting") {
REQUIRE(loop.length() == Approx(len));
}
THEN("order is correctly preserved after splitting") {
REQUIRE(loop.paths.front().role() == erExternalPerimeter);
REQUIRE(loop.paths[1].role() == erOverhangPerimeter);
REQUIRE(loop.paths[2].role() == erExternalPerimeter);
REQUIRE(loop.paths[3].role() == erOverhangPerimeter);
}
}
}
GIVEN("Some complex loop") {
ExtrusionLoop loop;
loop.paths.emplace_back(new_extrusion_path(
Polyline { { 15896783, 15868739 }, { 24842049, 12117558 }, { 33853238, 15801279 }, { 37591780, 24780128 }, { 37591780, 24844970 },
{ 33853231, 33825297 }, { 24842049, 37509013 }, { 15896798, 33757841 }, { 12211841, 24812544 }, { 15896783, 15868739 } },
erExternalPerimeter, 1.));
double len = loop.length();
THEN("split_at() preserves total length") {
loop.split_at({ 15896783, 15868739 }, false, 0);
REQUIRE(loop.length() == Approx(len));
}
}
}
SCENARIO("ExtrusionEntityCollection: Basics", "[ExtrusionEntity]")
{
Polyline polyline { { 100, 100 }, { 200, 100 }, { 200, 200 } };
ExtrusionPath path = new_extrusion_path(polyline, erExternalPerimeter, 1.);
ExtrusionLoop loop;
loop.paths.emplace_back(new_extrusion_path(Polygon(polyline.points).split_at_first_point(), erInternalInfill, 1.));
ExtrusionEntityCollection collection;
collection.append(path);
THEN("no_sort is false by default") {
REQUIRE(! collection.no_sort);
}
collection.append(collection);
THEN("append ExtrusionEntityCollection") {
REQUIRE(collection.entities.size() == 2);
}
collection.append(path);
THEN("append ExtrusionPath") {
REQUIRE(collection.entities.size() == 3);
}
collection.append(loop);
THEN("append ExtrusionLoop") {
REQUIRE(collection.entities.size() == 4);
}
THEN("appended collection was duplicated") {
REQUIRE(dynamic_cast<ExtrusionEntityCollection*>(collection.entities[1])->entities.size() == 1);
}
WHEN("cloned") {
auto coll2 = std::unique_ptr<ExtrusionEntityCollection>(dynamic_cast<ExtrusionEntityCollection*>(collection.clone()));
THEN("expected no_sort value") {
assert(! coll2->no_sort);
}
coll2->no_sort = true;
THEN("no_sort is kept after clone") {
auto coll3 = std::unique_ptr<ExtrusionEntityCollection>(dynamic_cast<ExtrusionEntityCollection*>(coll2->clone()));
assert(coll3->no_sort);
}
}
}
SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]")
{
srand(0xDEADBEEF); // consistent seed for test reproducibility.
// Generate one specific random path set and save it for later comparison
@ -132,6 +379,7 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") {
ExtrusionEntityCollection unchained_extrusions;
extrusion_entities_append_paths(unchained_extrusions.entities, test.unchained,
erInternalInfill, 0., 0.4f, 0.3f);
THEN("Chaining works") {
ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point);
REQUIRE(chained_extrusions.entities.size() == test.chained.size());
for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) {
@ -140,6 +388,17 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") {
REQUIRE(p1 == p2);
}
}
THEN("Chaining produces no change with no_sort") {
unchained_extrusions.no_sort = true;
ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point);
REQUIRE(chained_extrusions.entities.size() == test.unchained.size());
for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) {
const Points &p1 = test.unchained[i].points;
const Points &p2 = dynamic_cast<const ExtrusionPath*>(chained_extrusions.entities[i])->polyline.points;
REQUIRE(p1 == p2);
}
}
}
}
TEST_CASE("ExtrusionEntityCollection: Chained path with no explicit starting point", "[ExtrusionEntity]") {

View File

@ -3,14 +3,17 @@
#include <numeric>
#include <sstream>
#include "libslic3r/libslic3r.h"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Fill/Fill.hpp"
#include "libslic3r/Flow.hpp"
#include "libslic3r/Layer.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/SVG.hpp"
#include "libslic3r/libslic3r.h"
#include "test_data.hpp"
@ -329,6 +332,130 @@ SCENARIO("Infill only where needed", "[Fill]")
}
}
SCENARIO("Combine infill", "[Fill]")
{
{
auto test = [](const DynamicPrintConfig &config) {
std::string gcode = Test::slice({ Test::TestMesh::cube_20x20x20 }, config);
THEN("infill_every_layers does not crash") {
REQUIRE(! gcode.empty());
}
Slic3r::GCodeReader parser;
int tool = -1;
std::set<coord_t> layers; // layer_z => 1
std::map<coord_t, bool> layer_infill; // layer_z => has_infill
const int infill_extruder = config.opt_int("infill_extruder");
const int support_material_extruder = config.opt_int("support_material_extruder");
parser.parse_buffer(gcode,
[&tool, &layers, &layer_infill, infill_extruder, support_material_extruder](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
{
coord_t z = line.new_Z(self) / SCALING_FACTOR;
if (boost::starts_with(line.cmd(), "T")) {
tool = atoi(line.cmd().data() + 1);
} else if (line.cmd_is("G1") && line.extruding(self) && line.dist_XY(self) > 0 && tool + 1 != support_material_extruder) {
if (tool + 1 == infill_extruder)
layer_infill[z] = true;
else if (auto it = layer_infill.find(z); it == layer_infill.end())
layer_infill.insert(it, std::make_pair(z, false));
}
// Previously, all G-code commands had a fixed number of decimal points with means with redundant zeros after decimal points.
// We changed this behavior and got rid of these redundant padding zeros, which caused this test to fail
// because the position in Z-axis is compared as a string, and previously, G-code contained the following two commands:
// "G1 Z5 F5000 ; lift nozzle"
// "G1 Z5.000 F7800.000"
// That has a different Z-axis position from the view of string comparisons of floating-point numbers.
// To correct the computation of the number of printed layers, even in the case of string comparisons of floating-point numbers,
// we filtered out the G-code command with the commend 'lift nozzle'.
if (line.cmd_is("G1") && line.dist_Z(self) != 0 && line.comment().find("lift nozzle") == std::string::npos)
layers.insert(z);
});
auto layers_with_perimeters = int(layer_infill.size());
auto layers_with_infill = int(std::count_if(layer_infill.begin(), layer_infill.end(), [](auto &v){ return v.second; }));
THEN("expected number of layers") {
REQUIRE(layers.size() == layers_with_perimeters + config.opt_int("raft_layers"));
}
if (config.opt_int("raft_layers") == 0) {
// first infill layer printed directly on print bed is not combined, so we don't consider it.
-- layers_with_infill;
-- layers_with_perimeters;
}
// we expect that infill is generated for half the number of combined layers
// plus for each single layer that was not combined (remainder)
THEN("infill is only present in correct number of layers") {
int infill_every = config.opt_int("infill_every_layers");
REQUIRE(layers_with_infill == int(layers_with_perimeters / infill_every) + (layers_with_perimeters % infill_every));
}
};
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
{ "nozzle_diameter", "0.5, 0.5, 0.5, 0.5" },
{ "layer_height", 0.2 },
{ "first_layer_height", 0.2 },
{ "infill_every_layers", 2 },
{ "perimeter_extruder", 1 },
{ "infill_extruder", 2 },
{ "wipe_into_infill", false },
{ "support_material_extruder", 3 },
{ "support_material_interface_extruder", 3 },
{ "top_solid_layers", 0 },
{ "bottom_solid_layers", 0 }
});
test(config);
// Reuse the config above
config.set_deserialize_strict({
{ "skirts", 0 }, // prevent usage of perimeter_extruder in raft layers
{ "raft_layers", 5 }
});
test(config);
}
WHEN("infill_every_layers == 2") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({ Test::TestMesh::cube_20x20x20 }, print, {
{ "nozzle_diameter", "0.5" },
{ "layer_height", 0.2 },
{ "first_layer_height", 0.2 },
{ "infill_every_layers", 2 }
});
THEN("infill combination produces internal void surfaces") {
bool has_void = false;
for (const Layer *layer : print.get_object(0)->layers())
if (layer->get_region(0)->fill_surfaces().filter_by_type(stInternalVoid).size() > 0) {
has_void = true;
break;
}
REQUIRE(has_void);
}
}
WHEN("infill_every_layers disabled") {
// we disable combination after infill has been generated
Slic3r::Print print;
Slic3r::Test::init_and_process_print({ Test::TestMesh::cube_20x20x20 }, print, {
{ "nozzle_diameter", "0.5" },
{ "layer_height", 0.2 },
{ "first_layer_height", 0.2 },
{ "infill_every_layers", 1 }
});
THEN("infill combination is idempotent") {
bool has_infill_on_each_layer = true;
for (const Layer *layer : print.get_object(0)->layers())
if (layer->get_region(0)->fill_surfaces().empty()) {
has_infill_on_each_layer = false;
break;
}
REQUIRE(has_infill_on_each_layer);
}
}
}
SCENARIO("Infill density zero", "[Fill]")
{
WHEN("20mm cube is sliced") {

View File

@ -44,22 +44,31 @@ SCENARIO("Perimeter nesting", "[Perimeters]")
ExtrusionEntityCollection loops;
ExtrusionEntityCollection gap_fill;
SurfaceCollection fill_surfaces;
PerimeterGenerator perimeter_generator(
&slices,
ExPolygons fill_expolygons;
Flow flow(1., 1., 1.);
PerimeterGenerator::Parameters perimeter_generator_params(
1., // layer height
Flow(1., 1., 1.),
static_cast<const PrintRegionConfig*>(&config),
static_cast<const PrintObjectConfig*>(&config),
static_cast<const PrintConfig*>(&config),
false, // spiral_vase
// output:
&loops, &gap_fill, &fill_surfaces);
-1, // layer ID
flow, flow, flow, flow,
static_cast<const PrintRegionConfig&>(config),
static_cast<const PrintObjectConfig&>(config),
static_cast<const PrintConfig&>(config),
false); // spiral_vase
Polygons lower_layer_polygons_cache;
for (const Surface &surface : slices)
// FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation.
// if (config.perimeter_generator == PerimeterGeneratorType::Arachne)
// perimeter_generator.process_arachne();
// PerimeterGenerator::process_arachne();
// else
perimeter_generator.process_classic();
PerimeterGenerator::process_classic(
// input:
perimeter_generator_params,
surface,
nullptr,
// cache:
lower_layer_polygons_cache,
// output:
loops, gap_fill, fill_expolygons);
THEN("expected number of collections") {
REQUIRE(loops.entities.size() == data.expolygons.size());
@ -459,8 +468,10 @@ SCENARIO("Some weird coverage test", "[Perimeters]")
object->slice();
Layer *layer = object->get_layer(1);
LayerRegion *layerm = layer->get_region(0);
layerm->slices.clear();
layerm->slices.append({ expolygon }, stInternal);
layerm->m_slices.clear();
layerm->m_slices.append({ expolygon }, stInternal);
layer->lslices = { expolygon };
layer->lslices_ex = { { get_extents(expolygon) } };
// make perimeters
layer->make_perimeters();
@ -472,22 +483,22 @@ SCENARIO("Some weird coverage test", "[Perimeters]")
Polygons covered_by_infill;
{
Polygons acc;
for (const ExtrusionEntity *ee : layerm->perimeters.entities)
for (const ExtrusionEntity *ee : layerm->perimeters())
for (const ExtrusionEntity *ee : dynamic_cast<const ExtrusionEntityCollection*>(ee)->entities)
append(acc, offset(dynamic_cast<const ExtrusionLoop*>(ee)->polygon().split_at_first_point(), float(pflow.scaled_width() / 2.f + SCALED_EPSILON)));
covered_by_perimeters = union_(acc);
}
{
Polygons acc;
for (const Surface &surface : layerm->fill_surfaces.surfaces)
append(acc, to_polygons(surface.expolygon));
for (const ExtrusionEntity *ee : layerm->thin_fills.entities)
for (const ExPolygon &expolygon : layerm->fill_expolygons())
append(acc, to_polygons(expolygon));
for (const ExtrusionEntity *ee : layerm->thin_fills().entities)
append(acc, offset(dynamic_cast<const ExtrusionPath*>(ee)->polyline, float(iflow.scaled_width() / 2.f + SCALED_EPSILON)));
covered_by_infill = union_(acc);
}
// compute the non covered area
ExPolygons non_covered = diff_ex(to_polygons(layerm->slices.surfaces), union_(covered_by_perimeters, covered_by_infill));
ExPolygons non_covered = diff_ex(to_polygons(layerm->slices().surfaces), union_(covered_by_perimeters, covered_by_infill));
/*
if (0) {
@ -506,7 +517,8 @@ SCENARIO("Some weird coverage test", "[Perimeters]")
}
*/
THEN("no gap between perimeters and infill") {
size_t num_non_convered = std::count_if(non_covered.begin(), non_covered.end(), [&iflow](const ExPolygon &ex){ return ex.area() > sqr(double(iflow.scaled_width())); });
size_t num_non_convered = std::count_if(non_covered.begin(), non_covered.end(),
[&iflow](const ExPolygon &ex){ return ex.area() > sqr(double(iflow.scaled_width())); });
REQUIRE(num_non_convered == 0);
}
}

View File

@ -20,11 +20,11 @@ SCENARIO("PrintObject: Perimeter generation", "[PrintObject]") {
}
THEN("Every layer in region 0 has 1 island of perimeters") {
for (const Layer *layer : object.layers())
REQUIRE(layer->regions().front()->perimeters.entities.size() == 1);
REQUIRE(layer->regions().front()->perimeters().size() == 1);
}
THEN("Every layer in region 0 has 3 paths in its perimeters list.") {
for (const Layer *layer : object.layers())
REQUIRE(layer->regions().front()->perimeters.items_count() == 3);
REQUIRE(layer->regions().front()->perimeters().items_count() == 3);
}
}
}
@ -66,7 +66,7 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t
// iterate over all of the regions in the layer
for (const LayerRegion *region : layer.regions()) {
// for each region, iterate over the fill surfaces
for (const Surface &surface : region->fill_surfaces.surfaces)
for (const Surface &surface : region->fill_surfaces())
CHECK(surface.is_solid());
}
};

View File

@ -0,0 +1,112 @@
#include <catch2/catch.hpp>
#include "libslic3r/GCodeReader.hpp"
#include "test_data.hpp" // get access to init_print, etc
using namespace Slic3r::Test;
using namespace Slic3r;
SCENARIO("Shells", "[Shells]") {
GIVEN("20mm box") {
auto test = [](const DynamicPrintConfig &config){
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
std::vector<coord_t> zs;
std::set<coord_t> layers_with_solid_infill;
std::set<coord_t> layers_with_bridge_infill;
const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60;
const double bridge_speed = config.opt_float("bridge_speed") * 60;
GCodeReader parser;
parser.parse_buffer(gcode,
[&zs, &layers_with_solid_infill, &layers_with_bridge_infill, solid_infill_speed, bridge_speed]
(Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
{
double z = line.new_Z(self);
REQUIRE(z >= 0);
if (z > 0) {
coord_t scaled_z = scaled<float>(z);
zs.emplace_back(scaled_z);
if (line.extruding(self) && line.dist_XY(self) > 0) {
double f = line.new_F(self);
if (std::abs(f - solid_infill_speed) < EPSILON)
layers_with_solid_infill.insert(scaled_z);
if (std::abs(f - bridge_speed) < EPSILON)
layers_with_bridge_infill.insert(scaled_z);
}
}
});
sort_remove_duplicates(zs);
auto has_solid_infill = [&layers_with_solid_infill](coord_t z) { return layers_with_solid_infill.find(z) != layers_with_solid_infill.end(); };
auto has_bridge_infill = [&layers_with_bridge_infill](coord_t z) { return layers_with_bridge_infill.find(z) != layers_with_bridge_infill.end(); };
auto has_shells = [&has_solid_infill, &has_bridge_infill, &zs](int layer_idx) { coord_t z = zs[layer_idx]; return has_solid_infill(z) || has_bridge_infill(z); };
const int bottom_solid_layers = config.opt_int("bottom_solid_layers");
const int top_solid_layers = config.opt_int("top_solid_layers");
THEN("correct number of bottom solid layers") {
for (int i = 0; i < bottom_solid_layers; ++ i)
REQUIRE(has_shells(i));
for (int i = bottom_solid_layers; i < int(zs.size() / 2); ++ i)
REQUIRE(! has_shells(i));
}
THEN("correct number of top solid layers") {
for (int i = 0; i < top_solid_layers; ++ i)
REQUIRE(has_shells(int(zs.size()) - i - 1));
for (int i = top_solid_layers; i < int(zs.size() / 2); ++ i)
REQUIRE(! has_shells(int(zs.size()) - i - 1));
}
if (top_solid_layers > 0) {
THEN("solid infill speed is used on solid infill") {
for (int i = 0; i < top_solid_layers - 1; ++ i) {
auto z = zs[int(zs.size()) - i - 1];
REQUIRE(has_solid_infill(z));
REQUIRE(! has_bridge_infill(z));
}
}
THEN("bridge used in first solid layer over sparse infill") {
auto z = zs[int(zs.size()) - top_solid_layers];
REQUIRE(! has_solid_infill(z));
REQUIRE(has_bridge_infill(z));
}
}
};
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
{ "skirts", 0 },
{ "perimeters", 0 },
{ "solid_infill_speed", 99 },
{ "top_solid_infill_speed", 99 },
{ "bridge_speed", 72 },
{ "first_layer_speed", "100%" },
{ "cooling", "0" }
});
WHEN("three top and bottom layers") {
// proper number of shells is applied
config.set_deserialize_strict({
{ "top_solid_layers", 3 },
{ "bottom_solid_layers", 3 }
});
test(config);
}
WHEN("zero top and bottom layers") {
// no shells are applied when both top and bottom are set to zero
config.set_deserialize_strict({
{ "top_solid_layers", 0 },
{ "bottom_solid_layers", 0 }
});
test(config);
}
WHEN("three top and bottom layers, zero infill") {
// proper number of shells is applied even when fill density is none
config.set_deserialize_strict({
{ "perimeters", 1 },
{ "top_solid_layers", 3 },
{ "bottom_solid_layers", 3 }
});
test(config);
}
}
}

View File

@ -16,7 +16,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} });
ExPolygon expolygon{ square, hole_in_square };
WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(40.), scaled<double>(0.5));
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(40.));
THEN("medial axis of a square shape is a single path") {
REQUIRE(res.size() == 1);
}
@ -32,7 +32,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
GIVEN("narrow rectangle") {
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) };
WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5));
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(20.));
THEN("medial axis of a narrow rectangle is a single line") {
REQUIRE(res.size() == 1);
}
@ -50,7 +50,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
{100, 200}
})};
WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(1.), scaled<double>(0.5));
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(1.));
THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") {
REQUIRE(res.size() == 1);
}
@ -81,7 +81,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
{1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0}
}};
WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(1.324888), scaled<double>(0.25));
Polylines res = expolygon.medial_axis(scaled<double>(0.25), scaled<double>(1.324888));
THEN("medial axis of a semicircumference is a single line") {
REQUIRE(res.size() == 1);
}
@ -103,7 +103,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
GIVEN("narrow trapezoid") {
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) };
WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5));
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(20.));
THEN("medial axis of a narrow trapezoid is a single line") {
REQUIRE(res.size() == 1);
}
@ -115,7 +115,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
GIVEN("L shape") {
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) };
WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5));
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(20.));
THEN("medial axis of an L shape is a single line") {
REQUIRE(res.size() == 1);
}
@ -134,7 +134,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
{-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024},
}};
WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(819998., 102499.75);
Polylines res = expolygon.medial_axis(102499.75, 819998.);
THEN("medial axis is a single line") {
REQUIRE(res.size() == 1);
}
@ -147,7 +147,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
GIVEN("narrow triangle") {
ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) };
WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(4.), scaled<double>(0.5));
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(4.));
THEN("medial axis of a narrow triangle is a single line") {
REQUIRE(res.size() == 1);
}
@ -159,7 +159,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
GIVEN("GH #2474") {
ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }};
WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(1871238, 500000);
Polylines res = expolygon.medial_axis(500000, 1871238);
THEN("medial axis is a single line") {
REQUIRE(res.size() == 1);
}

View File

@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests
test_curve_fitting.cpp
test_cut_surface.cpp
test_elephant_foot_compensation.cpp
test_expolygon.cpp
test_geometry.cpp
test_placeholder_parser.cpp
test_polygon.cpp

View File

@ -0,0 +1,67 @@
#include <catch2/catch.hpp>
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/ExPolygon.hpp"
using namespace Slic3r;
static inline bool points_close(const Point &p1, const Point &p2)
{
return (p1 - p2).cast<double>().norm() < SCALED_EPSILON;
}
static bool polygons_close_permuted(const Polygon &poly1, const Polygon &poly2, const std::vector<int> &permutation2)
{
if (poly1.size() != poly2.size() || poly1.size() != permutation2.size())
return false;
for (size_t i = 0; i < poly1.size(); ++ i)
if (poly1[i] != poly2[permutation2[i]])
return false;
return true;
}
SCENARIO("Basics", "[ExPolygon]") {
GIVEN("ccw_square") {
Polygon ccw_square{ { 100, 100 }, { 200, 100 }, { 200, 200 }, { 100, 200 } };
Polygon cw_hole_in_square{ { 140, 140 }, { 140, 160 }, { 160, 160 }, { 160, 140 } };
ExPolygon expolygon { ccw_square, cw_hole_in_square };
THEN("expolygon is valid") {
REQUIRE(expolygon.is_valid());
}
THEN("expolygon area") {
REQUIRE(expolygon.area() == Approx(100*100-20*20));
}
WHEN("Expolygon scaled") {
ExPolygon expolygon2 = expolygon;
expolygon2.scale(2.5);
REQUIRE(expolygon.contour.size() == expolygon2.contour.size());
REQUIRE(expolygon.holes.size() == 1);
REQUIRE(expolygon2.holes.size() == 1);
for (size_t i = 0; i < expolygon.contour.size(); ++ i)
REQUIRE(points_close(expolygon.contour[i] * 2.5, expolygon2.contour[i]));
for (size_t i = 0; i < expolygon.holes.front().size(); ++ i)
REQUIRE(points_close(expolygon.holes.front()[i] * 2.5, expolygon2.holes.front()[i]));
}
WHEN("Expolygon translated") {
ExPolygon expolygon2 = expolygon;
expolygon2.translate(10, -5);
REQUIRE(expolygon.contour.size() == expolygon2.contour.size());
REQUIRE(expolygon.holes.size() == 1);
REQUIRE(expolygon2.holes.size() == 1);
for (size_t i = 0; i < expolygon.contour.size(); ++ i)
REQUIRE(points_close(expolygon.contour[i] + Point(10, -5), expolygon2.contour[i]));
for (size_t i = 0; i < expolygon.holes.front().size(); ++ i)
REQUIRE(points_close(expolygon.holes.front()[i] + Point(10, -5), expolygon2.holes.front()[i]));
}
WHEN("Expolygon rotated around point") {
ExPolygon expolygon2 = expolygon;
expolygon2.rotate(M_PI / 2, Point(150, 150));
REQUIRE(expolygon.contour.size() == expolygon2.contour.size());
REQUIRE(expolygon.holes.size() == 1);
REQUIRE(expolygon2.holes.size() == 1);
REQUIRE(polygons_close_permuted(expolygon2.contour, expolygon.contour, { 1, 2, 3, 0}));
REQUIRE(polygons_close_permuted(expolygon2.holes.front(), expolygon.holes.front(), { 3, 0, 1, 2}));
}
}
}

View File

@ -1918,7 +1918,6 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]")
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);

View File

@ -43,22 +43,15 @@ set(XS_MAIN_XS ${CMAKE_CURRENT_BINARY_DIR}/main.xs)
set(XSP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/xsp)
#FIXME list the dependecies explicitely, add dependency on the typemap.
set(XS_XSP_FILES
${XSP_DIR}/BoundingBox.xsp
${XSP_DIR}/Config.xsp
${XSP_DIR}/ExPolygon.xsp
${XSP_DIR}/ExtrusionEntityCollection.xsp
${XSP_DIR}/ExtrusionLoop.xsp
${XSP_DIR}/ExtrusionPath.xsp
${XSP_DIR}/Geometry.xsp
${XSP_DIR}/Layer.xsp
${XSP_DIR}/Line.xsp
${XSP_DIR}/Model.xsp
${XSP_DIR}/Point.xsp
${XSP_DIR}/Polygon.xsp
${XSP_DIR}/Polyline.xsp
${XSP_DIR}/Print.xsp
${XSP_DIR}/Surface.xsp
${XSP_DIR}/SurfaceCollection.xsp
${XSP_DIR}/TriangleMesh.xsp
${XSP_DIR}/XS.xsp
)

View File

@ -48,61 +48,6 @@ use overload
'@{}' => sub { $_[0]->arrayref },
'fallback' => 1;
package Slic3r::ExtrusionPath::Collection;
use overload
'@{}' => sub { $_[0]->arrayref },
'fallback' => 1;
sub new {
my ($class, @paths) = @_;
my $self = $class->_new;
$self->append(@paths);
return $self;
}
package Slic3r::ExtrusionLoop;
use overload
'@{}' => sub { $_[0]->arrayref },
'fallback' => 1;
sub new_from_paths {
my ($class, @paths) = @_;
my $loop = $class->new;
$loop->append($_) for @paths;
return $loop;
}
package Slic3r::ExtrusionPath;
use overload
'@{}' => sub { $_[0]->arrayref },
'fallback' => 1;
sub new {
my ($class, %args) = @_;
return $class->_new(
$args{polyline}, # required
$args{role}, # required
$args{mm3_per_mm} // die("Missing required mm3_per_mm in ExtrusionPath constructor"),
$args{width} // -1,
$args{height} // -1,
);
}
sub clone {
my ($self, %args) = @_;
return __PACKAGE__->_new(
$args{polyline} // $self->polyline,
$args{role} // $self->role,
$args{mm3_per_mm} // $self->mm3_per_mm,
$args{width} // $self->width,
$args{height} // $self->height,
);
}
package Slic3r::Surface;
sub new {
@ -155,12 +100,6 @@ for my $class (qw(
Slic3r::Config::Print
Slic3r::Config::Static
Slic3r::ExPolygon
Slic3r::ExtrusionLoop
Slic3r::ExtrusionPath
Slic3r::ExtrusionPath::Collection
Slic3r::Geometry::BoundingBox
Slic3r::Layer
Slic3r::Layer::Region
Slic3r::Line
Slic3r::Model
Slic3r::Model::Instance
@ -175,10 +114,6 @@ for my $class (qw(
Slic3r::Polyline
Slic3r::Polyline::Collection
Slic3r::Print
Slic3r::Print::Object
Slic3r::Print::Region
Slic3r::Surface
Slic3r::Surface::Collection
Slic3r::TriangleMesh
))
{

Some files were not shown because too many files have changed in this diff Show More