Merge branch 'master' into fs_emboss
# Conflicts: # src/libslic3r/Point.hpp
This commit is contained in:
commit
d5fedd928c
@ -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.
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
||||
}
|
||||
@ -246,7 +246,7 @@ int PointInPolygon(const IntPoint &pt, const Path &path)
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Called by Poly2ContainsPoly1()
|
||||
int PointInPolygon (const IntPoint &pt, OutPt *op)
|
||||
int PointInPolygon(const IntPoint &pt, OutPt *op)
|
||||
{
|
||||
//returns 0 if false, +1 if true, -1 if pt ON polygon boundary
|
||||
int result = 0;
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -10,6 +10,11 @@
|
||||
#include "SkeletalTrapezoidationEdge.hpp"
|
||||
#include "SkeletalTrapezoidationJoint.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
class Line;
|
||||
};
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
|
@ -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
|
@ -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)
|
||||
{
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 ¶ms,
|
||||
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());
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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 <p : per_object[i]) {
|
||||
ordering_item.print_z = ltp.print_z();
|
||||
ordering_item.layer_idx = <p - &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 ®ion = 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 ®ion) -> 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 ®ion = 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 ®ion = 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 ®ion : by_region)
|
||||
if (! region.perimeters.empty()) {
|
||||
m_config.apply(print.get_print_region(®ion - &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 ®ion : 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(®ion - &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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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.)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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)));
|
||||
}
|
||||
|
@ -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++;
|
||||
}
|
||||
};
|
||||
|
@ -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 ®ion = 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 <, 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 <)
|
||||
{
|
||||
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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -70,7 +70,7 @@ const char* GCodeReader::parse_line_internal(const char *ptr, const char *end, G
|
||||
if (axis != NUM_AXES_WITH_UNKNOWN) {
|
||||
// Try to parse the numeric value.
|
||||
double v;
|
||||
c = skip_whitespaces(++c);
|
||||
c = skip_whitespaces(++ c);
|
||||
auto [pend, ec] = fast_float::from_chars(c, end, v);
|
||||
if (pend != c && is_end_of_word(*pend)) {
|
||||
// The axis value has been parsed correctly.
|
||||
|
@ -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());
|
||||
// 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;
|
||||
|
||||
// get next points
|
||||
this->process_edge_neighbors(edge, &polyline);
|
||||
assert(polyline.width.size() == polyline.points.size() * 2 - 2);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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();
|
||||
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
|
||||
|
@ -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_
|
||||
|
@ -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_
|
||||
|
@ -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
|
||||
®ions = 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).
|
||||
®ion_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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 ®ion_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),
|
||||
®ion_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));
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include "libslic3r/SurfaceMesh.hpp"
|
||||
#include <numeric>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Measure {
|
||||
|
||||
|
@ -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.
|
||||
|
@ -45,16 +45,6 @@ void MultiPoint::rotate(double angle, const Point ¢er)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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;
|
||||
|
@ -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 ¶ms, 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 ¶ms, 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 ¶ms,
|
||||
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];
|
||||
@ -1129,7 +1156,7 @@ void PerimeterGenerator::process_arachne()
|
||||
for (size_t unlocked_idx : blocking[best_candidate])
|
||||
blocked[unlocked_idx]--;
|
||||
|
||||
if(!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then.
|
||||
if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then.
|
||||
if(best_path->is_closed)
|
||||
current_position = best_path->junctions[0].p; //We end where we started.
|
||||
else
|
||||
@ -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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
append(out_fill_expolygons, std::move(infill_areas));
|
||||
}
|
||||
|
||||
void PerimeterGenerator::process_classic()
|
||||
void PerimeterGenerator::process_classic(
|
||||
// Inputs:
|
||||
const Parameters ¶ms,
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 ¶ms,
|
||||
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 ¶ms,
|
||||
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
|
||||
|
@ -57,98 +57,6 @@ void Point::rotate(double angle, const Point ¢er)
|
||||
(*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);
|
||||
|
@ -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 ¢er) 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);
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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();
|
||||
@ -517,24 +517,33 @@ void PrintObject::slice()
|
||||
// Update bounding boxes, back up raw slices of complex models.
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
[this](const tbb::blocked_range<size_t>& range) {
|
||||
[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 &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 {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.;
|
||||
|
@ -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
@ -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 ¶ms);
|
||||
std::tuple<Issues, Malformations> full_search(const PrintObject *po, const Params ¶ms);
|
||||
Issues full_search(const PrintObject *po, const Params ¶ms);
|
||||
|
||||
void estimate_supports_malformations(SupportLayerPtrs &layers, float supports_flow_width, const Params ¶ms);
|
||||
void estimate_malformations(LayerPtrs &layers, const Params ¶ms);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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); }
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 } } });
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
//FIXME At least FillRectilinear2 and std::boost Voronoi require coord_t to be 32bit.
|
||||
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_
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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__
|
12
t/geometry.t
12
t/geometry.t
@ -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';
|
||||
#}
|
||||
|
||||
#==========================================================
|
||||
|
||||
|
69
t/shells.t
69
t/shells.t
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
@ -101,7 +348,7 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") {
|
||||
{ {0,20}, {0,18}, {0,15} },
|
||||
{ {0,10}, {0,8}, {0,5} }
|
||||
},
|
||||
{ 0,30 }
|
||||
{ 0, 30 }
|
||||
},
|
||||
{
|
||||
{
|
||||
@ -112,7 +359,7 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") {
|
||||
{ {20,5}, {15,5}, {10,5} },
|
||||
{ {15,0}, {10,0}, {4,0} }
|
||||
},
|
||||
{ 30,0 }
|
||||
{ 30, 0 }
|
||||
},
|
||||
{
|
||||
{
|
||||
@ -123,7 +370,7 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") {
|
||||
{ {20,5}, {15,5}, {10,5} },
|
||||
{ {15,0}, {10,0}, {4,0} }
|
||||
},
|
||||
{ 30,0 }
|
||||
{ 30, 0 }
|
||||
},
|
||||
};
|
||||
for (const Test &test : tests) {
|
||||
@ -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]") {
|
||||
|
@ -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") {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
};
|
||||
|
112
tests/fff_print/test_shells.cpp
Normal file
112
tests/fff_print/test_shells.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
67
tests/libslic3r/test_expolygon.cpp
Normal file
67
tests/libslic3r/test_expolygon.cpp
Normal 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}));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user