mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-30 16:31:57 +08:00
Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_world_coordinates
This commit is contained in:
commit
84c0b816cb
@ -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();
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 } },
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user