Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_world_coordinates

This commit is contained in:
enricoturri1966 2021-10-15 11:58:42 +02:00
commit 84c0b816cb
35 changed files with 1030 additions and 508 deletions

View File

@ -155,7 +155,20 @@ bool PolyNode::IsHole() const
node = node->Parent;
}
return result;
}
}
void PolyTree::RemoveOutermostPolygon()
{
if (this->ChildCount() == 1 && this->Childs[0]->ChildCount() > 0) {
PolyNode *outerNode = this->Childs[0];
this->Childs.reserve(outerNode->ChildCount());
this->Childs[0] = outerNode->Childs[0];
this->Childs[0]->Parent = outerNode->Parent;
for (int i = 1; i < outerNode->ChildCount(); ++i)
this->AddChild(*outerNode->Childs[i]);
} else
this->Clear();
}
//------------------------------------------------------------------------------
// Miscellaneous global functions
@ -3444,7 +3457,8 @@ void ClipperOffset::Execute(Paths& solution, double delta)
clpr.AddPath(outer, ptSubject, true);
clpr.ReverseSolution(true);
clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
if (solution.size() > 0) solution.erase(solution.begin());
if (! solution.empty())
solution.erase(solution.begin());
}
}
//------------------------------------------------------------------------------
@ -3475,17 +3489,7 @@ void ClipperOffset::Execute(PolyTree& solution, double delta)
clpr.ReverseSolution(true);
clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
//remove the outer PolyNode rectangle ...
if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0)
{
PolyNode* outerNode = solution.Childs[0];
solution.Childs.reserve(outerNode->ChildCount());
solution.Childs[0] = outerNode->Childs[0];
solution.Childs[0]->Parent = outerNode->Parent;
for (int i = 1; i < outerNode->ChildCount(); ++i)
solution.AddChild(*outerNode->Childs[i]);
}
else
solution.Clear();
solution.RemoveOutermostPolygon();
}
}
//------------------------------------------------------------------------------

View File

@ -180,6 +180,7 @@ public:
PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); }
void Clear() { AllNodes.clear(); Childs.clear(); }
int Total() const;
void RemoveOutermostPolygon();
private:
PolyTree(const PolyTree &src) = delete;
PolyTree& operator=(const PolyTree &src) = delete;
@ -521,6 +522,7 @@ public:
double MiterLimit;
double ArcTolerance;
double ShortestEdgeLength;
private:
Paths m_destPolys;
Path m_srcPoly;
@ -528,6 +530,8 @@ private:
std::vector<DoublePoint> m_normals;
double m_delta, m_sinA, m_sin, m_cos;
double m_miterLim, m_StepsPerRad;
// x: index of the lowest contour in m_polyNodes
// y: index of the lowest point in the lowest contour
IntPoint m_lowest;
PolyNode m_polyNodes;

View File

@ -121,10 +121,10 @@ void AppConfig::set_defaults()
if (get("auto_toolbar_size").empty())
set("auto_toolbar_size", "100");
if (get("notify_release").empty())
set("notify_release", "all"); // or "none" or "release"
if (get("notify_testing_release").empty())
set("notify_testing_release", "1");
#if ENABLE_ENVIRONMENT_MAP
if (get("use_environment_map").empty())
set("use_environment_map", "0");

View File

@ -177,7 +177,7 @@ static ExPolygons top_level_outer_brim_area(const Print &print
append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare)));
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare));
append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare));
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes));
@ -230,13 +230,13 @@ static ExPolygons inner_brim_area(const Print &print,
}
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner)
append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_separation, ClipperLib::jtSquare), offset_ex(ex_poly.holes, -brim_width - brim_separation, ClipperLib::jtSquare)));
append(brim_area_object, diff_ex(shrink_ex(ex_poly.holes, brim_separation, ClipperLib::jtSquare), shrink_ex(ex_poly.holes, brim_width + brim_separation, ClipperLib::jtSquare)));
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes));
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare));
append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare));
append(holes_object, ex_poly.holes);
}
@ -385,10 +385,10 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing()));
for (size_t i = 0; i < num_loops; ++i) {
try_cancel();
islands = offset(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare);
islands = expand(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare);
for (Polygon &poly : islands)
poly.douglas_peucker(SCALED_RESOLUTION);
polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing())));
polygons_append(loops, shrink(islands, 0.5f * float(flow.scaled_spacing())));
}
loops = union_pt_chained_outside_in(loops);

View File

@ -117,15 +117,6 @@ Polylines PolyTreeToPolylines(ClipperLib::PolyTree &&polytree)
return out;
}
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(input, ClipperLib::ptSubject, true);
ClipperLib::PolyTree polytree;
clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero
return PolyTreeToExPolygons(std::move(polytree));
}
#if 0
// Global test.
bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
@ -165,23 +156,28 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
}
#endif
// Offset outside by 10um, one by one.
template<typename PathsProvider>
static ClipperLib::Paths safety_offset(PathsProvider &&paths)
// Offset CCW contours outside, CW contours (holes) inside.
// Don't calculate union of the output paths.
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
ClipperLib::ClipperOffset co;
ClipperLib::Paths out;
out.reserve(paths.size());
ClipperLib::Paths out_this;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
for (const ClipperLib::Path &path : paths) {
co.Clear();
co.MiterLimit = 2.;
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
// contours will be CCW oriented even though the input paths are CW oriented.
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
co.AddPath(path, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
bool ccw = ClipperLib::Orientation(path);
co.Execute(out_this, ccw ? ClipperSafetyOffset : - ClipperSafetyOffset);
co.AddPath(path, joinType, endType);
bool ccw = endType == ClipperLib::etClosedPolygon ? ClipperLib::Orientation(path) : true;
co.Execute(out_this, ccw ? offset : - offset);
if (! ccw) {
// Reverse the resulting contours.
for (ClipperLib::Path &path : out_this)
@ -192,38 +188,122 @@ static ClipperLib::Paths safety_offset(PathsProvider &&paths)
return out;
}
// Only safe for a single path.
// Offset outside by 10um, one by one.
template<typename PathsProvider>
ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit)
static ClipperLib::Paths safety_offset(PathsProvider &&paths)
{
// perform offset
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
float delta_scaled = delta;
co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.AddPaths(std::forward<PathsProvider>(input), joinType, endType);
ClipperLib::Paths retval;
co.Execute(retval, delta_scaled);
return raw_offset(std::forward<PathsProvider>(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit);
}
template<class TResult, class TSubj, class TClip>
TResult clipper_do(
const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
clipper.AddPaths(std::forward<TClip>(clip), ClipperLib::ptClip, true);
TResult retval;
clipper.Execute(clipType, retval, fillType, fillType);
return retval;
}
Slic3r::Polygons offset(const Slic3r::Polygon& polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(ClipperUtils::SinglePathProvider(polygon.points), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); }
template<class TResult, class TSubj, class TClip>
TResult clipper_do(
const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType,
const ApplySafetyOffset do_safety_offset)
{
// Safety offset only allowed on intersection and difference.
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
return do_safety_offset == ApplySafetyOffset::Yes ?
clipper_do<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) :
clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), fillType);
}
template<class TResult, class TSubj>
TResult clipper_union(
TSubj && subject,
// fillType pftNonZero and pftPositive "should" produce the same result for "normalized with implicit union" set of polygons
const ClipperLib::PolyFillType fillType = ClipperLib::pftNonZero)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
TResult retval;
clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType);
return retval;
}
// Perform union of input polygons using the positive rule, convert to ExPolygons.
//FIXME is there any benefit of not doing the boolean / using pftEvenOdd?
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union)
{
return PolyTreeToExPolygons(clipper_union<ClipperLib::PolyTree>(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd));
}
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset > 0);
return raw_offset<PathsProvider, ClipperLib::etOpenButt>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit);
}
template<class TResult, typename PathsProvider>
static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset > 0);
return clipper_union<TResult>(raw_offset(std::forward<PathsProvider>(paths), offset, joinType, miterLimit));
}
// used by shrink_paths()
template<class Container> static void remove_outermost_polygon(Container & solution);
template<> void remove_outermost_polygon<ClipperLib::Paths>(ClipperLib::Paths &solution)
{ if (! solution.empty()) solution.erase(solution.begin()); }
template<> void remove_outermost_polygon<ClipperLib::PolyTree>(ClipperLib::PolyTree &solution)
{ solution.RemoveOutermostPolygon(); }
template<class TResult, typename PathsProvider>
static TResult shrink_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset > 0);
TResult out;
if (auto raw = raw_offset(std::forward<PathsProvider>(paths), - offset, joinType, miterLimit); ! raw.empty()) {
ClipperLib::Clipper clipper;
clipper.AddPaths(raw, ClipperLib::ptSubject, true);
ClipperLib::IntRect r = clipper.GetBounds();
clipper.AddPath({ { r.left - 10, r.bottom + 10 }, { r.right + 10, r.bottom + 10 }, { r.right + 10, r.top - 10 }, { r.left - 10, r.top - 10 } }, ClipperLib::ptSubject, true);
clipper.ReverseSolution(true);
clipper.Execute(ClipperLib::ctUnion, out, ClipperLib::pftNegative, ClipperLib::pftNegative);
remove_outermost_polygon(out);
}
return out;
}
template<class TResult, typename PathsProvider>
static TResult offset_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
{
assert(offset != 0);
return offset > 0 ?
expand_paths<TResult>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit) :
shrink_paths<TResult>(std::forward<PathsProvider>(paths), - offset, joinType, miterLimit);
}
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(raw_offset(ClipperUtils::SinglePathProvider(polygon.points), delta, joinType, miterLimit)); }
#ifdef CLIPPERUTILS_UNSAFE_OFFSET
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); }
{ return to_polygons(offset_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); }
#endif // CLIPPERUTILS_UNSAFE_OFFSET
{ return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(ClipperUtils::SinglePathProvider(polyline.points), ClipperLib::etOpenButt, delta, joinType, miterLimit)); }
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit))); }
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(ClipperUtils::PolylinesProvider(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); }
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); }
// returns number of expolygons collected (0 or 1).
static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)
@ -274,14 +354,8 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
append(out, std::move(contours));
} else if (delta < 0) {
// Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
// Subtract the offsetted holes from the offsetted contours.
ClipperLib::Clipper clipper;
clipper.Clear();
clipper.AddPaths(contours, ClipperLib::ptSubject, true);
clipper.AddPaths(holes, ClipperLib::ptClip, true);
ClipperLib::Paths output;
clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
if (! output.empty()) {
// Subtract the offsetted holes from the offsetted contours.
if (auto output = clipper_do<ClipperLib::Paths>(ClipperLib::ctDifference, contours, holes, ClipperLib::pftNonZero); ! output.empty()) {
append(out, std::move(output));
} else {
// The offsetted holes have eaten up the offsetted outer contour.
@ -308,7 +382,7 @@ static int offset_expolygon_inner(const Slic3r::Surface &surface, const float de
static int offset_expolygon_inner(const Slic3r::Surface *surface, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)
{ return offset_expolygon_inner(surface->expolygon, delta, joinType, miterLimit, out); }
ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::Paths expolygon_offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{
ClipperLib::Paths out;
offset_expolygon_inner(expolygon, delta, joinType, miterLimit, out);
@ -317,9 +391,9 @@ ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta,
// This is a safe variant of the polygons offset, tailored for multiple ExPolygons.
// It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours.
// Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united.
// Each ExPolygon is offsetted separately. For outer offset, the the offsetted ExPolygons shall be united outside of this function.
template<typename ExPolygonVector>
ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
static std::pair<ClipperLib::Paths, size_t> expolygons_offset_raw(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{
// Offsetted ExPolygons before they are united.
ClipperLib::Paths output;
@ -329,124 +403,76 @@ ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta,
size_t expolygons_collected = 0;
for (const auto &expoly : expolygons)
expolygons_collected += offset_expolygon_inner(expoly, delta, joinType, miterLimit, output);
return std::make_pair(std::move(output), expolygons_collected);
}
// 4) Unite the offsetted expolygons.
if (expolygons_collected > 1 && delta > 0) {
// See comment on expolygon_offsets_raw. In addition, for positive offset the contours are united.
template<typename ExPolygonVector>
static ClipperLib::Paths expolygons_offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{
auto [output, expolygons_collected] = expolygons_offset_raw(expolygons, delta, joinType, miterLimit);
// Unite the offsetted expolygons.
return expolygons_collected > 1 && delta > 0 ?
// There is a chance that the outwards offsetted expolygons may intersect. Perform a union.
ClipperLib::Clipper clipper;
clipper.Clear();
clipper.AddPaths(output, ClipperLib::ptSubject, true);
clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
} else {
clipper_union<ClipperLib::Paths>(output) :
// Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output.
}
return output;
output;
}
// See comment on expolygons_offset_raw. In addition, the polygons are always united to conver to polytree.
template<typename ExPolygonVector>
static ClipperLib::PolyTree expolygons_offset_pt(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{
auto [output, expolygons_collected] = expolygons_offset_raw(expolygons, delta, joinType, miterLimit);
// Unite the offsetted expolygons for both the
return clipper_union<ClipperLib::PolyTree>(output);
}
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(expolygon, delta, joinType, miterLimit)); }
{ return to_polygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); }
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(expolygons, delta, joinType, miterLimit)); }
{ return to_polygons(expolygons_offset(expolygons, delta, joinType, miterLimit)); }
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); }
{ return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); }
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return to_polygons(_offset(surfaces, delta, joinType, miterLimit)); }
{ return to_polygons(expolygons_offset(surfaces, delta, joinType, miterLimit)); }
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); }
//FIXME one may spare one Clipper Union call.
{ return ClipperPaths_to_Slic3rExPolygons(expolygon_offset(expolygon, delta, joinType, miterLimit)); }
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); }
{ 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 ClipperPaths_to_Slic3rExPolygons(_offset(surfaces, delta, joinType, miterLimit)); }
{ return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
#ifdef CLIPPERUTILS_UNSAFE_OFFSET
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &polygons)
{ return offset(polygons, ClipperSafetyOffset); }
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons)
{ return offset_ex(polygons, ClipperSafetyOffset); }
#endif // CLIPPERUTILS_UNSAFE_OFFSET
Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons)
{ return offset(expolygons, ClipperSafetyOffset); }
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons)
{ return offset_ex(expolygons, ClipperSafetyOffset); }
ClipperLib::Paths _offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
// prepare ClipperOffset object
ClipperLib::ClipperOffset co;
if (joinType == jtRound) {
co.ArcTolerance = miterLimit;
} else {
co.MiterLimit = miterLimit;
}
float delta_scaled1 = delta1;
float delta_scaled2 = delta2;
co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR);
// perform first offset
ClipperLib::Paths output1;
co.AddPaths(ClipperUtils::PolygonsProvider(polygons), joinType, ClipperLib::etClosedPolygon);
co.Execute(output1, delta_scaled1);
// perform second offset
co.Clear();
co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths retval;
co.Execute(retval, delta_scaled2);
return retval;
}
Polygons offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
return to_polygons(_offset2(polygons, delta1, delta2, joinType, miterLimit));
}
ExPolygons offset2_ex(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
return ClipperPaths_to_Slic3rExPolygons(_offset2(polygons, delta1, delta2, joinType, miterLimit));
}
//FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper
// conversions and unnecessary Clipper calls. It is not that bad now as Clipper uses Slic3r's own Point / Polygon types directly.
Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
return offset(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit);
return to_polygons(offset_paths<ClipperLib::Paths>(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
return offset_ex(offset_ex(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit);
return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
template<class TResult, class TSubj, class TClip>
TResult _clipper_do(
const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType)
// Offset outside, then inside produces morphological closing. All deltas should be positive.
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
clipper.AddPaths(std::forward<TClip>(clip), ClipperLib::ptClip, true);
TResult retval;
clipper.Execute(clipType, retval, fillType, fillType);
return retval;
assert(delta1 > 0);
assert(delta2 > 0);
return to_polygons(shrink_paths<ClipperLib::Paths>(expand_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
assert(delta1 > 0);
assert(delta2 > 0);
return PolyTreeToExPolygons(shrink_paths<ClipperLib::PolyTree>(expand_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
template<class TResult, class TSubj, class TClip>
TResult _clipper_do(
const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType,
const ApplySafetyOffset do_safety_offset)
// Offset inside, then outside produces morphological opening. All deltas should be positive.
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
// Safety offset only allowed on intersection and difference.
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
return do_safety_offset == ApplySafetyOffset::Yes ?
_clipper_do<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) :
_clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), fillType);
assert(delta1 > 0);
assert(delta2 > 0);
return to_polygons(expand_paths<ClipperLib::Paths>(shrink_paths<ClipperLib::Paths>(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit));
}
// Fix of #117: A large fractal pyramid takes ages to slice
@ -457,29 +483,22 @@ TResult _clipper_do(
// 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time.
// 2) Run Clipper Union once again to extract the PolyTree from the result of 1).
template<typename PathProvider1, typename PathProvider2>
inline ClipperLib::PolyTree _clipper_do_polytree2(
inline ClipperLib::PolyTree clipper_do_polytree(
const ClipperLib::ClipType clipType,
PathProvider1 &&subject,
PathProvider2 &&clip,
const ClipperLib::PolyFillType fillType)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(std::forward<PathProvider1>(subject), ClipperLib::ptSubject, true);
clipper.AddPaths(std::forward<PathProvider2>(clip), ClipperLib::ptClip, true);
// Perform the operation with the output to input_subject.
// This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library
// if there are overapping edges.
ClipperLib::Paths input_subject;
clipper.Execute(clipType, input_subject, fillType, fillType);
// Perform an additional Union operation to generate the PolyTree ordering.
clipper.Clear();
clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
ClipperLib::PolyTree retval;
clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType);
return retval;
if (auto output = clipper_do<ClipperLib::Paths>(clipType, subject, clip, fillType); ! output.empty())
// Perform an additional Union operation to generate the PolyTree ordering.
return clipper_union<ClipperLib::PolyTree>(output, fillType);
return ClipperLib::PolyTree();
}
template<typename PathProvider1, typename PathProvider2>
inline ClipperLib::PolyTree _clipper_do_polytree2(
inline ClipperLib::PolyTree clipper_do_polytree(
const ClipperLib::ClipType clipType,
PathProvider1 &&subject,
PathProvider2 &&clip,
@ -488,14 +507,14 @@ inline ClipperLib::PolyTree _clipper_do_polytree2(
{
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
return do_safety_offset == ApplySafetyOffset::Yes ?
_clipper_do_polytree2(clipType, std::forward<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) :
_clipper_do_polytree2(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(clip), fillType);
clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) :
clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(clip), fillType);
}
template<class TSubj, class TClip>
static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset)
{
return to_polygons(_clipper_do<ClipperLib::Paths>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), ClipperLib::pftNonZero, do_safety_offset));
return to_polygons(clipper_do<ClipperLib::Paths>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(clip), ClipperLib::pftNonZero, do_safety_offset));
}
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
@ -529,7 +548,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons
template <typename TSubject, typename TClip>
static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero)
{ return PolyTreeToExPolygons(_clipper_do_polytree2(clipType, std::forward<TSubject>(subject), std::forward<TClip>(clip), fill_type, do_safety_offset)); }
{ return PolyTreeToExPolygons(clipper_do_polytree(clipType, std::forward<TSubject>(subject), std::forward<TClip>(clip), fill_type, do_safety_offset)); }
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
@ -578,9 +597,9 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Sli
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type)
{ return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject)
{ return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject)
{ return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
template<typename PathsProvider1, typename PathsProvider2>
Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip)
@ -692,14 +711,15 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol
return retval;
}
// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed.
// If the contours are not intersecting, their orientation shall not be modified by union_pt().
ClipperLib::PolyTree union_pt(const Polygons &subject)
{
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
return clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
}
ClipperLib::PolyTree union_pt(const ExPolygons &subject)
{
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
return clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd);
}
// Simple spatial ordering of Polynodes
@ -730,7 +750,7 @@ static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *ou
});
}
static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons *retval)
static void traverse_pt_outside_in(ClipperLib::PolyNodes &&nodes, Polygons *retval)
{
// collect ordering points
Points ordering_points;
@ -740,22 +760,20 @@ static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons
// Perform the ordering, push results recursively.
//FIXME pass the last point to chain_clipper_polynodes?
for (const ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) {
retval->emplace_back(node->Contour);
for (ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) {
retval->emplace_back(std::move(node->Contour));
if (node->IsHole())
// Orient a hole, which is clockwise oriented, to CCW.
retval->back().reverse();
// traverse the next depth
traverse_pt_outside_in(node->Childs, retval);
traverse_pt_outside_in(std::move(node->Childs), retval);
}
}
Polygons union_pt_chained_outside_in(const Polygons &subject)
{
ClipperLib::PolyTree polytree = union_pt(subject);
Polygons retval;
traverse_pt_outside_in(polytree.Childs, &retval);
traverse_pt_outside_in(union_pt(subject).Childs, &retval);
return retval;
}

View File

@ -12,16 +12,26 @@ using Slic3r::ClipperLib::jtMiter;
using Slic3r::ClipperLib::jtRound;
using Slic3r::ClipperLib::jtSquare;
static constexpr const float ClipperSafetyOffset = 10.f;
namespace Slic3r {
static constexpr const float ClipperSafetyOffset = 10.f;
static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter;
//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2.
// Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill.
// However such a high limit causes issues with large positive or negative offsets, where a sharp corner
// is extended excessively.
static constexpr const double DefaultMiterLimit = 3.;
static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Slic3r::ClipperLib::jtSquare;
// Miter limit is ignored for jtSquare.
static constexpr const double DefaultLineMiterLimit = 0.;
enum class ApplySafetyOffset {
No,
Yes
};
#define CLIPPERUTILS_UNSAFE_OFFSET
namespace Slic3r {
namespace ClipperUtils {
class PathsProviderIteratorBase {
public:
@ -81,6 +91,33 @@ namespace ClipperUtils {
static Points s_end;
};
template<typename PathType>
class PathsProvider {
public:
PathsProvider(const std::vector<PathType> &paths) : m_paths(paths) {}
struct iterator : public PathsProviderIteratorBase {
public:
explicit iterator(typename std::vector<PathType>::const_iterator it) : m_it(it) {}
const Points& operator*() const { return *m_it; }
bool operator==(const iterator &rhs) const { return m_it == rhs.m_it; }
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
const Points& operator++(int) { return *(m_it ++); }
iterator& operator++() { ++ m_it; return *this; }
private:
typename std::vector<PathType>::const_iterator m_it;
};
iterator cbegin() const { return iterator(m_paths.begin()); }
iterator begin() const { return this->cbegin(); }
iterator cend() const { return iterator(m_paths.end()); }
iterator end() const { return this->cend(); }
size_t size() const { return m_paths.size(); }
private:
const std::vector<PathType> &m_paths;
};
template<typename MultiPointType>
class MultiPointsProvider {
public:
@ -261,36 +298,66 @@ namespace ClipperUtils {
};
}
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input);
// Perform union of input polygons using the non-zero rule, convert to ExPolygons.
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union = false);
// offset Polygons
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
// offset Polylines
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3);
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3);
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
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);
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); }
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); }
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons) { return offset_ex(expolygons, ClipperSafetyOffset); }
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons);
Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons);
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons);
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons);
Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
// Aliases for the various offset(...) functions, conveying the purpose of the offset.
inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, delta, joinType, miterLimit); }
// Input polygons for shrinking shall be "normalized": There must be no overlap / intersections between the input polygons.
inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); }
inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
#ifdef CLIPPERUTILS_UNSAFE_OFFSET
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3);
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons);
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons);
#endif // CLIPPERUTILS_UNSAFE_OFFSET
// Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better.
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
// Offset outside, then inside produces morphological closing. All deltas should be positive.
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return closing(polygons, delta, delta, joinType, miterLimit); }
Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return closing_ex(polygons, delta, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return offset2_ex(polygons, delta, - delta, joinType, miterLimit); }
// Offset inside, then outside produces morphological opening. All deltas should be positive.
// Input polygons for opening shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return opening(polygons, delta, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { return offset2_ex(polygons, - delta, delta, joinType, miterLimit); }
Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip);
@ -366,6 +433,8 @@ Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFil
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject);
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject);
// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed.
// If the contours are not intersecting, their orientation shall not be modified by union_pt().
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject);
ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject);

View File

@ -252,11 +252,11 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
// Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2
Polygons collapsed = diff(
surfaces_polygons,
offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2 + ClipperSafetyOffset));
opening(surfaces_polygons, float(distance_between_surfaces /2), float(distance_between_surfaces / 2 + ClipperSafetyOffset)));
//FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being
// added if two offsetted void regions merge.
// polygons_append(voids, collapsed);
ExPolygons extensions = intersection_ex(offset(collapsed, (float)distance_between_surfaces), voids, ApplySafetyOffset::Yes);
ExPolygons extensions = intersection_ex(expand(collapsed, float(distance_between_surfaces)), voids, ApplySafetyOffset::Yes);
// Now find an internal infill SurfaceFill to add these extrusions to.
SurfaceFill *internal_solid_fill = nullptr;
unsigned int region_id = 0;

View File

@ -402,19 +402,19 @@ public:
hole.rotate(angle);
}
double mitterLimit = 3.;
double miterLimit = DefaultMiterLimit;
// for the infill pattern, don't cut the corners.
// default miterLimt = 3
//double mitterLimit = 10.;
//double miterLimit = 10.;
assert(aoffset1 < 0);
assert(aoffset2 <= 0);
assert(aoffset2 == 0 || aoffset2 < aoffset1);
// bool sticks_removed =
remove_sticks(polygons_src);
// if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!";
polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, mitterLimit);
polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit);
if (aoffset2 < 0)
polygons_inner = offset(polygons_outer, float(aoffset2 - aoffset1), ClipperLib::jtMiter, mitterLimit);
polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), ClipperLib::jtMiter, miterLimit);
// Filter out contours with zero area or small area, contours with 2 points only.
const double min_area_threshold = 0.01 * aoffset2 * aoffset2;
remove_small(polygons_outer, min_area_threshold);

View File

@ -868,7 +868,7 @@ static Polygons get_boundary_external(const Layer &layer)
}
// Used offset_ex for cases when another object will be in the hole of another polygon
boundary = to_polygons(offset_ex(boundary, perimeter_offset));
boundary = expand(boundary, perimeter_offset);
// Reverse all polygons for making normals point from the polygon out.
for (Polygon &poly : boundary)
poly.reverse();

View File

@ -212,7 +212,7 @@ void SeamPlacer::init(const Print& print)
std::vector<float> deltas(input.points.size(), offset);
input.make_counter_clockwise();
out.front() = mittered_offset_path_scaled(input.points, deltas, 3.);
return ClipperPaths_to_Slic3rExPolygons(out);
return ClipperPaths_to_Slic3rExPolygons(out, true); // perform union
};

View File

@ -188,17 +188,23 @@ public:
// Extrusion paths for the support base and for the support interface and contacts.
ExtrusionEntityCollection support_fills;
// Is there any valid extrusion assigned to this LayerRegion?
virtual bool has_extrusions() const { return ! support_fills.empty(); }
// Zero based index of an interface layer, used for alternating direction of interface / contact layers.
size_t interface_id() const { return m_interface_id; }
protected:
friend class PrintObject;
// The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower
// between the raft and the object first layer.
SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
Layer(id, object, height, print_z, slice_z) {}
SupportLayer(size_t id, size_t interface_id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
Layer(id, object, height, print_z, slice_z), m_interface_id(interface_id) {}
virtual ~SupportLayer() = default;
size_t m_interface_id;
};
template<typename LayerContainer>

View File

@ -190,8 +190,7 @@ static inline std::vector<ColoredLine> to_lines(const std::vector<std::vector<Co
return lines;
}
// Double vertex equal to a coord_t point after conversion to double.
static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const Point &ipt)
static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const Vec2d &ipt)
{
// Convert ipt to doubles, force the 80bit FPU temporary to 64bit and then compare.
// This should work with any settings of math compiler switches and the C++ compiler
@ -199,11 +198,11 @@ static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const
using ulp_cmp_type = boost::polygon::detail::ulp_comparison<double>;
ulp_cmp_type ulp_cmp;
static constexpr int ULPS = boost::polygon::voronoi_diagram_traits<double>::vertex_equality_predicate_type::ULPS;
return ulp_cmp(vertex.x(), double(ipt.x()), ULPS) == ulp_cmp_type::EQUAL &&
ulp_cmp(vertex.y(), double(ipt.y()), ULPS) == ulp_cmp_type::EQUAL;
return ulp_cmp(vertex.x(), ipt.x(), ULPS) == ulp_cmp_type::EQUAL &&
ulp_cmp(vertex.y(), ipt.y(), ULPS) == ulp_cmp_type::EQUAL;
}
static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Point &ipt)
static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Vec2d &ipt)
{
return vertex_equal_to_point(*vertex, ipt);
}
@ -509,6 +508,8 @@ static inline Point mk_point(const Voronoi::Internal::point_type &point) { retur
static inline Point mk_point(const voronoi_diagram<double>::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; }
static inline Point mk_point(const Vec2d &point) { return {coord_t(std::round(point.x())), coord_t(std::round(point.y()))}; }
static inline Vec2d mk_vec2(const voronoi_diagram<double>::vertex_type *point) { return {point->x(), point->y()}; }
struct MMU_Graph
@ -528,7 +529,7 @@ struct MMU_Graph
struct Node
{
Point point;
Vec2d point;
std::list<size_t> arc_idxs;
void remove_edge(const size_t to_idx, MMU_Graph &graph)
@ -665,48 +666,67 @@ struct MMU_Graph
struct CPoint
{
CPoint() = delete;
CPoint(const Point &point, size_t contour_idx, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(contour_idx) {}
CPoint(const Point &point, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(0) {}
CPoint(const Vec2d &point, size_t contour_idx, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(contour_idx) {}
CPoint(const Vec2d &point, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(0) {}
const Vec2d m_point_double;
const Point m_point;
size_t m_point_idx;
size_t m_contour_idx;
[[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 == rhs.m_point && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; }
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; }
};
struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }};
typedef ClosestPointInRadiusLookup<CPoint, CPointAccessor> CPointLookupType;
CPointLookupType closest_voronoi_point(3 * coord_t(SCALED_EPSILON));
CPointLookupType closest_voronoi_point(coord_t(SCALED_EPSILON));
CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON));
for (const Polygon &polygon : color_poly_tmp)
for (const Point &pt : polygon.points)
closest_contour_point.insert(CPoint(pt, &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front()));
closest_contour_point.insert(CPoint(Vec2d(pt.x(), pt.y()), &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front()));
for (const voronoi_diagram<double>::vertex_type &vertex : vd.vertices()) {
vertex.color(-1);
Point vertex_point = mk_point(vertex);
Vec2d vertex_point_double = Vec2d(vertex.x(), vertex.y());
Point vertex_point = mk_point(vertex);
const Point &first_point = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
const Point &second_point = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
const Vec2d &first_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
const Vec2d &second_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
if (vertex_equal_to_point(&vertex, first_point)) {
if (vertex_equal_to_point(&vertex, first_point_double)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx);
} else if (vertex_equal_to_point(&vertex, second_point)) {
} else if (vertex_equal_to_point(&vertex, second_point_double)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
} else if (bbox.contains(vertex_point)) {
if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < Slic3r::sqr(3 * SCALED_EPSILON)) {
vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx));
} else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(3 * SCALED_EPSILON)) {
closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count()));
} else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(SCALED_EPSILON / 10.0)) {
closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count()));
vertex.color(this->nodes_count());
this->nodes.push_back({vertex_point});
this->nodes.push_back({vertex_point_double});
} else {
vertex.color(voronoi_pt->m_point_idx);
// Boost Voronoi diagram generator sometimes creates two very closed points instead of one point.
// For the example points (146872.99999999997, -146872.99999999997) and (146873, -146873), this example also included in Voronoi generator test cases.
std::vector<std::pair<const CPoint *, double>> all_closes_c_points = closest_voronoi_point.find_all(vertex_point);
int merge_to_point = -1;
for (const std::pair<const CPoint *, double> &c_point : all_closes_c_points)
if ((vertex_point_double - c_point.first->point_double()).squaredNorm() <= Slic3r::sqr(EPSILON)) {
merge_to_point = int(c_point.first->m_point_idx);
break;
}
if (merge_to_point != -1) {
vertex.color(merge_to_point);
} else {
closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count()));
vertex.color(this->nodes_count());
this->nodes.push_back({vertex_point_double});
}
}
}
}
@ -850,7 +870,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
MMU_Graph graph;
graph.nodes.reserve(points.size() + vd.vertices().size());
for (const Point &point : points)
graph.nodes.push_back({point});
graph.nodes.push_back({Vec2d(double(point.x()), double(point.y()))});
graph.add_contours(color_poly);
init_polygon_indices(graph, color_poly, lines_colored);
@ -984,8 +1004,10 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
}
} else if (Point intersection; line_intersection_with_epsilon(contour_line, edge_line, &intersection)) {
mark_processed(edge_it);
Point real_v0 = graph.nodes[edge_it->vertex0()->color()].point;
Point real_v1 = graph.nodes[edge_it->vertex1()->color()].point;
Vec2d real_v0_double = graph.nodes[edge_it->vertex0()->color()].point;
Vec2d real_v1_double = graph.nodes[edge_it->vertex1()->color()].point;
Point real_v0 = Point(coord_t(real_v0_double.x()), coord_t(real_v0_double.y()));
Point real_v1 = Point(coord_t(real_v1_double.x()), coord_t(real_v1_double.y()));
if (is_point_closer_to_beginning_of_line(contour_line, intersection)) {
Line first_part(intersection, real_v0);
@ -999,8 +1021,9 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
graph.append_edge(edge_it->vertex1()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx);
}
} else {
const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx;
const Point int_point = graph.nodes[int_point_idx].point;
const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx;
const Vec2d int_point_double = graph.nodes[int_point_idx].point;
const Point int_point = Point(coord_t(int_point_double.x()), coord_t(int_point_double.y()));
const Line first_part(int_point, real_v0);
const Line second_part(int_point, real_v1);
@ -1039,12 +1062,12 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
return graph;
}
static inline Polygon to_polygon(const Lines &lines)
static inline Polygon to_polygon(const std::vector<Linef> &lines)
{
Polygon poly_out;
poly_out.points.reserve(lines.size());
for (const Line &line : lines)
poly_out.points.emplace_back(line.a);
for (const Linef &line : lines)
poly_out.points.emplace_back(mk_point(line.a));
return poly_out;
}
@ -1056,7 +1079,7 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
{
std::vector<bool> used_arcs(graph.arcs.size(), false);
// When there is no next arc, then is returned original_arc or edge with is marked as used
auto get_next = [&graph, &used_arcs](const Line &process_line, const MMU_Graph::Arc &original_arc) -> const MMU_Graph::Arc & {
auto get_next = [&graph, &used_arcs](const Linef &process_line, const MMU_Graph::Arc &original_arc) -> const MMU_Graph::Arc & {
std::vector<std::pair<const MMU_Graph::Arc *, double>> sorted_arcs;
for (const size_t &arc_idx : graph.nodes[original_arc.to_idx].arc_idxs) {
const MMU_Graph::Arc &arc = graph.arcs[arc_idx];
@ -1064,8 +1087,8 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
continue;
assert(original_arc.to_idx == arc.from_idx);
Vec2d process_line_vec_n = (process_line.a - process_line.b).cast<double>().normalized();
Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).cast<double>().normalized();
Vec2d process_line_vec_n = (process_line.a - process_line.b).normalized();
Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).normalized();
double angle = ::acos(std::clamp(neighbour_line_vec_n.dot(process_line_vec_n), -1.0, 1.0));
if (Slic3r::cross2(neighbour_line_vec_n, process_line_vec_n) < 0.0)
@ -1098,17 +1121,17 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
for (const size_t &arc_idx : node.arc_idxs) {
const MMU_Graph::Arc &arc = graph.arcs[arc_idx];
if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx])continue;
if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx])
continue;
Line process_line(node.point, graph.nodes[arc.to_idx].point);
Linef process_line(node.point, graph.nodes[arc.to_idx].point);
used_arcs[arc_idx] = true;
Lines face_lines;
std::vector<Linef> face_lines;
face_lines.emplace_back(process_line);
Point start_p = process_line.a;
Vec2d start_p = process_line.a;
Line p_vec = process_line;
Linef p_vec = process_line;
const MMU_Graph::Arc *p_arc = &arc;
do {
const MMU_Graph::Arc &next = get_next(p_vec, *p_arc);
@ -1118,7 +1141,7 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
break;
used_arcs[next_arc_idx] = true;
p_vec = Line(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point);
p_vec = Linef(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point);
p_arc = &next;
} while (graph.nodes[p_arc->to_idx].point != start_p || !all_arc_used(graph.nodes[p_arc->to_idx]));
@ -1141,16 +1164,16 @@ static inline double compute_edge_length(const MMU_Graph &graph, const size_t st
used_arcs[start_arc_idx] = true;
const MMU_Graph::Arc *arc = &graph.arcs[start_arc_idx];
size_t idx = start_idx;
double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).cast<double>().norm();;
double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();;
while (graph.nodes[arc->to_idx].arc_idxs.size() == 2) {
bool found = false;
for (const size_t &arc_idx : graph.nodes[arc->to_idx].arc_idxs) {
if (const MMU_Graph::Arc &arc_n = graph.arcs[arc_idx]; arc_n.type == MMU_Graph::ARC_TYPE::NON_BORDER && !used_arcs[arc_idx] && arc_n.to_idx != idx) {
Line first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point);
Line second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point);
Linef first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point);
Linef second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point);
Vec2d first_line_vec = (first_line.a - first_line.b).cast<double>();
Vec2d second_line_vec = (second_line.b - second_line.a).cast<double>();
Vec2d first_line_vec = (first_line.a - first_line.b);
Vec2d second_line_vec = (second_line.b - second_line.a);
Vec2d first_line_vec_n = first_line_vec.normalized();
Vec2d second_line_vec_n = second_line_vec.normalized();
double angle = ::acos(std::clamp(first_line_vec_n.dot(second_line_vec_n), -1.0, 1.0));
@ -1163,7 +1186,7 @@ static inline double compute_edge_length(const MMU_Graph &graph, const size_t st
idx = arc->to_idx;
arc = &arc_n;
line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).cast<double>().norm();
line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();
used_arcs[arc_idx] = true;
found = true;
break;
@ -1185,7 +1208,7 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
for (const std::pair<size_t, size_t> &colored_segment : colored_segment_p) {
size_t first_idx = graph.get_global_index(poly_idx, colored_segment.first);
size_t second_idx = graph.get_global_index(poly_idx, (colored_segment.second + 1) % graph.polygon_sizes[poly_idx]);
Line seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point);
Linef seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point);
if (graph.nodes[first_idx].arc_idxs.size() >= 3) {
std::vector<std::pair<MMU_Graph::Arc *, double>> arc_to_check;
@ -1377,7 +1400,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
// Clean up thin projections. They are not printable anyways.
top_ex = offset2_ex(top_ex, - stat.small_region_threshold, + stat.small_region_threshold);
top_ex = opening_ex(top_ex, stat.small_region_threshold);
if (! top_ex.empty()) {
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
float offset = 0.f;
@ -1385,8 +1408,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - stat.top_solid_layers), int(0)); --last_idx) {
offset -= stat.extrusion_width;
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = offset2_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)),
- stat.small_region_threshold, + stat.small_region_threshold);
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
if (last.empty())
break;
append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
@ -1396,7 +1418,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) {
// Clean up thin projections. They are not printable anyways.
bottom_ex = offset2_ex(bottom_ex, - stat.small_region_threshold, + stat.small_region_threshold);
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
if (! bottom_ex.empty()) {
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
float offset = 0.f;
@ -1404,8 +1426,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_solid_layers, num_layers); ++last_idx) {
offset -= stat.extrusion_width;
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = offset2_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)),
- stat.small_region_threshold, + stat.small_region_threshold);
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
if (last.empty())
break;
append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
@ -1502,7 +1523,7 @@ static void export_graph_to_svg(const std::string &path, const MMU_Graph &graph,
for (const MMU_Graph::Node &node : graph.nodes)
for (const size_t &arc_idx : node.arc_idxs) {
const MMU_Graph::Arc &arc = graph.arcs[arc_idx];
Line arc_line(node.point, graph.nodes[arc.to_idx].point);
Line arc_line(mk_point(node.point), mk_point(graph.nodes[arc.to_idx].point));
if (arc.type == MMU_Graph::ARC_TYPE::BORDER && arc.color >= 0 && arc.color < int(colors.size()))
svg.draw(arc_line, colors[arc.color], stroke_width);
else

View File

@ -347,10 +347,10 @@ void PerimeterGenerator::process()
// 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));
ExPolygons expp = offset2_ex(
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.), float(min_width / 2.));
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);
@ -495,7 +495,7 @@ void PerimeterGenerator::process()
double max = 2. * perimeter_spacing;
ExPolygons gaps_ex = diff_ex(
//FIXME offset2 would be enough and cheaper.
offset2_ex(gaps, - float(min / 2.), float(min / 2.)),
opening_ex(gaps, float(min / 2.)),
offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
ThickPolylines polylines;
for (const ExPolygon &ex : gaps_ex)

View File

@ -1139,7 +1139,7 @@ void Print::_make_wipe_tower()
// Insert the new support layer.
double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z);
//FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height);
it_layer = m_objects.front()->insert_support_layer(it_layer, -1, 0, height, lt.print_z, lt.print_z - 0.5 * height);
++ it_layer;
}
}

View File

@ -300,8 +300,8 @@ public:
size_t support_layer_count() const { return m_support_layers.size(); }
void clear_support_layers();
SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; }
SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z);
SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z);
SupportLayer* add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z);
SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z);
void delete_support_layer(int idx);
// Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters.

View File

@ -461,15 +461,15 @@ void PrintObject::clear_support_layers()
m_support_layers.clear();
}
SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t print_z)
SupportLayer* PrintObject::add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z)
{
m_support_layers.emplace_back(new SupportLayer(id, this, height, print_z, -1));
m_support_layers.emplace_back(new SupportLayer(id, interface_id, this, height, print_z, -1));
return m_support_layers.back();
}
SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z)
SupportLayerPtrs::iterator PrintObject::insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z)
{
return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z));
return m_support_layers.insert(pos, new SupportLayer(id, interface_id, this, height, print_z, slice_z));
}
// Called by Print::apply().
@ -774,7 +774,7 @@ void PrintObject::detect_surfaces_type()
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);
surfaces_append(top, offset2_ex(upper_slices, -offset, offset), stTop);
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
@ -792,15 +792,15 @@ void PrintObject::detect_surfaces_type()
to_polygons(lower_layer->get_region(region_id)->slices.surfaces) :
to_polygons(lower_layer->slices);
surfaces_append(bottom,
offset2_ex(diff(layerm->slices.surfaces, lower_slices, true), -offset, offset),
opening_ex(diff(layerm->slices.surfaces, lower_slices, true), offset),
surface_type_bottom_other);
#else
// Any surface lying on the void is a true bottom bridge (an overhang)
surfaces_append(
bottom,
offset2_ex(
opening_ex(
diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
-offset, offset),
offset),
surface_type_bottom_other);
// if user requested internal shells, we need to identify surfaces
// lying on other slices not belonging to this region
@ -809,12 +809,12 @@ void PrintObject::detect_surfaces_type()
// on something else, excluding those lying on our own region
surfaces_append(
bottom,
offset2_ex(
opening_ex(
diff_ex(
intersection(layerm->slices.surfaces, lower_layer->lslices), // supported
lower_layer->m_regions[region_id]->slices.surfaces,
ApplySafetyOffset::Yes),
-offset, offset),
offset),
stBottom);
}
#endif
@ -1337,7 +1337,7 @@ void PrintObject::discover_vertical_shells()
// get a triangle in $too_narrow; if we grow it below then the shell
// would have a different shape from the external surface and we'd still
// have the same angle, so the next shell would be grown even more and so on.
Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, ClipperLib::jtMiter, 5.), true);
Polygons too_narrow = diff(shell, opening(shell, margin, ClipperLib::jtMiter, 5.), true);
if (! too_narrow.empty()) {
// grow the collapsing parts and add the extra area to the neighbor layer
// as well as to our original surfaces so that we support this
@ -1453,7 +1453,7 @@ void PrintObject::bridge_over_infill()
// The gaps will be filled by a separate region, which makes the infill less stable and it takes longer.
{
float min_width = float(bridge_flow.scaled_width()) * 3.f;
to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width);
to_bridge_pp = opening(to_bridge_pp, min_width);
}
if (to_bridge_pp.empty()) continue;
@ -1744,7 +1744,7 @@ void PrintObject::clip_fill_surfaces()
for (const LayerRegion *layerm : layer->m_regions)
pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width());
// Append such thick perimeters to the areas that need support
polygons_append(overhangs, offset2(perimeters, -pw, +pw));
polygons_append(overhangs, opening(perimeters, pw));
}
// Find new internal infill.
polygons_append(overhangs, std::move(upper_internal));
@ -1884,7 +1884,7 @@ void PrintObject::discover_horizontal_shells()
float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width());
Polygons too_narrow = diff(
new_internal_solid,
offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, jtMiter, 5));
opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5));
// Trim the regularized region by the original region.
if (! too_narrow.empty())
new_internal_solid = solid = diff(new_internal_solid, too_narrow);
@ -1903,7 +1903,7 @@ void PrintObject::discover_horizontal_shells()
// have the same angle, so the next shell would be grown even more and so on.
Polygons too_narrow = diff(
new_internal_solid,
offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5));
opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5));
if (! too_narrow.empty()) {
// grow the collapsing parts and add the extra area to the neighbor layer
// as well as to our original surfaces so that we support this
@ -1915,7 +1915,7 @@ void PrintObject::discover_horizontal_shells()
polygons_append(internal, to_polygons(surface.expolygon));
polygons_append(new_internal_solid,
intersection(
offset(too_narrow, +margin),
expand(too_narrow, +margin),
// Discard bridges as they are grown for anchoring and we can't
// remove such anchors. (This may happen when a bridge is being
// anchored onto a wall where little space remains after the bridge

View File

@ -393,7 +393,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
}
}
if (merged)
expolygons = offset2_ex(expolygons, float(scale_(EPSILON)), -float(scale_(EPSILON)));
expolygons = closing_ex(expolygons, float(scale_(EPSILON)));
slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons);
i = j;
}
@ -648,7 +648,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
ByRegion &src = by_region[region_id];
if (src.needs_merge)
// Multiple regions were merged into one.
src.expolygons = offset2_ex(src.expolygons, float(scale_(10 * EPSILON)), - float(scale_(10 * EPSILON)));
src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON)));
layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
}
}

View File

@ -186,8 +186,8 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
// Produce 2 bands around the island, a safe band for dangling overhangs
// and an unsafe band for sloped overhangs.
// These masks include the original island
auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare);
auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare);
auto dangl_mask = expand(bottom_polygons, between_layers_offset, ClipperLib::jtSquare);
auto overh_mask = expand(bottom_polygons, slope_offset, ClipperLib::jtSquare);
// Absolutely hopeless overhangs are those outside the unsafe band
top.overhangs = diff_ex(*top.polygon, overh_mask);

View File

@ -367,6 +367,29 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
// Object is printed with the same extruder as the support.
m_support_params.can_merge_support_regions = true;
}
m_support_params.base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value));
m_support_params.interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.));
m_support_params.interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing();
m_support_params.interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / m_support_params.interface_spacing);
m_support_params.support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing();
m_support_params.support_density = std::min(1., m_support_params.support_material_flow.spacing() / m_support_params.support_spacing);
if (m_object_config->support_material_interface_layers.value == 0) {
// No interface layers allowed, print everything with the base support pattern.
m_support_params.interface_spacing = m_support_params.support_spacing;
m_support_params.interface_density = m_support_params.support_density;
}
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
m_support_params.with_sheath = m_object_config->support_material_with_sheath;
m_support_params.base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (m_support_params.support_density > 0.95 ? ipRectilinear : ipSupportBase);
m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
m_support_params.contact_fill_pattern =
(m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) ||
m_object_config->support_material_interface_pattern == smipConcentric ?
ipConcentric :
(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
}
// Using the std::deque as an allocator.
@ -397,6 +420,11 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr
dst.insert(dst.end(), src.begin(), src.end());
}
// Support layer that is covered by some form of dense interface.
static constexpr const std::initializer_list<PrintObjectSupportMaterial::SupporLayerType> support_types_interface {
PrintObjectSupportMaterial::sltRaftInterface, PrintObjectSupportMaterial::sltBottomContact, PrintObjectSupportMaterial::sltBottomInterface, PrintObjectSupportMaterial::sltTopContact, PrintObjectSupportMaterial::sltTopInterface
};
void PrintObjectSupportMaterial::generate(PrintObject &object)
{
BOOST_LOG_TRIVIAL(info) << "Support generator - Start";
@ -546,6 +574,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
// Sort the layers lexicographically by a raising print_z and a decreasing height.
std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
int layer_id = 0;
int layer_id_interface = 0;
assert(object.support_layers().empty());
for (size_t i = 0; i < layers_sorted.size();) {
// Find the last layer with roughly the same print_z, find the minimum layer height of all.
@ -557,17 +586,43 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z);
coordf_t height_min = layers_sorted[i]->height;
bool empty = true;
// For snug supports, layers where the direction of the support interface shall change are accounted for.
size_t num_interfaces = 0;
size_t num_top_contacts = 0;
double top_contact_bottom_z = 0;
for (size_t u = i; u < j; ++u) {
MyLayer &layer = *layers_sorted[u];
if (! layer.polygons.empty())
empty = false;
if (! layer.polygons.empty()) {
empty = false;
num_interfaces += one_of(layer.layer_type, support_types_interface);
if (layer.layer_type == sltTopContact) {
++ num_top_contacts;
assert(num_top_contacts <= 1);
// All top contact layers sharing this print_z shall also share bottom_z.
//assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON);
top_contact_bottom_z = layer.bottom_z;
}
}
layer.print_z = zavg;
height_min = std::min(height_min, layer.height);
}
if (! empty) {
// Here the upper_layer and lower_layer pointers are left to null at the support layers,
// as they are never used. These pointers are candidates for removal.
object.add_support_layer(layer_id ++, height_min, zavg);
bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces;
size_t this_layer_id_interface = layer_id_interface;
if (this_layer_contacts_only) {
// Find a supporting layer for its interface ID.
for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it)
if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) {
// other_layer supports this top contact layer. Assign a different support interface direction to this layer
// from the layer that supports it.
this_layer_id_interface = other_layer.interface_id() + 1;
}
}
object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg);
if (num_interfaces && ! this_layer_contacts_only)
++ layer_id_interface;
}
i = j;
}
@ -883,7 +938,14 @@ public:
// Merge the support polygons by applying morphological closing and inwards smoothing.
auto closing_distance = scaled<float>(m_support_material_closing_radius);
auto smoothing_distance = scaled<float>(m_extrusion_width);
return smooth_outward(offset(offset_ex(*m_support_polygons, closing_distance), - closing_distance), smoothing_distance);
#ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z),
{ { { diff_ex(expand(*m_support_polygons, closing_distance), closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "closed", "blue", 0.5f } },
{ { union_ex(smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance)) }, { "regularized", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } },
{ { union_ex(*m_support_polygons) }, { "src", "green", 0.5f } },
});
#endif /* SLIC3R_DEBUG */
return smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance);
}
assert(false);
return Polygons();
@ -1250,7 +1312,7 @@ namespace SupportMaterialInternal {
Polygons bridges;
{
// Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported.
Polygons lower_grown_slices = offset(lower_layer_polygons,
Polygons lower_grown_slices = expand(lower_layer_polygons,
//FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width.
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))),
SUPPORT_SURFACES_OFFSET_PARAMETERS);
@ -1414,7 +1476,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
overhang_polygons = to_polygons(layer.lslices);
#endif
// Expand for better stability.
contact_polygons = offset(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
contact_polygons = expand(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
}
else if (! layer.regions().empty())
{
@ -1475,20 +1537,20 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
//FIXME cache the lower layer offset if this layer has multiple regions.
#if 0
//FIXME this solution will trigger stupid supports for sharp corners, see GH #4874
diff_polygons = offset2(
diff_polygons = opening(
diff(layerm_polygons,
// Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they
// are not supporting this layer.
// However this may lead to a situation where regions at the current layer that are narrow thus not extrudable will generate unnecessary supports.
// For example, see GH issue #3094
offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)),
//FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to
opening(lower_layer_polygons, 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)),
//FIXME This opening is targeted to reduce very thin regions to support, but it may lead to
// no support at all for not so steep overhangs.
- 0.1f * fw, 0.1f * fw);
0.1f * fw);
#else
diff_polygons =
diff(layerm_polygons,
offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
expand(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
#endif
if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) {
// Don't support overhangs above the top surfaces.
@ -1500,7 +1562,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
// This is done to increase size of the supporting columns below, as they are calculated by
// propagating these contact surfaces downwards.
diff_polygons = diff(
intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons),
intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons),
lower_layer_polygons);
}
//FIXME add user defined filtering here based on minimal area or minimum radius or whatever.
@ -1516,7 +1578,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
// Subtracting them as they are may leave unwanted narrow
// residues of diff_polygons that would then be supported.
diff_polygons = diff(diff_polygons,
offset(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON)));
expand(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON)));
}
#ifdef SLIC3R_DEBUG
@ -1588,7 +1650,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
#endif // SLIC3R_DEBUG
enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]),
// Inflate just a tiny bit to avoid intersection of the overhang areas with the object.
offset(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
#ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z),
{ { layer.lslices, { "layer.lslices", "gray", 0.2f } },
@ -1720,7 +1782,7 @@ static inline void fill_contact_layer(
if (lower_layer_polygons_for_dense_interface_cache.empty())
lower_layer_polygons_for_dense_interface_cache =
//FIXME no_interface_offset * 0.6f offset is not quite correct, one shall derive it based on an angle thus depending on layer height.
offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS);
opening(lower_layer_polygons, no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS);
return lower_layer_polygons_for_dense_interface_cache;
};
@ -1733,7 +1795,7 @@ static inline void fill_contact_layer(
#endif // SLIC3R_DEBUG
));
// 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
bool reduce_interfaces = layer_id > 0 && ! slicing_params.soluble_interface;
bool reduce_interfaces = object_config.support_material_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface;
if (reduce_interfaces) {
// Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions.
Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface());
@ -1741,7 +1803,7 @@ static inline void fill_contact_layer(
dense_interface_polygons =
diff(
// Regularize the contour.
offset(dense_interface_polygons, no_interface_offset * 0.1f),
expand(dense_interface_polygons, no_interface_offset * 0.1f),
slices_margin.polygons);
// Support islands, to be stretched into a grid.
//FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons,
@ -1799,7 +1861,7 @@ static inline void fill_contact_layer(
dense_interface_polygons =
diff(
// Regularize the contour.
offset(dense_interface_polygons, no_interface_offset * 0.1f),
expand(dense_interface_polygons, no_interface_offset * 0.1f),
slices_margin.all_polygons);
// Support islands, to be stretched into a grid.
//FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons,
@ -1937,7 +1999,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
);
// Now apply the contact areas to the layer where they need to be made.
if (! contact_polygons.empty()) {
if (! contact_polygons.empty() || ! overhang_polygons.empty()) {
// Allocate the two empty layers.
auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex);
if (new_layer) {
@ -2041,8 +2103,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts(
layer_new.idx_object_layer_below = layer_id;
layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges;
//FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
//FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks.
layer_new.polygons = offset(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (! slicing_params.soluble_interface) {
// Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface,
@ -2081,7 +2142,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts(
// Trim the already created base layers above the current layer intersecting with the new bottom contacts layer.
//FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage?
touching = offset(touching, float(SCALED_EPSILON));
touching = expand(touching, float(SCALED_EPSILON));
for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) {
const Layer &layer_above = *object.layers()[layer_id_above];
if (layer_above.print_z > layer_new.print_z - EPSILON)
@ -2249,7 +2310,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
#endif
// These are the overhang surfaces. They are touching the object and they are not expanded away from the object.
// Use a slight positive offset to overlap the touching regions.
polygons_append(polygons_new, offset(*top_contact.overhang_polygons, float(SCALED_EPSILON)));
polygons_append(polygons_new, expand(*top_contact.overhang_polygons, float(SCALED_EPSILON)));
polygons_append(overhangs_projection, union_(polygons_new));
polygons_append(enforcers_projection, enforcers_new);
}
@ -2478,14 +2539,16 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int
// or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer.
// Intermediate layers are always printed with a normal etrusion flow (non-bridging).
size_t idx_layer_object = 0;
for (size_t idx_extreme = 0; idx_extreme < extremes.size(); ++ idx_extreme) {
size_t idx_extreme_first = 0;
if (! extremes.empty() && std::abs(extremes.front()->extreme_z() - m_slicing_params.raft_interface_top_z) < EPSILON) {
// This is a raft contact layer, its height has been decided in this->top_contact_layers().
// Ignore this layer when calculating the intermediate support layers.
assert(extremes.front()->layer_type == sltTopContact);
++ idx_extreme_first;
}
for (size_t idx_extreme = idx_extreme_first; idx_extreme < extremes.size(); ++ idx_extreme) {
MyLayer *extr2 = extremes[idx_extreme];
coordf_t extr2z = extr2->extreme_z();
if (std::abs(extr2z - m_slicing_params.raft_interface_top_z) < EPSILON) {
// This is a raft contact layer, its height has been decided in this->top_contact_layers().
assert(extr2->layer_type == sltTopContact);
continue;
}
if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) {
// This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers().
assert(extr2->layer_type == sltTopContact);
@ -2502,7 +2565,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int
}
assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON);
assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON);
MyLayer *extr1 = (idx_extreme == 0) ? nullptr : extremes[idx_extreme - 1];
MyLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1];
// Fuse a support layer firmly to the raft top interface (not to the raft contacts).
coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z();
assert(extr2z >= extr1z);
@ -2736,7 +2799,6 @@ void PrintObjectSupportMaterial::generate_base_layers(
ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons
layer_intermediate.layer_type = sltBase;
// For snug supports, expand the interfaces into the intermediate layer to make it printable.
#if 0
// coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing);
// Fillet the base polygons and trim them again with the top, interface and contact layers.
@ -2868,7 +2930,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
if (brim_inner) {
Polygons holes = ex.holes;
polygons_reverse(holes);
holes = offset(holes, - brim_separation, ClipperLib::jtRound, float(scale_(0.1)));
holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1)));
polygons_reverse(holes);
polygons_append(brim, std::move(holes));
} else
@ -2900,11 +2962,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
Polygons interface_polygons;
if (contacts != nullptr && ! contacts->polygons.empty())
polygons_append(interface_polygons, offset(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (interfaces != nullptr && ! interfaces->polygons.empty())
polygons_append(interface_polygons, offset(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (base_interfaces != nullptr && ! base_interfaces->polygons.empty())
polygons_append(interface_polygons, offset(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
// Output vector.
MyLayersPtr raft_layers;
@ -2931,7 +2993,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
new_layer.print_z = m_slicing_params.first_print_layer_height;
new_layer.height = m_slicing_params.first_print_layer_height;
new_layer.bottom_z = 0.;
new_layer.polygons = inflate_factor_1st_layer > 0 ? offset(base, inflate_factor_1st_layer) : base;
new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base;
}
// Insert the base layers.
for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) {
@ -2965,7 +3027,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width())));
float step = inflate_factor_1st_layer / nsteps;
for (int i = 0; i < nsteps; ++ i)
raft = diff(offset(raft, step), trimming);
raft = diff(expand(raft, step), trimming);
} else
raft = diff(raft, trimming);
if (contacts != nullptr)
@ -3028,26 +3090,43 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
interface_layers.assign(intermediate_layers.size(), nullptr);
if (num_base_interface_layers_top || num_base_interface_layers_bottom)
base_interface_layers.assign(intermediate_layers.size(), nullptr);
auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5;
auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density;
auto closing_distance = smoothing_distance; // scaled<float>(m_object_config->support_material_closing_radius.value);
tbb::spin_mutex layer_storage_mutex;
// Insert a new layer into base_interface_layers, if intersection with base exists.
auto insert_layer = [&layer_storage, &layer_storage_mutex](MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) {
auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius](
MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> MyLayer* {
assert(! bottom.empty() || ! top.empty());
MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type);
layer_new.print_z = intermediate_layer.print_z;
layer_new.bottom_z = intermediate_layer.bottom_z;
layer_new.height = intermediate_layer.height;
layer_new.bridging = intermediate_layer.bridging;
// Merge top into bottom, unite them with a safety offset.
append(bottom, std::move(top));
layer_new.polygons = intersection(union_safety_offset(std::move(bottom)), intermediate_layer.polygons);
// Subtract the interface from the base regions.
intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons);
if (subtract)
// Trim the base interface layer with the interface layer.
layer_new.polygons = diff(std::move(layer_new.polygons), *subtract);
//FIXME filter layer_new.polygons islands by a minimum area?
// $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
return &layer_new;
// Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners).
bottom = intersection(
snug_supports ?
smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) :
union_safety_offset(std::move(bottom)),
intermediate_layer.polygons);
if (! bottom.empty()) {
//FIXME Remove non-printable tiny islands, let them be printed using the base support.
//bottom = offset(offset_ex(std::move(bottom), - minimum_island_radius), minimum_island_radius);
if (! bottom.empty()) {
MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type);
layer_new.polygons = std::move(bottom);
layer_new.print_z = intermediate_layer.print_z;
layer_new.bottom_z = intermediate_layer.bottom_z;
layer_new.height = intermediate_layer.height;
layer_new.bridging = intermediate_layer.bridging;
// Subtract the interface from the base regions.
intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons);
if (subtract)
// Trim the base interface layer with the interface layer.
layer_new.polygons = diff(std::move(layer_new.polygons), *subtract);
//FIXME filter layer_new.polygons islands by a minimum area?
// $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
return &layer_new;
}
}
return nullptr;
};
tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
[&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer,
@ -3196,7 +3275,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
return;
if (! with_sheath) {
fill_expolygons_generate_paths(dst, offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON)), filler, density, role, flow);
fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow);
return;
}
@ -3208,7 +3287,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
// Clip the sheath path to avoid the extruder to get exactly on the first point of the loop.
double clip_length = spacing * 0.15;
for (ExPolygon &expoly : offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()))) {
for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) {
// Don't reorder the skirt and its infills.
std::unique_ptr<ExtrusionEntityCollection> eec;
if (no_sort) {
@ -3461,10 +3540,10 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
// make more loops
Polygons loop_polygons = loops0;
for (int i = 1; i < n_contact_loops; ++ i)
polygons_append(loop_polygons,
offset2(
polygons_append(loop_polygons,
opening(
loops0,
- i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(),
i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(),
0.5f * flow.scaled_spacing()));
// Clip such loops to the side oriented towards the object.
// Collect split points, so they will be recognized after the clipping.
@ -3476,7 +3555,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
map_split_points[it->first_point()] = -1;
loop_lines.push_back(it->split_at_first_point());
}
loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN)));
loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN)));
// Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces.
// Try to connect them.
for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) {
@ -3816,27 +3895,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width());
loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0;
float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value));
float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.));
coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing();
coordf_t interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / interface_spacing);
coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing();
coordf_t support_density = std::min(1., m_support_params.support_material_flow.spacing() / support_spacing);
if (m_object_config->support_material_interface_layers.value == 0) {
// No interface layers allowed, print everything with the base support pattern.
interface_spacing = support_spacing;
interface_density = support_density;
}
// Prepare fillers.
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
bool with_sheath = m_object_config->support_material_with_sheath;
InfillPattern infill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (support_density > 0.95 ? ipRectilinear : ipSupportBase);
std::vector<float> angles;
angles.push_back(base_angle);
if (support_pattern == smpRectilinearGrid)
angles.push_back(interface_angle);
std::vector<float> angles { m_support_params.base_angle };
if (m_object_config->support_material_pattern == smpRectilinearGrid)
angles.push_back(m_support_params.interface_angle);
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
@ -3848,16 +3909,16 @@ void PrintObjectSupportMaterial::generate_toolpaths(
float raft_angle_interface = 0.f;
if (m_slicing_params.base_raft_layers > 1) {
// There are all raft layer types (1st layer, base, interface & contact layers) available.
raft_angle_1st_layer = interface_angle;
raft_angle_base = base_angle;
raft_angle_interface = interface_angle;
raft_angle_1st_layer = m_support_params.interface_angle;
raft_angle_base = m_support_params.base_angle;
raft_angle_interface = m_support_params.interface_angle;
} else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) {
// 1st layer, interface & contact layers available.
raft_angle_1st_layer = base_angle;
raft_angle_1st_layer = m_support_params.base_angle;
if (this->has_support())
// Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer.
raft_angle_1st_layer += 0.7854f;
raft_angle_interface = interface_angle;
raft_angle_interface = m_support_params.interface_angle;
} else if (m_slicing_params.interface_raft_layers == 1) {
// Only the contact raft layer is non-empty, which will be printed as the 1st layer.
assert(m_slicing_params.base_raft_layers == 0);
@ -3874,7 +3935,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1));
tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers),
[this, &support_layers, &raft_layers,
infill_pattern, &bbox_object, support_density, interface_density, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor, with_sheath]
&bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) {
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
{
@ -3883,8 +3944,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
assert(support_layer.support_fills.entities.empty());
MyLayer &raft_layer = *raft_layers[support_layer_id];
std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(ipRectilinear));
std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern));
std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.interface_fill_pattern));
std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.base_fill_pattern));
filler_interface->set_bounding_box(bbox_object);
filler_support->set_bounding_box(bbox_object);
@ -3900,17 +3961,17 @@ void PrintObjectSupportMaterial::generate_toolpaths(
Fill * filler = filler_support.get();
filler->angle = raft_angle_base;
filler->spacing = m_support_params.support_material_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density));
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density));
fill_expolygons_with_sheath_generate_paths(
// Destination
support_layer.support_fills.entities,
// Regions to fill
to_infill_polygons,
// Filler and its parameters
filler, float(support_density),
filler, float(m_support_params.support_density),
// Extrusion parameters
erSupportMaterial, flow,
with_sheath, false);
m_support_params.with_sheath, false);
}
}
@ -3929,7 +3990,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
filler->spacing = m_support_params.support_material_flow.spacing();
assert(! raft_layer.bridging);
flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter());
density = float(interface_density);
density = float(m_support_params.interface_density);
} else
continue;
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
@ -3970,15 +4031,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
};
std::vector<LayerCache> layer_caches(support_layers.size());
const auto fill_type_interface =
(m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) ||
m_object_config->support_material_interface_pattern == smipConcentric ?
ipConcentric : ipRectilinear;
tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, support_layers.size()),
[this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor,
infill_pattern, &bbox_object, support_density, fill_type_interface, interface_density, interface_angle, &angles, link_max_length_factor, with_sheath]
&bbox_object, &angles, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) {
// Indices of the 1st layer in their respective container at the support layer height.
size_t idx_layer_bottom_contact = size_t(-1);
@ -3987,14 +4042,14 @@ void PrintObjectSupportMaterial::generate_toolpaths(
size_t idx_layer_interface = size_t(-1);
size_t idx_layer_base_interface = size_t(-1);
const auto fill_type_first_layer = ipRectilinear;
auto filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(fill_type_interface));
auto filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
// Filler for the 1st layer interface, if different from filler_interface.
auto filler_first_layer_ptr = std::unique_ptr<Fill>(range.begin() == 0 && fill_type_interface != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr);
auto filler_first_layer_ptr = std::unique_ptr<Fill>(range.begin() == 0 && m_support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr);
// Pointer to the 1st layer interface filler.
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get();
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr : Fill::new_from_type(ipRectilinear));
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern));
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr : Fill::new_from_type(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase));
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.base_fill_pattern));
filler_interface->set_bounding_box(bbox_object);
if (filler_first_layer_ptr)
filler_first_layer_ptr->set_bounding_box(bbox_object);
@ -4005,6 +4060,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
{
SupportLayer &support_layer = *support_layers[support_layer_id];
LayerCache &layer_cache = layer_caches[support_layer_id];
float interface_angle_delta = m_object_config->support_material_style.value == smsSnug ?
(support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) :
0;
// Find polygons with the same print_z.
MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
@ -4084,8 +4142,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// If zero interface layers are configured, use the same angle as for the base layers.
angles[support_layer_id % angles.size()] :
// Use interface angle for the interface layers.
interface_angle;
double density = interface_as_base ? support_density : interface_density;
m_support_params.interface_angle + interface_angle_delta;
double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density;
filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing();
filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density));
fill_expolygons_generate_paths(
@ -4106,9 +4164,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
assert(! base_interface_layer.layer->bridging);
Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
filler->angle = interface_angle;
filler->angle = m_support_params.interface_angle + interface_angle_delta;
filler->spacing = m_support_params.support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density));
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.interface_density));
fill_expolygons_generate_paths(
// Destination
base_interface_layer.extrusions,
@ -4116,7 +4174,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// Regions to fill
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
// Filler and its parameters
filler, float(interface_density),
filler, float(m_support_params.interface_density),
// Extrusion parameters
erSupportMaterial, interface_flow);
}
@ -4130,9 +4188,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
assert(! base_layer.layer->bridging);
auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height));
filler->spacing = m_support_params.support_material_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density));
float density = float(support_density);
bool sheath = with_sheath;
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density));
float density = float(m_support_params.support_density);
bool sheath = m_support_params.with_sheath;
bool no_sort = false;
if (base_layer.layer->bottom_z < EPSILON) {
// Base flange (the 1st layer).

View File

@ -132,6 +132,18 @@ public:
// coordf_t support_layer_height_max;
coordf_t gap_xy;
float base_angle;
float interface_angle;
coordf_t interface_spacing;
coordf_t interface_density;
coordf_t support_spacing;
coordf_t support_density;
InfillPattern base_fill_pattern;
InfillPattern interface_fill_pattern;
InfillPattern contact_fill_pattern;
bool with_sheath;
};
// Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained

View File

@ -1617,7 +1617,7 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c
/* The following line is commented out because it can generate wrong polygons,
see for example issue #661 */
//ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset);
//ExPolygons ex_slices = closing(p_slices, safety_offset);
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
size_t holes_count = 0;

View File

@ -919,7 +919,6 @@ bool GUI_App::on_init_inner()
}
});
Bind(EVT_SLIC3R_ALPHA_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
//app_config->set("version_alpha_online", into_u8(evt.GetString()));
app_config->save();
if (this->plater_ != nullptr && app_config->get("notify_testing_release") == "1") {
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) {
@ -928,7 +927,6 @@ bool GUI_App::on_init_inner()
}
});
Bind(EVT_SLIC3R_BETA_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
//app_config->set("version_beta_online", into_u8(evt.GetString()));
app_config->save();
if (this->plater_ != nullptr && app_config->get("notify_testing_release") == "1") {
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) {
@ -2022,14 +2020,14 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
menu->Append(local_menu, _L("&Configuration"));
}
void GUI_App::open_preferences(size_t open_on_tab)
void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option)
{
bool app_layout_changed = false;
{
// the dialog needs to be destroyed before the call to recreate_GUI()
// or sometimes the application crashes into wxDialogBase() destructor
// so we put it into an inner scope
PreferencesDialog dlg(mainframe, open_on_tab);
PreferencesDialog dlg(mainframe, open_on_tab, highlight_option);
dlg.ShowModal();
app_layout_changed = dlg.settings_layout_changed();
#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER

View File

@ -259,7 +259,7 @@ public:
wxString current_language_code_safe() const;
bool is_localized() const { return m_wxLocale->GetLocale() != "English"; }
void open_preferences(size_t open_on_tab = 0);
void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
virtual bool OnExceptionInMainLoop() override;
// Calls wxLaunchDefaultBrowser if user confirms in dialog.

View File

@ -77,8 +77,10 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/)
m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(50 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()),
wxLC_ICON | wxSIMPLE_BORDER);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this);
m_list_ctrl->Bind(wxEVT_LIST_KEY_DOWN, &GalleryDialog::key_down, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &GalleryDialog::show_context_menu, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent& event) {
m_selected_items.clear();
select(event);
@ -111,19 +113,11 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/)
this->Bind(wxEVT_BUTTON, method, this, ID);
};
auto enable_del_fn = [this]() {
if (m_selected_items.empty())
return false;
for (const Item& item : m_selected_items)
if (item.is_system)
return false;
return true;
};
add_btn(0, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes);
add_btn(1, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, enable_del_fn);
add_btn(2, ID_BTN_REPLACE_CUSTOM_PNG, _L("Replace PNG"), _L("Replace PNG for custom shape. You can't raplace PNG for system shape"),&GalleryDialog::replace_custom_png, [this]() { return (m_selected_items.size() == 1 && !m_selected_items[0].is_system); });
buttons->InsertStretchSpacer(3, 2* BORDER_W);
size_t btn_pos = 0;
add_btn(btn_pos++, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes);
add_btn(btn_pos++, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, [this](){ return can_delete(); });
//add_btn(btn_pos++, ID_BTN_REPLACE_CUSTOM_PNG, _L("Change thumbnail"), _L("Replace PNG for custom shape. You can't raplace thimbnail for system shape"), &GalleryDialog::change_thumbnail, [this](){ return can_change_thumbnail(); });
buttons->InsertStretchSpacer(btn_pos, 2* BORDER_W);
load_label_icon_list();
@ -146,6 +140,21 @@ GalleryDialog::~GalleryDialog()
{
}
bool GalleryDialog::can_delete()
{
if (m_selected_items.empty())
return false;
for (const Item& item : m_selected_items)
if (item.is_system)
return false;
return true;
}
bool GalleryDialog::can_change_thumbnail()
{
return (m_selected_items.size() == 1 && !m_selected_items[0].is_system);
}
void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect)
{
const int& em = em_unit();
@ -405,7 +414,7 @@ void GalleryDialog::add_custom_shapes(wxEvent& event)
load_files(input_files);
}
void GalleryDialog::del_custom_shapes(wxEvent& event)
void GalleryDialog::del_custom_shapes()
{
auto custom_dir = get_dir(false);
@ -438,7 +447,7 @@ static void show_warning(const wxString& title, const std::string& error_file_ty
dialog.ShowModal();
}
void GalleryDialog::replace_custom_png(wxEvent& event)
void GalleryDialog::change_thumbnail()
{
if (m_selected_items.size() != 1 || m_selected_items[0].is_system)
return;
@ -494,6 +503,23 @@ void GalleryDialog::deselect(wxListEvent& event)
m_selected_items.erase(std::remove_if(m_selected_items.begin(), m_selected_items.end(), [name](Item item) { return item.name == name; }));
}
void GalleryDialog::show_context_menu(wxListEvent& event)
{
wxMenu* menu = new wxMenu();
if (can_delete())
append_menu_item(menu, wxID_ANY, _L("Delete"), "", [this](wxCommandEvent&) { del_custom_shapes(); });
if (can_change_thumbnail())
append_menu_item(menu, wxID_ANY, _L("Change thumbnail"), "", [this](wxCommandEvent&) { change_thumbnail(); });
this->PopupMenu(menu);
}
void GalleryDialog::key_down(wxListEvent& event)
{
if (can_delete() && (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK))
del_custom_shapes();
}
void GalleryDialog::update()
{
m_selected_items.clear();

View File

@ -33,10 +33,17 @@ class GalleryDialog : public DPIDialog
void load_label_icon_list();
void add_custom_shapes(wxEvent& event);
void del_custom_shapes(wxEvent& event);
void replace_custom_png(wxEvent& event);
void del_custom_shapes();
void del_custom_shapes(wxEvent& event) { del_custom_shapes(); }
void change_thumbnail();
void change_thumbnail(wxEvent& event) { change_thumbnail(); }
void select(wxListEvent& event);
void deselect(wxListEvent& event);
void show_context_menu(wxListEvent& event);
void key_down(wxListEvent& event);
bool can_delete();
bool can_change_thumbnail();
void update();

View File

@ -919,7 +919,7 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp
}
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
wxGetApp().open_preferences(2);
wxGetApp().open_preferences(2, "show_hints");
}
ImGui::PopStyleColor(5);

View File

@ -72,6 +72,11 @@ void OG_CustomCtrl::init_ctrl_lines()
const std::vector<Line>& og_lines = opt_group->get_lines();
for (const Line& line : og_lines)
{
if (line.is_separator()) {
ctrl_lines.emplace_back(CtrlLine(0, this, line));
continue;
}
if (line.full_width && (
// description line
line.widget != nullptr ||
@ -124,6 +129,15 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/)
line_height = win_height;
};
auto correct_horiz_pos = [this](int& h_pos, Field* field) {
if (m_max_win_width > 0 && field->getWindow()) {
int win_width = field->getWindow()->GetSize().GetWidth();
if (dynamic_cast<CheckBox*>(field))
win_width *= 0.5;
h_pos += m_max_win_width - win_width;
}
};
for (CtrlLine& ctrl_line : ctrl_lines) {
if (&ctrl_line.og_line == &line)
{
@ -160,6 +174,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/)
h_pos += 3 * blinking_button_width;
Field* field = opt_group->get_field(option_set.front().opt_id);
correct_line_height(ctrl_line.height, field->getWindow());
correct_horiz_pos(h_pos, field);
break;
}
@ -189,8 +204,10 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/)
}
h_pos += (opt.opt.gui_type == ConfigOptionDef::GUIType::legend ? 1 : 3) * blinking_button_width;
if (field == field_in)
if (field == field_in) {
correct_horiz_pos(h_pos, field);
break;
}
if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend)
h_pos += 2 * blinking_button_width;
@ -361,6 +378,28 @@ void OG_CustomCtrl::correct_widgets_position(wxSizer* widget, const Line& line,
}
};
void OG_CustomCtrl::init_max_win_width()
{
if (opt_group->ctrl_horiz_alignment == wxALIGN_RIGHT && m_max_win_width == 0)
for (CtrlLine& line : ctrl_lines) {
if (int max_win_width = line.get_max_win_width();
m_max_win_width < max_win_width)
m_max_win_width = max_win_width;
}
}
void OG_CustomCtrl::set_max_win_width(int max_win_width)
{
if (m_max_win_width == max_win_width)
return;
m_max_win_width = max_win_width;
for (CtrlLine& line : ctrl_lines)
line.correct_items_positions();
GetParent()->Layout();
}
void OG_CustomCtrl::msw_rescale()
{
#ifdef __WXOSX__
@ -374,6 +413,8 @@ void OG_CustomCtrl::msw_rescale()
m_bmp_mode_sz = create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize();
m_bmp_blinking_sz = create_scaled_bitmap("search_blink", this).GetSize();
m_max_win_width = 0;
wxCoord v_pos = 0;
for (CtrlLine& line : ctrl_lines) {
line.msw_rescale();
@ -407,6 +448,21 @@ OG_CustomCtrl::CtrlLine::CtrlLine( wxCoord height,
}
}
int OG_CustomCtrl::CtrlLine::get_max_win_width()
{
int max_win_width = 0;
if (!draw_just_act_buttons) {
const std::vector<Option>& option_set = og_line.get_options();
for (auto opt : option_set) {
Field* field = ctrl->opt_group->get_field(opt.opt_id);
if (field && field->getWindow())
max_win_width = field->getWindow()->GetSize().GetWidth();
}
}
return max_win_width;
}
void OG_CustomCtrl::CtrlLine::correct_items_positions()
{
if (draw_just_act_buttons || !is_visible)
@ -447,6 +503,8 @@ void OG_CustomCtrl::CtrlLine::msw_rescale()
void OG_CustomCtrl::CtrlLine::update_visibility(ConfigOptionMode mode)
{
if (og_line.is_separator())
return;
const std::vector<Option>& option_set = og_line.get_options();
const ConfigOptionMode& line_mode = option_set.front().opt.mode;
@ -480,8 +538,25 @@ void OG_CustomCtrl::CtrlLine::update_visibility(ConfigOptionMode mode)
correct_items_positions();
}
void OG_CustomCtrl::CtrlLine::render_separator(wxDC& dc, wxCoord v_pos)
{
wxPoint begin(ctrl->m_h_gap, v_pos);
wxPoint end(ctrl->GetSize().GetWidth() - ctrl->m_h_gap, v_pos);
wxPen pen, old_pen = pen = dc.GetPen();
pen.SetColour(*wxLIGHT_GREY);
dc.SetPen(pen);
dc.DrawLine(begin, end);
dc.SetPen(old_pen);
}
void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
{
if (is_separator()) {
render_separator(dc, v_pos);
return;
}
Field* field = ctrl->opt_group->get_field(og_line.get_options().front().opt_id);
bool suppress_hyperlinks = get_app_config()->get("suppress_hyperlinks") == "1";
@ -522,6 +597,8 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
{
if (field && field->undo_to_sys_bitmap())
h_pos = draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap()->bmp(), field->undo_bitmap()->bmp(), field->blink()) + ctrl->m_h_gap;
else if (field && !field->undo_to_sys_bitmap() && field->blink())
draw_blinking_bmp(dc, wxPoint(h_pos, v_pos), field->blink());
// update width for full_width fields
if (option_set.front().opt.full_width && field->getWindow())
field->getWindow()->SetSize(ctrl->GetSize().x - h_pos, -1);

View File

@ -33,6 +33,8 @@ class OG_CustomCtrl :public wxPanel
wxSize m_bmp_mode_sz;
wxSize m_bmp_blinking_sz;
int m_max_win_width{0};
struct CtrlLine {
wxCoord height { wxDefaultCoord };
OG_CustomCtrl* ctrl { nullptr };
@ -50,16 +52,20 @@ class OG_CustomCtrl :public wxPanel
bool draw_mode_bitmap = true);
~CtrlLine() { ctrl = nullptr; }
int get_max_win_width();
void correct_items_positions();
void msw_rescale();
void update_visibility(ConfigOptionMode mode);
void render_separator(wxDC& dc, wxCoord v_pos);
void render(wxDC& dc, wxCoord v_pos);
wxCoord draw_mode_bmp(wxDC& dc, wxCoord v_pos);
wxCoord draw_text (wxDC& dc, wxPoint pos, const wxString& text, const wxColour* color, int width, bool is_url = false);
wxPoint draw_blinking_bmp(wxDC& dc, wxPoint pos, bool is_blinking);
wxCoord draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id = 0);
bool launch_browser() const;
bool is_separator() const { return og_line.is_separator(); }
std::vector<wxRect> rects_undo_icon;
std::vector<wxRect> rects_undo_to_sys_icon;
@ -86,6 +92,9 @@ public:
bool update_visibility(ConfigOptionMode mode);
void correct_window_position(wxWindow* win, const Line& line, Field* field = nullptr);
void correct_widgets_position(wxSizer* widget, const Line& line, Field* field = nullptr);
void init_max_win_width();
void set_max_win_width(int max_win_width);
int get_max_win_width() { return m_max_win_width; }
void msw_rescale();
void sys_color_changed();

View File

@ -126,6 +126,12 @@ bool OptionsGroup::is_legend_line()
return false;
}
void OptionsGroup::set_max_win_width(int max_win_width)
{
if (custom_ctrl)
custom_ctrl->set_max_win_width(max_win_width);
}
void OptionsGroup::show_field(const t_config_option_key& opt_key, bool show/* = true*/)
{
Field* field = get_field(opt_key);
@ -185,8 +191,16 @@ void OptionsGroup::append_line(const Line& line)
m_options_mode.push_back(option_set[0].opt.mode);
}
void OptionsGroup::append_separator()
{
m_lines.emplace_back(Line());
}
void OptionsGroup::activate_line(Line& line)
{
if (line.is_separator())
return;
m_use_custom_ctrl_as_parent = false;
if (line.full_width && (
@ -396,7 +410,7 @@ void OptionsGroup::activate_line(Line& line)
}
// create all controls for the option group from the m_lines
bool OptionsGroup::activate(std::function<void()> throw_if_canceled)
bool OptionsGroup::activate(std::function<void()> throw_if_canceled/* = [](){}*/, int horiz_alignment/* = wxALIGN_LEFT*/)
{
if (sizer)//(!sizer->IsEmpty())
return false;
@ -436,6 +450,10 @@ bool OptionsGroup::activate(std::function<void()> throw_if_canceled)
throw_if_canceled();
activate_line(line);
}
ctrl_horiz_alignment = horiz_alignment;
if (custom_ctrl)
custom_ctrl->init_max_win_width();
} catch (UIBuildCanceled&) {
auto p = sizer;
this->clear();

View File

@ -49,6 +49,7 @@ using t_option = std::unique_ptr<Option>; //!
/// Represents option lines
class Line {
bool m_is_separator{ false };
public:
wxString label;
wxString label_tooltip;
@ -71,6 +72,9 @@ public:
}
Line(wxString label, wxString tooltip) :
label(_(label)), label_tooltip(_(tooltip)) {}
Line() : m_is_separator(true) {}
bool is_separator() const { return m_is_separator; }
const std::vector<widget_t>& get_extra_widgets() const {return m_extra_widgets;}
const std::vector<Option>& get_options() const { return m_options; }
@ -95,6 +99,7 @@ public:
size_t label_width = 20 ;// {200};
wxSizer* sizer {nullptr};
OG_CustomCtrl* custom_ctrl{ nullptr };
int ctrl_horiz_alignment{ wxALIGN_LEFT};
column_t extra_column {nullptr};
t_change m_on_change { nullptr };
// To be called when the field loses focus, to assign a new initial value to the field.
@ -124,12 +129,13 @@ public:
void activate_line(Line& line);
// create all controls for the option group from the m_lines
bool activate(std::function<void()> throw_if_canceled = [](){});
bool activate(std::function<void()> throw_if_canceled = [](){}, int horiz_alignment = wxALIGN_LEFT);
// delete all controls from the option group
void clear(bool destroy_custom_ctrl = false);
Line create_single_option_line(const Option& option, const wxString& path = wxEmptyString) const;
void append_single_option_line(const Option& option, const wxString& path = wxEmptyString) { append_line(create_single_option_line(option, path)); }
void append_separator();
// return a non-owning pointer reference
inline Field* get_field(const t_config_option_key& id) const{
@ -171,6 +177,9 @@ public:
wxGridSizer* get_grid_sizer() { return m_grid_sizer; }
const std::vector<Line>& get_lines() { return m_lines; }
bool is_legend_line();
// if we have to set the same control alignment for different option groups,
// we have to set same max contrtol width to all of them
void set_max_win_width(int max_win_width);
protected:
std::map<t_config_option_key, Option> m_options;

View File

@ -8,11 +8,42 @@
#include <wx/notebook.h>
#include "Notebook.hpp"
#include "ButtonsDescription.hpp"
#include "OG_CustomCtrl.hpp"
#include <initializer_list>
namespace Slic3r {
static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values& enum_keys_map)
{
t_config_enum_names names;
int cnt = 0;
for (const auto& kvp : enum_keys_map)
cnt = std::max(cnt, kvp.second);
cnt += 1;
names.assign(cnt, "");
for (const auto& kvp : enum_keys_map)
names[kvp.second] = kvp.first;
return names;
}
#define CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NAME) \
static t_config_enum_names s_keys_names_##NAME = enum_names_from_keys_map(s_keys_map_##NAME); \
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values() { return s_keys_map_##NAME; } \
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names() { return s_keys_names_##NAME; }
static const t_config_enum_values s_keys_map_NotifyReleaseMode = {
{"all", NotifyReleaseAll},
{"release", NotifyReleaseOnly},
{"none", NotifyReleaseNone},
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NotifyReleaseMode)
namespace GUI {
PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab) :
PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab, const std::string& highlight_opt_key) :
DPIDialog(parent, wxID_ANY, _L("Preferences"), wxDefaultPosition,
wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
{
@ -20,6 +51,8 @@ PreferencesDialog::PreferencesDialog(wxWindow* parent, int selected_tab) :
isOSX = true;
#endif
build(selected_tab);
if (!highlight_opt_key.empty())
init_highlighter(highlight_opt_key);
}
static std::shared_ptr<ConfigOptionsGroup>create_options_tab(const wxString& title, wxBookCtrlBase* tabs)
@ -39,7 +72,7 @@ static std::shared_ptr<ConfigOptionsGroup>create_options_tab(const wxString& tit
static void activate_options_tab(std::shared_ptr<ConfigOptionsGroup> optgroup)
{
optgroup->activate();
optgroup->activate([](){}, wxALIGN_RIGHT);
optgroup->update_visibility(comSimple);
wxBoxSizer* sizer = static_cast<wxBoxSizer*>(static_cast<wxPanel*>(optgroup->parent())->GetSizer());
sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10);
@ -116,6 +149,8 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "version_check");
m_optgroup_general->append_single_option_line(option);
m_optgroup_general->append_separator();
// Please keep in sync with ConfigWizard
def.label = L("Export sources full pathnames to 3mf and amf");
def.type = coBool;
@ -141,6 +176,8 @@ void PreferencesDialog::build(size_t selected_tab)
m_optgroup_general->append_single_option_line(option);
#endif // _WIN32
m_optgroup_general->append_separator();
// Please keep in sync with ConfigWizard
def.label = L("Update built-in Presets automatically");
def.type = coBool;
@ -165,6 +202,8 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "show_incompatible_presets");
m_optgroup_general->append_single_option_line(option);
m_optgroup_general->append_separator();
def.label = L("Show drop project dialog");
def.type = coBool;
def.tooltip = L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load.");
@ -172,7 +211,6 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "show_drop_project_dialog");
m_optgroup_general->append_single_option_line(option);
#if __APPLE__
def.label = L("Allow just a single PrusaSlicer instance");
def.type = coBool;
@ -186,6 +224,8 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "single_instance");
m_optgroup_general->append_single_option_line(option);
m_optgroup_general->append_separator();
def.label = L("Ask for unsaved changes when closing application or loading new project");
def.type = coBool;
def.tooltip = L("Always ask for unsaved changes, when: \n"
@ -230,6 +270,8 @@ void PreferencesDialog::build(size_t selected_tab)
m_optgroup_general->append_single_option_line(option);
#endif
m_optgroup_general->append_separator();
// Show/Hide splash screen
def.label = L("Show splash screen");
def.type = coBool;
@ -291,7 +333,15 @@ void PreferencesDialog::build(size_t selected_tab)
m_optgroup_gui->m_on_change = [this, tabs](t_config_option_key opt_key, boost::any value) {
if (opt_key == "suppress_hyperlinks")
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "";
else
else if (opt_key == "notify_release") {
int val_int = boost::any_cast<int>(value);
for (const auto& item : s_keys_map_NotifyReleaseMode) {
if (item.second == val_int) {
m_values[opt_key] = item.first;
break;
}
}
} else
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
if (opt_key == "use_custom_toolbar_size") {
@ -300,6 +350,8 @@ void PreferencesDialog::build(size_t selected_tab)
tabs->Layout();
this->layout();
}
};
def.label = L("Sequential slider applied only to top layer");
@ -361,7 +413,9 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "tabs_as_menu");
m_optgroup_gui->append_single_option_line(option);
#endif
m_optgroup_gui->append_separator();
def.label = L("Show \"Tip of the day\" notification after start");
def.type = coBool;
def.tooltip = L("If enabled, useful hints are displayed at startup.");
@ -369,6 +423,24 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "show_hints");
m_optgroup_gui->append_single_option_line(option);
ConfigOptionDef def_enum;
def_enum.label = L("Notify about new releases");
def_enum.type = coEnum;
def_enum.tooltip = L("You will be notified about new release after startup acordingly: All = Regular release and alpha / beta releases. Release only = regular release.");
def_enum.enum_keys_map = &ConfigOptionEnum<NotifyReleaseMode>::get_enum_values();
def_enum.enum_values.push_back("all");
def_enum.enum_values.push_back("release");
def_enum.enum_values.push_back("none");
def_enum.enum_labels.push_back(L("All"));
def_enum.enum_labels.push_back(L("Release only"));
def_enum.enum_labels.push_back(L("None"));
def_enum.mode = comSimple;
def_enum.set_default_value(new ConfigOptionEnum<NotifyReleaseMode>(static_cast<NotifyReleaseMode>(s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")))));
option = Option(def_enum, "notify_release");
m_optgroup_gui->append_single_option_line(option);
m_optgroup_gui->append_separator();
def.label = L("Use custom size for toolbar icons");
def.type = coBool;
def.tooltip = L("If enabled, you can change size of toolbar icons manually.");
@ -376,15 +448,12 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "use_custom_toolbar_size");
m_optgroup_gui->append_single_option_line(option);
def.label = L("Notify about testing releases");
def.type = coBool;
def.tooltip = L("If enabled, you will be notified about alpha / beta releases available for download.");
def.set_default_value(new ConfigOptionBool{ app_config->get("notify_testing_release") == "1" });
option = Option(def, "notify_testing_release");
m_optgroup_gui->append_single_option_line(option);
}
activate_options_tab(m_optgroup_gui);
// set Field for notify_release to its value to activate the object
boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release"));
m_optgroup_gui->get_field("notify_release")->set_value(val, false);
if (is_editor) {
create_icon_size_slider();
@ -413,6 +482,9 @@ void PreferencesDialog::build(size_t selected_tab)
}
#endif // ENABLE_ENVIRONMENT_MAP
// update alignment of the controls for all tabs
update_ctrls_alignment();
if (selected_tab < tabs->GetPageCount())
tabs->SetSelection(selected_tab);
@ -432,6 +504,20 @@ void PreferencesDialog::build(size_t selected_tab)
this->CenterOnParent();
}
void PreferencesDialog::update_ctrls_alignment()
{
int max_ctrl_width{ 0 };
std::initializer_list<ConfigOptionsGroup*> og_list = { m_optgroup_general.get(), m_optgroup_camera.get(), m_optgroup_gui.get() };
for (auto og : og_list) {
if (int max = og->custom_ctrl->get_max_win_width();
max_ctrl_width < max)
max_ctrl_width = max;
}
if (max_ctrl_width)
for (auto og : og_list)
og->custom_ctrl->set_max_win_width(max_ctrl_width);
}
void PreferencesDialog::accept(wxEvent&)
{
// if (m_values.find("no_defaults") != m_values.end()
@ -671,6 +757,71 @@ void PreferencesDialog::create_settings_text_color_widget()
m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());
}
void PreferencesDialog::init_highlighter(const t_config_option_key& opt_key)
{
m_highlighter.set_timer_owner(this, 0);
this->Bind(wxEVT_TIMER, [this](wxTimerEvent&)
{
m_highlighter.blink();
});
std::pair<OG_CustomCtrl*, bool*> ctrl = { nullptr, nullptr };
for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui }) {
ctrl = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, -1);
if (ctrl.first && ctrl.second) {
m_highlighter.init(ctrl);
break;
}
}
}
void PreferencesDialog::PreferencesHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/)
{
m_timer.SetOwner(owner, timerid);
}
void PreferencesDialog::PreferencesHighlighter::init(std::pair<OG_CustomCtrl*, bool*> params)
{
if (m_timer.IsRunning())
invalidate();
if (!params.first || !params.second)
return;
m_timer.Start(300, false);
m_custom_ctrl = params.first;
m_show_blink_ptr = params.second;
*m_show_blink_ptr = true;
m_custom_ctrl->Refresh();
}
void PreferencesDialog::PreferencesHighlighter::invalidate()
{
m_timer.Stop();
if (m_custom_ctrl && m_show_blink_ptr) {
*m_show_blink_ptr = false;
m_custom_ctrl->Refresh();
m_show_blink_ptr = nullptr;
m_custom_ctrl = nullptr;
}
m_blink_counter = 0;
}
void PreferencesDialog::PreferencesHighlighter::blink()
{
if (m_custom_ctrl && m_show_blink_ptr) {
*m_show_blink_ptr = !*m_show_blink_ptr;
m_custom_ctrl->Refresh();
}
else
return;
if ((++m_blink_counter) == 11)
invalidate();
}
} // GUI
} // Slic3r

View File

@ -5,14 +5,23 @@
#include "GUI_Utils.hpp"
#include <wx/dialog.h>
#include <wx/timer.h>
#include <map>
class wxColourPickerCtrl;
namespace Slic3r {
enum NotifyReleaseMode {
NotifyReleaseAll,
NotifyReleaseOnly,
NotifyReleaseNone
};
namespace GUI {
class ConfigOptionsGroup;
class OG_CustomCtrl;
class PreferencesDialog : public DPIDialog
{
@ -32,13 +41,14 @@ class PreferencesDialog : public DPIDialog
bool m_recreate_GUI{false};
public:
explicit PreferencesDialog(wxWindow* parent, int selected_tab = 0);
explicit PreferencesDialog(wxWindow* parent, int selected_tab = 0, const std::string& highlight_opt_key = std::string());
~PreferencesDialog() = default;
bool settings_layout_changed() const { return m_settings_layout_changed; }
bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; }
bool recreate_GUI() const { return m_recreate_GUI; }
void build(size_t selected_tab = 0);
void update_ctrls_alignment();
void accept(wxEvent&);
protected:
@ -47,6 +57,22 @@ protected:
void create_icon_size_slider();
void create_settings_mode_widget();
void create_settings_text_color_widget();
void init_highlighter(const t_config_option_key& opt_key);
struct PreferencesHighlighter
{
void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY);
void init(std::pair<OG_CustomCtrl*, bool*>);
void blink();
void invalidate();
private:
OG_CustomCtrl* m_custom_ctrl{ nullptr };
bool* m_show_blink_ptr{ nullptr };
int m_blink_counter{ 0 };
wxTimer m_timer;
}
m_highlighter;
};
} // GUI

View File

@ -168,6 +168,7 @@ struct PresetUpdater::priv
bool get_file(const std::string &url, const fs::path &target_path) const;
void prune_tmps() const;
void sync_version() const;
void parse_version_string(const std::string& body) const;
void sync_config(const VendorMap vendors);
void check_install_indices() const;
@ -263,60 +264,68 @@ void PresetUpdater::priv::sync_version() const
})
.on_complete([&](std::string body, unsigned /* http_status */) {
boost::trim(body);
// release version
std::string version;
const auto first_nl_pos = body.find_first_of("\n\r");
if (first_nl_pos != std::string::npos)
version = body.substr(0,first_nl_pos);
else
version = body;
if (! Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
parse_version_string(body);
})
.perform_sync();
}
// Parses version string obtained in sync_version() and sends events to UI thread.
// Version string must contain release version on first line. Follows non-mandatory alpha / beta releases on following lines (alpha=2.0.0-alpha1).
void PresetUpdater::priv::parse_version_string(const std::string& body) const
{
// release version
std::string version;
const auto first_nl_pos = body.find_first_of("\n\r");
if (first_nl_pos != std::string::npos)
version = body.substr(0, first_nl_pos);
else
version = body;
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
// alpha / beta version
size_t nexn_nl_pos = first_nl_pos;
while (nexn_nl_pos != std::string::npos && body.size() > nexn_nl_pos + 1) {
const auto last_nl_pos = nexn_nl_pos;
nexn_nl_pos = body.find_first_of("\n\r", last_nl_pos + 1);
std::string line;
if (nexn_nl_pos == std::string::npos)
line = body.substr(last_nl_pos + 1);
else
line = body.substr(last_nl_pos + 1, nexn_nl_pos - last_nl_pos - 1);
// alpha
if (line.substr(0, 6) == "alpha=") {
version = line.substr(6);
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents for alpha release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version of alpha release: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_ALPHA_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
// alpha / beta version
size_t nexn_nl_pos = first_nl_pos;
while (nexn_nl_pos != std::string::npos && body.size() > nexn_nl_pos + 1) {
const auto last_nl_pos = nexn_nl_pos;
nexn_nl_pos = body.find_first_of("\n\r", last_nl_pos + 1);
std::string line;
if (nexn_nl_pos == std::string::npos)
line = body.substr(last_nl_pos + 1);
else
line = body.substr(last_nl_pos + 1, nexn_nl_pos - last_nl_pos - 1);
// alpha
if (line.substr(0, 6) == "alpha=") {
version = line.substr(6);
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents for alpha release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version of alpha release: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_ALPHA_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
// beta
} else if (line.substr(0, 5) == "beta=") {
version = line.substr(5);
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents for beta release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version of beta release: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_BETA_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
}
// beta
}
else if (line.substr(0, 5) == "beta=") {
version = line.substr(5);
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents for beta release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
})
.perform_sync();
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version of beta release: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_BETA_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
}
}
}
// Download vendor indices. Also download new bundles if an index indicates there's a new one available.

View File

@ -34,7 +34,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") {
}
}
WHEN("offset2_ex") {
ExPolygons result = Slic3r::offset2_ex(square_with_hole, 5.f, -2.f);
ExPolygons result = Slic3r::offset2_ex({ square_with_hole }, 5.f, -2.f);
THEN("offset matches") {
REQUIRE(result == ExPolygons { {
{ { 203, 203 }, { 97, 203 }, { 97, 97 }, { 203, 97 } },

View File

@ -49,7 +49,7 @@ offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, mit
Slic3r::ClipperLib::JoinType joinType
double miterLimit
CODE:
RETVAL = offset2_ex(polygons, delta1, delta2, joinType, miterLimit);
RETVAL = offset2_ex(union_ex(polygons), delta1, delta2, joinType, miterLimit);
OUTPUT:
RETVAL