From 7ff76d07684858fd937ef2f5d863f105a10f798e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 14 Oct 2021 09:11:19 +0200 Subject: [PATCH 01/42] New ClipperUtils functions: opening(), closing() as an alternative for offset2() with clear meaning. New ClipperUtils functions: expand(), shrink() as an alternative for offset() with clear meaning. All offset values for the new functions are positive. Various offsetting ClipperUtils (offset, offset2, offset2_ex) working over Polygons were marked as unsafe, sometimes producing invalid output if called for more than one polygon. These functions were reworked to offset polygons one by one. The new functions working over Polygons shall work the same way as the old safe ones working over ExPolygons, but working with Polygons shall be computationally more efficient. Improvements in FDM support generator: 1) For both grid and snug supports: Don't filter out supports for which the contacts are completely reduced by support / object XY separation. 2) Rounding / merging of supports using the closing radius parameter is now smoother, it does not produce sharp corners. 3) Snug supports: When calculating support interfaces, expand the projected support contact areas to produce wider, printable and more stable interfaces. 4) Don't reduce support interfaces for snug supports for steep overhangs, that would normally not need them. Snug supports often produce very narrow support interface regions and turning them off makes the support interfaces disappear. --- src/clipper/clipper.cpp | 30 +- src/clipper/clipper.hpp | 4 + src/libslic3r/Brim.cpp | 10 +- src/libslic3r/ClipperUtils.cpp | 363 +++++++++--------- src/libslic3r/ClipperUtils.hpp | 123 ++++-- src/libslic3r/Fill/Fill.cpp | 4 +- src/libslic3r/Fill/FillRectilinear.cpp | 8 +- .../GCode/AvoidCrossingPerimeters.cpp | 2 +- src/libslic3r/GCode/SeamPlacer.cpp | 2 +- src/libslic3r/MultiMaterialSegmentation.cpp | 10 +- src/libslic3r/PerimeterGenerator.cpp | 6 +- src/libslic3r/PrintObject.cpp | 24 +- src/libslic3r/PrintObjectSlice.cpp | 4 +- src/libslic3r/SLA/SupportPointGenerator.cpp | 4 +- src/libslic3r/SupportMaterial.cpp | 222 ++++++----- src/libslic3r/SupportMaterial.hpp | 12 + src/libslic3r/TriangleMeshSlicer.cpp | 2 +- tests/libslic3r/test_clipper_utils.cpp | 2 +- xs/xsp/Clipper.xsp | 2 +- 19 files changed, 480 insertions(+), 354 deletions(-) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index e46a0797c..84d68b1e6 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -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(); } } //------------------------------------------------------------------------------ diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 74e6601f9..b1dae3c24 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -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 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; diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index db31975e3..69a9e87f9 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -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); diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index e8f87a6e3..5e54da684 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -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,78 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree) } #endif -// Offset outside by 10um, one by one. -template -static ClipperLib::Paths safety_offset(PathsProvider &&paths) +template +TResult clipper_do( + const ClipperLib::ClipType clipType, + TSubj && subject, + TClip && clip, + const ClipperLib::PolyFillType fillType) +{ + ClipperLib::Clipper clipper; + clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, true); + clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); + TResult retval; + clipper.Execute(clipType, retval, fillType, fillType); + return retval; +} + +template +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(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : + clipper_do(clipType, std::forward(subject), std::forward(clip), fillType); +} + +template +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(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(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd)); +} + +// Offset CCW contours outside, CW contours (holes) inside. +// Don't calculate union of the output paths. +template +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 +238,71 @@ static ClipperLib::Paths safety_offset(PathsProvider &&paths) return out; } -// Only safe for a single path. -template -ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) +template +static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) { - // 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(input), joinType, endType); - ClipperLib::Paths retval; - co.Execute(retval, delta_scaled); - return retval; + assert(offset > 0); + return raw_offset(std::forward(paths), offset, joinType, miterLimit); } -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)); } +// Offset outside by 10um, one by one. +template +static ClipperLib::Paths safety_offset(PathsProvider &&paths) +{ + return raw_offset(std::forward(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit); +} + +template +static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(offset > 0); + return clipper_union(raw_offset(std::forward(paths), offset, joinType, miterLimit)); +} + +template static void remove_outermost_polygon(Container & solution); +template<> static void remove_outermost_polygon(ClipperLib::Paths &solution) + { if (! solution.empty()) solution.erase(solution.begin()); } +template<> static void remove_outermost_polygon(ClipperLib::PolyTree &solution) + { solution.RemoveOutermostPolygon(); } + +template +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(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 +static TResult offset_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(offset != 0); + return offset > 0 ? + expand_paths(std::forward(paths), offset, joinType, miterLimit) : + shrink_paths(std::forward(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(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(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(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(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 +353,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::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 +381,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 +390,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 -ClipperLib::Paths _offset(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) +static std::pair 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 +402,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 +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(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 +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(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(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(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } -template -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(subject), ClipperLib::ptSubject, true); - clipper.AddPaths(std::forward(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(expand_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(expand_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } -template -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(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : - _clipper_do(clipType, std::forward(subject), std::forward(clip), fillType); + assert(delta1 > 0); + assert(delta2 > 0); + return to_polygons(expand_paths(shrink_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } // Fix of #117: A large fractal pyramid takes ages to slice @@ -457,29 +482,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 -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(subject), ClipperLib::ptSubject, true); - clipper.AddPaths(std::forward(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(clipType, subject, clip, fillType); ! output.empty()) + // Perform an additional Union operation to generate the PolyTree ordering. + return clipper_union(output, fillType); + return ClipperLib::PolyTree(); } template -inline ClipperLib::PolyTree _clipper_do_polytree2( +inline ClipperLib::PolyTree clipper_do_polytree( const ClipperLib::ClipType clipType, PathProvider1 &&subject, PathProvider2 &&clip, @@ -488,14 +506,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(subject), safety_offset(std::forward(clip)), fillType) : - _clipper_do_polytree2(clipType, std::forward(subject), std::forward(clip), fillType); + clipper_do_polytree(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : + clipper_do_polytree(clipType, std::forward(subject), std::forward(clip), fillType); } template static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset) { - return to_polygons(_clipper_do(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); + return to_polygons(clipper_do(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); } Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) @@ -529,7 +547,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons template 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(subject), std::forward(clip), fill_type, do_safety_offset)); } + { return PolyTreeToExPolygons(clipper_do_polytree(clipType, std::forward(subject), std::forward(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 +596,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 Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip) @@ -692,14 +710,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::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); + return clipper_do(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); } - ClipperLib::PolyTree union_pt(const ExPolygons &subject) { - return _clipper_do(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); + return clipper_do(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftEvenOdd); } // Simple spatial ordering of Polynodes @@ -730,7 +749,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 +759,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; } diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index f49d922c1..829611176 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -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 + class PathsProvider { + public: + PathsProvider(const std::vector &paths) : m_paths(paths) {} + + struct iterator : public PathsProviderIteratorBase { + public: + explicit iterator(typename std::vector::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::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 &m_paths; + }; + template 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); diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 7ba6de7d4..117066690 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -252,11 +252,11 @@ std::vector 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; diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index baf57f426..fc0c1b30c 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -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); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 49de854f2..ef3e18825 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -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(); diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 8cc22647f..878a9ef58 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -212,7 +212,7 @@ void SeamPlacer::init(const Print& print) std::vector 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 }; diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index a91d43e78..b17ee000a 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1400,7 +1400,7 @@ static inline std::vector> mmu_segmentation_top_and_bott if (std::vector &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; @@ -1408,8 +1408,7 @@ static inline std::vector> 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)); @@ -1419,7 +1418,7 @@ static inline std::vector> mmu_segmentation_top_and_bott if (std::vector &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; @@ -1427,8 +1426,7 @@ static inline std::vector> 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)); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 3190845bd..55d3c6aa5 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -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) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f49eddf3e..235013143 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -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 diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 7db0de626..e2844a624 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -393,7 +393,7 @@ static std::vector> 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); } } diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index bbc6b03fa..c32da0431 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -186,8 +186,8 @@ static std::vector 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); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 9a5638c01..167aedcc2 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -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. @@ -883,7 +906,14 @@ public: // Merge the support polygons by applying morphological closing and inwards smoothing. auto closing_distance = scaled(m_support_material_closing_radius); auto smoothing_distance = scaled(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(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 +1280,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 +1444,7 @@ static inline std::tuple detect_overhangs( overhang_polygons = to_polygons(layer.lslices); #endif // Expand for better stability. - contact_polygons = offset(overhang_polygons, scaled(object_config.raft_expansion.value)); + contact_polygons = expand(overhang_polygons, scaled(object_config.raft_expansion.value)); } else if (! layer.regions().empty()) { @@ -1475,20 +1505,20 @@ static inline std::tuple 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 +1530,7 @@ static inline std::tuple 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 +1546,7 @@ static inline std::tuple 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 +1618,7 @@ static inline std::tuple 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 +1750,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 +1763,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 +1771,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 +1829,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 +1967,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 +2071,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 +2110,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 +2278,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); } @@ -2736,7 +2765,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 +2896,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 +2928,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 +2959,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 +2993,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 +3056,44 @@ std::pairsupport_material_style.value == smsSnug; + 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(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(0, int(intermediate_layers.size())), [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, @@ -3196,7 +3242,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 +3254,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 eec; if (no_sort) { @@ -3461,10 +3507,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 +3522,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 +3862,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 angles; - angles.push_back(base_angle); - - if (support_pattern == smpRectilinearGrid) - angles.push_back(interface_angle); + std::vector 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 +3876,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 +3902,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(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& range) { for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { @@ -3883,8 +3911,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( assert(support_layer.support_fills.entities.empty()); MyLayer &raft_layer = *raft_layers[support_layer_id]; - std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(ipRectilinear)); - std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); + std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.interface_fill_pattern)); + std::unique_ptr filler_support = std::unique_ptr(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 +3928,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 +3957,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 +3998,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( }; std::vector 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(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& 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 +4009,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::new_from_type(fill_type_interface)); + auto filler_interface = std::unique_ptr(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(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(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(base_interface_layers.empty() ? nullptr : Fill::new_from_type(ipRectilinear)); - auto filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); + auto filler_base_interface = std::unique_ptr(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::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); @@ -4084,8 +4106,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; + 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 +4128,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; 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 +4138,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 +4152,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). diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index a1bd81297..65604ef72 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -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 diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index d3c9a49b5..0f3d0f5b6 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -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; diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index 3cbd21a40..1b2b45eca 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -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 } }, diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index bae6a103e..eae3afeff 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -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 From 022253327389a343abd029499b6040696f615e1d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 14 Oct 2021 09:31:53 +0200 Subject: [PATCH 02/42] Fixed compilation on non MS systems --- src/libslic3r/ClipperUtils.cpp | 79 +++++++++++++++++----------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 5e54da684..ebf13bedf 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -156,6 +156,45 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree) } #endif +// Offset CCW contours outside, CW contours (holes) inside. +// Don't calculate union of the output paths. +template +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(); + // 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, 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) + std::reverse(path.begin(), path.end()); + } + append(out, std::move(out_this)); + } + return out; +} + +// Offset outside by 10um, one by one. +template +static ClipperLib::Paths safety_offset(PathsProvider &&paths) +{ + return raw_offset(std::forward(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit); +} + template TResult clipper_do( const ClipperLib::ClipType clipType, @@ -206,38 +245,6 @@ ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool return PolyTreeToExPolygons(clipper_union(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd)); } -// Offset CCW contours outside, CW contours (holes) inside. -// Don't calculate union of the output paths. -template -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(); - // 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, 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) - std::reverse(path.begin(), path.end()); - } - append(out, std::move(out_this)); - } - return out; -} - template static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) { @@ -245,13 +252,6 @@ static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset return raw_offset(std::forward(paths), offset, joinType, miterLimit); } -// Offset outside by 10um, one by one. -template -static ClipperLib::Paths safety_offset(PathsProvider &&paths) -{ - return raw_offset(std::forward(paths), ClipperSafetyOffset, DefaultJoinType, DefaultMiterLimit); -} - template static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) { @@ -259,6 +259,7 @@ static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::Joi return clipper_union(raw_offset(std::forward(paths), offset, joinType, miterLimit)); } +// used by shrink_paths() template static void remove_outermost_polygon(Container & solution); template<> static void remove_outermost_polygon(ClipperLib::Paths &solution) { if (! solution.empty()) solution.erase(solution.begin()); } From 64a3866ad59464f3edb1746048c78c4c00a71cc3 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 14 Oct 2021 09:33:39 +0200 Subject: [PATCH 03/42] Making GCC happy. --- src/libslic3r/ClipperUtils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index ebf13bedf..9b95bfed6 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -261,9 +261,9 @@ static TResult expand_paths(PathsProvider &&paths, float offset, ClipperLib::Joi // used by shrink_paths() template static void remove_outermost_polygon(Container & solution); -template<> static void remove_outermost_polygon(ClipperLib::Paths &solution) +template<> void remove_outermost_polygon(ClipperLib::Paths &solution) { if (! solution.empty()) solution.erase(solution.begin()); } -template<> static void remove_outermost_polygon(ClipperLib::PolyTree &solution) +template<> void remove_outermost_polygon(ClipperLib::PolyTree &solution) { solution.RemoveOutermostPolygon(); } template From aecd7e2b932d2e3550d7f5e65aba0c3a0d43eb6d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 14 Oct 2021 12:13:21 +0200 Subject: [PATCH 04/42] GalleryDialog: Added context menu for custom shapes + Added possibility to delete custom shaped by "DELETE" key --- src/slic3r/GUI/GalleryDialog.cpp | 60 +++++++++++++++++++++++--------- src/slic3r/GUI/GalleryDialog.hpp | 11 ++++-- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index fef131b88..f5e3083a7 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -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(); diff --git a/src/slic3r/GUI/GalleryDialog.hpp b/src/slic3r/GUI/GalleryDialog.hpp index 8cf3f0096..2aa04ffa2 100644 --- a/src/slic3r/GUI/GalleryDialog.hpp +++ b/src/slic3r/GUI/GalleryDialog.hpp @@ -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(); From ffc29a253671e203f64c6f9ea754da3f9aa22850 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 13 Oct 2021 16:45:05 +0200 Subject: [PATCH 05/42] Preferences Highlighter --- src/slic3r/GUI/GUI_App.cpp | 4 +- src/slic3r/GUI/GUI_App.hpp | 2 +- src/slic3r/GUI/HintNotification.cpp | 2 +- src/slic3r/GUI/OG_CustomCtrl.cpp | 2 + src/slic3r/GUI/Preferences.cpp | 69 ++++++++++++++++++++++++++++- src/slic3r/GUI/Preferences.hpp | 19 +++++++- 6 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a19858eea..a98998a84 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2020,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 diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 3061bbe13..d350da969 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -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. diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index fc410fce2..21aecd15b 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -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); diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 9c31f679b..18553b9cf 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -597,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); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index ae52b52a9..ee80131e0 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -43,7 +43,7 @@ namespace Slic3r { 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) { @@ -51,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_ptrcreate_options_tab(const wxString& title, wxBookCtrlBase* tabs) @@ -755,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 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 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 diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 7f708ff9c..fdfa39455 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -20,6 +20,7 @@ namespace Slic3r { namespace GUI { class ConfigOptionsGroup; +class OG_CustomCtrl; class PreferencesDialog : public DPIDialog { @@ -39,7 +40,7 @@ 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; } @@ -55,6 +56,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); + 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 From a6f4b9b71f4b258848500e1c6b558fe27dcc212b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 14 Oct 2021 13:55:52 +0200 Subject: [PATCH 06/42] Snug supports: Extrude interface layers 45 degrees from the base layer, flip direction of the infill layer with each interface layer. Unfortunately the flipping of support interface directions may not work reliably due to base support layer heights growing at different rate from the interface layers. --- src/libslic3r/Layer.hpp | 10 ++++- src/libslic3r/Print.cpp | 2 +- src/libslic3r/Print.hpp | 4 +- src/libslic3r/PrintObject.cpp | 8 ++-- src/libslic3r/SupportMaterial.cpp | 62 ++++++++++++++++++++++++------- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index e6bf4b4fc..60440de97 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -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 diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b33e8bda2..03a3d1e57 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -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; } } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 0056aee33..ac986216e 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -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. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 235013143..dd5a2b573 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -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(). diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 167aedcc2..1322b4071 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -420,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 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"; @@ -569,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. @@ -580,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; } @@ -2507,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); @@ -2531,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); @@ -3056,7 +3090,6 @@ std::pairsupport_material_style.value == smsSnug; 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(m_object_config->support_material_closing_radius.value); @@ -4027,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; @@ -4106,7 +4142,7 @@ 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. - m_support_params.interface_angle; + 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)); @@ -4128,7 +4164,7 @@ 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 = m_support_params.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 / m_support_params.interface_density)); fill_expolygons_generate_paths( From 43d952779ab99be6b4cd6d935b89378d0a832896 Mon Sep 17 00:00:00 2001 From: Jurriaan Pruis Date: Fri, 15 Oct 2021 10:44:19 +0200 Subject: [PATCH 07/42] Fix missing `wx/timer.h` header compile issue Tried to compile the latest git version on my system and got an error that wxTimer could not be found. Fixed it by adding in the header. cc @kocikdav --- src/slic3r/GUI/Preferences.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index fdfa39455..01f116760 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -5,6 +5,7 @@ #include "GUI_Utils.hpp" #include +#include #include class wxColourPickerCtrl; From 1ca24f0bd03d7f97d576bfac43022733459a9c92 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 15 Oct 2021 14:31:41 +0200 Subject: [PATCH 08/42] Fixed visualization of G-code in G-code viewer after 07e7e115901c80f282b06ea6b86bc56b28e1a02b Fix of prusa-gcodeviewer changes modification time of the viewed gcode file #7005 --- src/libslic3r/GCode/GCodeProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index c3489a621..56bb7fc7c 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1251,7 +1251,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::functionprocess_gcode_line(line, true); - }); + }, m_result.lines_ends); // Don't post-process the G-code to update time stamps. this->finalize(false); From 81cb190e2f87cfe3164e5c5237d6597b7581a17b Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 15 Oct 2021 14:54:43 +0200 Subject: [PATCH 09/42] Export ongoing notification with delay 1000ms to prevent quick opening and closing on fast systems --- src/slic3r/GUI/NotificationManager.cpp | 17 +++++++++++++++-- src/slic3r/GUI/NotificationManager.hpp | 16 ++++++++++------ src/slic3r/GUI/Plater.cpp | 5 +++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index b12256cf0..2a27ce74a 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -60,6 +60,7 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat _u8L("Undo desktop integration was successful.") }, {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, _u8L("Undo desktop integration failed.") }, + {NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") }, //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, //{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") }, //{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification @@ -1151,6 +1152,8 @@ bool NotificationManager::SlicingProgressNotification::set_progress_state(Notifi m_sp_state = state; return true; case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED: + if (m_sp_state != SlicingProgressState::SP_BEGAN && m_sp_state != SlicingProgressState::SP_PROGRESS) + return false; set_percentage(1); m_has_cancel_button = false; m_has_print_info = false; @@ -1508,6 +1511,16 @@ void NotificationManager::push_notification(NotificationType type, int duration = get_standart_duration(level); push_notification_data({ type, level, duration, text, hypertext, callback }, timestamp); } + +void NotificationManager::push_delayed_notification(const NotificationType type, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) +{ + auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications), + boost::bind(&NotificationData::type, boost::placeholders::_1) == type); + assert(it != std::end(basic_notifications)); + if (it != std::end(basic_notifications)) + push_delayed_notification_data(std::make_unique(*it, m_id_provider, m_evt_handler), condition_callback, initial_delay, delay_interval); +} + void NotificationManager::push_validate_error_notification(const std::string& text) { push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); @@ -1911,7 +1924,7 @@ void NotificationManager::push_hint_notification(bool open_next) auto condition = [&self = std::as_const(*this)]() { return self.get_notification_count() == 0; }; - push_delayed_notification(std::make_unique(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000); + push_delayed_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000); } } @@ -1974,7 +1987,7 @@ bool NotificationManager::push_notification_data(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) +void NotificationManager::push_delayed_notification_data(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) { if (initial_delay == 0 && condition_callback()) { if( push_notification_data(std::move(notification), 0)) diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 00065f795..2031586b8 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -109,8 +109,10 @@ enum class NotificationType // Give user advice to simplify object with big amount of triangles // Contains ObjectID for closing when object is deleted SimplifySuggestion, - // information about netfabb is finished repairing model (blocking proccess) - NetfabbFinished + // information about netfabb is finished repairing model (blocking proccess) + NetfabbFinished, + // Short meesage to fill space between start and finish of export + ExportOngoing }; class NotificationManager @@ -151,6 +153,10 @@ public: // ErrorNotificationLevel and ImportantNotificationLevel are never faded out. void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "", std::function callback = std::function(), int timestamp = 0); + // Pushes basic_notification with delay. See push_delayed_notification_data. + void push_delayed_notification(const NotificationType type, std::function condition_callback, int64_t initial_delay, int64_t delay_interval); + // Removes all notifications of type from m_waiting_notifications + void stop_delayed_notifications_of_type(const NotificationType type); // Creates Validate Error notification with a custom text and no fade out. void push_validate_error_notification(const std::string& text); // Creates Slicing Error notification with a custom text and no fade out. @@ -699,10 +705,8 @@ private: // and condition callback is success, notification is regular pushed from update function. // Otherwise another delay interval waiting. Timestamp is 0. // Note that notification object is constructed when being added to the waiting list, but there are no updates called on it and its timer is reset at regular push. - // Also note that no control of same notification is done during push_delayed_notification but if waiting notif fails to push, it continues waiting. - void push_delayed_notification(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval); - // Removes all notifications of type from m_waiting_notifications - void stop_delayed_notifications_of_type(const NotificationType type); + // Also note that no control of same notification is done during push_delayed_notification_data but if waiting notif fails to push, it continues waiting. + void push_delayed_notification_data(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval); //finds older notification of same type and moves it to the end of queue. returns true if found bool activate_existing(const NotificationManager::PopNotification* notification); // Put the more important notifications to the bottom of the list. diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0710c0916..ba0f60839 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4025,6 +4025,7 @@ void Plater::priv::on_export_began(wxCommandEvent& evt) { if (show_warning_dialog) warnings_dialog(); + notification_manager->push_delayed_notification(NotificationType::ExportOngoing, [](){return true;}, 1000, 1000); } void Plater::priv::on_slicing_began() { @@ -4157,6 +4158,10 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) if(wxGetApp().get_mode() == comSimple) { show_action_buttons(false); } + if (exporting_status != ExportingStatus::NOT_EXPORTING && !has_error) { + notification_manager->stop_delayed_notifications_of_type(NotificationType::ExportOngoing); + notification_manager->close_notification_of_type(NotificationType::ExportOngoing); + } // If writing to removable drive was scheduled, show notification with eject button if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) { show_action_buttons(false); From 5e3da340aeac014d1bea6634d74e45e5fc76b639 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 15 Oct 2021 15:32:14 +0200 Subject: [PATCH 10/42] Fix crash with some models after hole drilling --- src/libslic3r/MeshBoolean.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 25250e234..e2e5e254a 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -159,7 +159,7 @@ template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) int i = 0; Vec3i facet; for (auto v : vtc) { - if (i > 2) { i = 0; break; } + if (i > 2 || v < 0 || v >= cgalmesh.vertices().size()) { i = 0; break; } facet(i++) = v; } From bec140b4bc3ee0be7ba38309b30f137c4f257a84 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 15 Oct 2021 16:35:12 +0200 Subject: [PATCH 11/42] "only_retract_when_crossing_perimeters" disabled by default to reduce stringing for "custom" printers based on "defaults". --- src/libslic3r/PrintConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 2917a9a19..b34a6cb58 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1853,7 +1853,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Disables retraction when the travel path does not exceed the upper layer's perimeters " "(and thus any ooze will be probably invisible)."); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("ooze_prevention", coBool); def->label = L("Enable"); From 6f6f6de506a23a8c83e1b27fe322064d499aa7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 13 Oct 2021 02:27:09 +0200 Subject: [PATCH 12/42] Added an option to limit painting to triangles only highlighted by "Highlight by angle" in the support painting gizmo. --- src/libslic3r/TriangleSelector.cpp | 30 ++++++---- src/libslic3r/TriangleSelector.hpp | 14 +++-- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 29 ++++++---- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 1 - .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 56 ++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 3 + 7 files changed, 93 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 987ef1c0a..f65292f03 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -127,7 +127,8 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& source, float radius, CursorType cursor_type, EnforcerBlockerType new_state, - const Transform3d& trafo, bool triangle_splitting) + const Transform3d& trafo, const Transform3d& trafo_no_translate, + bool triangle_splitting, float highlight_by_angle_deg) { assert(facet_start < m_orig_size_indices); @@ -143,6 +144,9 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, m_old_cursor_radius_sqr = m_cursor.radius_sqr; } + const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); + Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); + // Now start with the facet the pointer points to and check all adjacent facets. std::vector facets_to_check; facets_to_check.reserve(16); @@ -153,14 +157,14 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // Head of the bread-first facets_to_check FIFO. int facet_idx = 0; while (facet_idx < int(facets_to_check.size())) { - int facet = facets_to_check[facet_idx]; - if (! visited[facet]) { + int facet = facets_to_check[facet_idx]; + const Vec3f &facet_normal = m_face_normals[m_triangles[facet].source_triangle]; + if (!visited[facet] && (highlight_by_angle_deg == 0.f || vec_down.dot(facet_normal) >= highlight_angle_limit)) { if (select_triangle(facet, new_state, triangle_splitting)) { - // add neighboring facets to list to be proccessed later - for (int neighbor_idx : m_neighbors[facet]) { - if (neighbor_idx >=0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx))) + // add neighboring facets to list to be processed later + for (int neighbor_idx : m_neighbors[facet]) + if (neighbor_idx >= 0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx))) facets_to_check.push_back(neighbor_idx); - } } } visited[facet] = true; @@ -168,7 +172,10 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, } } -void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle, bool force_reselection) +void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, + const Transform3d& trafo_no_translate, + float seed_fill_angle, float highlight_by_angle_deg, + bool force_reselection) { assert(facet_start < m_orig_size_indices); @@ -182,14 +189,17 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st std::queue facet_queue; facet_queue.push(facet_start); - const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; + const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; + const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); + Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); // Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor. while (!facet_queue.empty()) { int current_facet = facet_queue.front(); facet_queue.pop(); - if (!visited[current_facet]) { + const Vec3f &facet_normal = m_face_normals[m_triangles[current_facet].source_triangle]; + if (!visited[current_facet] && (highlight_by_angle_deg == 0.f || vec_down.dot(facet_normal) >= highlight_angle_limit)) { if (m_triangles[current_facet].is_split()) { for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) { assert(split_triangle_idx < int(m_triangles[current_facet].children.size())); diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 05a78e2ee..b3c468a6e 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -45,12 +45,16 @@ public: CursorType type, // current type of cursor EnforcerBlockerType new_state, // enforcer or blocker? const Transform3d &trafo, // matrix to get from mesh to world - bool triangle_splitting); // If triangles will be split base on the cursor or not + const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation + bool triangle_splitting, // If triangles will be split base on the cursor or not + float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. - void seed_fill_select_triangles(const Vec3f &hit, // point where to start - int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to - float seed_fill_angle, // the maximal angle between two facets to be painted by the same color - bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle + void seed_fill_select_triangles(const Vec3f &hit, // point where to start + int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to + const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation + float seed_fill_angle, // the maximal angle between two facets to be painted by the same color + float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. + bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle void bucket_fill_select_triangles(const Vec3f &hit, // point where to start int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 12b827e64..36796032a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -20,7 +20,7 @@ namespace Slic3r::GUI { void GLGizmoFdmSupports::on_shutdown() { - m_angle_threshold_deg = 0.f; + m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); m_parent.toggle_model_objects_visibility(true); } @@ -62,6 +62,9 @@ bool GLGizmoFdmSupports::on_init() m_desc["smart_fill_angle"] = _L("Smart fill angle"); + m_desc["split_triangles"] = _L("Split triangles"); + m_desc["on_overhangs_only"] = _L("On overhangs only"); + return true; } @@ -116,6 +119,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f); + float caption_max = 0.f; float total_text_max = 0.f; for (const auto &t : std::array{"enforce", "block", "remove"}) { @@ -129,6 +135,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l float window_width = minimal_slider_width + sliders_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); + window_width = std::max(window_width, split_triangles_checkbox_width); + window_width = std::max(window_width, on_overhangs_only_checkbox_width); window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); @@ -152,25 +160,25 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_width); - if (m_imgui->slider_float("##angle_threshold_deg", &m_angle_threshold_deg, 0.f, 90.f, format_str.data())) { - m_parent.set_slope_normal_angle(90.f - m_angle_threshold_deg); + if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data())) { + m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg); if (! m_parent.is_using_slope()) { m_parent.use_slope(true); m_parent.set_as_dirty(); } } - m_imgui->disabled_begin(m_angle_threshold_deg == 0.f); + m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); ImGui::NewLine(); ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { - select_facets_by_angle(m_angle_threshold_deg, false); - m_angle_threshold_deg = 0.f; + select_facets_by_angle(m_highlight_by_angle_threshold_deg, false); + m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); } ImGui::SameLine(window_width - buttons_width); if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) { - m_angle_threshold_deg = 0.f; + m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); } m_imgui->disabled_end(); @@ -209,6 +217,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::EndTooltip(); } + m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); + ImGui::Separator(); if (m_tool_type == ToolType::BRUSH) { @@ -272,7 +282,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::EndTooltip(); } - m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled); + m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); @@ -287,8 +297,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l assert(m_tool_type == ToolType::SMART_FILL); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["smart_fill_angle"] + ":"); - std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo," - "placed after the number with no whitespace in between."); + ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_width); if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data())) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 0fb03140a..4929714a2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -35,7 +35,6 @@ private: PainterGizmoType get_painter_type() const override; void select_facets_by_angle(float threshold, bool block); - float m_angle_threshold_deg = 0.f; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index b472fbc1b..f6229d7c9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -129,6 +129,7 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["tool_bucket_fill"] = _L("Bucket fill"); m_desc["smart_fill_angle"] = _L("Smart fill angle"); + m_desc["split_triangles"] = _L("Split triangles"); init_extruders_data(); @@ -261,6 +262,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + float caption_max = 0.f; float total_text_max = 0.f; for (const auto &t : std::array{"first_color", "second_color", "remove"}) { @@ -274,6 +277,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott float window_width = minimal_slider_width + sliders_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); + window_width = std::max(window_width, split_triangles_checkbox_width); window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); @@ -438,7 +442,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::EndTooltip(); } - m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled); + m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index fdb05ae22..27b68bc93 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -253,7 +253,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); m_parent.set_as_dirty(); if (m_rr.mesh_id != -1) { - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle, true); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; } @@ -284,11 +289,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type(); } - const Camera &camera = wxGetApp().plater()->get_camera(); - const Selection &selection = m_parent.get_selection(); - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d &instance_trafo = mi->get_transformation().get_matrix(); + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); // List of mouse positions that will be used as seeds for painting. std::vector mouse_positions{mouse_position}; @@ -314,10 +320,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Precalculate transformations of individual meshes. std::vector trafo_matrices; - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part()) + std::vector trafo_matrices_not_translate; + for (const ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); - } + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); + } // Now "click" into all the prepared points and spill paint around them. for (const Vec2d& mp : mouse_positions) { @@ -339,7 +347,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return dragging_while_painting; } - const Transform3d& trafo_matrix = trafo_matrices[m_rr.mesh_id]; + const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id]; + const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id]; // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); @@ -348,7 +357,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state); if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle, true); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false, true); else if (m_tool_type == ToolType::BUCKET_FILL) @@ -357,7 +367,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_seed_fill_last_mesh_id = -1; } else if (m_tool_type == ToolType::BRUSH) m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type, - new_state, trafo_matrix, m_triangle_splitting_enabled); + new_state, trafo_matrix, trafo_matrix_not_translate, m_triangle_splitting_enabled, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_last_mouse_click = mouse_position; @@ -370,17 +381,21 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_triangle_selectors.empty()) return false; - const Camera & camera = wxGetApp().plater()->get_camera(); - const Selection & selection = m_parent.get_selection(); - const ModelObject * mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d & instance_trafo = mi->get_transformation().get_matrix(); + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); // Precalculate transformations of individual meshes. std::vector trafo_matrices; + std::vector trafo_matrices_not_translate; for (const ModelVolume *mv : mo->volumes) - if (mv->is_model_part()) + if (mv->is_model_part()) { trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); + } // Now "click" into all the prepared points and spill paint around them. update_raycast_cache(mouse_position, camera, trafo_matrices); @@ -405,9 +420,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if(m_rr.mesh_id != m_seed_fill_last_mesh_id) seed_fill_unselect_all(); + const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id]; + assert(m_rr.mesh_id < int(m_triangle_selectors.size())); if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false); else if (m_tool_type == ToolType::BUCKET_FILL) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index cc15af41f..cf12e9359 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -159,6 +159,9 @@ protected: ToolType m_tool_type = ToolType::BRUSH; float m_smart_fill_angle = 30.f; + bool m_paint_on_overhangs_only = false; + float m_highlight_by_angle_threshold_deg = 0.f; + static constexpr float SmartFillAngleMin = 0.0f; static constexpr float SmartFillAngleMax = 90.f; static constexpr float SmartFillAngleStep = 1.f; From b2fc50c9d9dd13879b2de58e4bb191b1bdc406e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 18 Oct 2021 10:56:50 +0200 Subject: [PATCH 13/42] Small refactoring of showing tooltips in gizmos. --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 81 ++++------------- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 30 ++----- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 90 +++++-------------- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 36 ++------ src/slic3r/GUI/ImGuiWrapper.cpp | 18 ++++ src/slic3r/GUI/ImGuiWrapper.hpp | 2 + 6 files changed, 75 insertions(+), 182 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 36796032a..bc32d5f31 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -196,26 +196,16 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) m_tool_type = ToolType::BRUSH; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); ImGui::SameLine(tool_type_offset + tool_type_radio_brush); ImGui::PushItemWidth(tool_type_radio_smart_fill); if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) m_tool_type = ToolType::SMART_FILL; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); @@ -231,13 +221,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) m_cursor_type = TriangleSelector::CursorType::SPHERE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::PushItemWidth(cursor_type_radio_circle); @@ -245,13 +230,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) m_cursor_type = TriangleSelector::CursorType::CIRCLE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); ImGui::PushItemWidth(cursor_type_radio_pointer); @@ -259,13 +239,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) m_cursor_type = TriangleSelector::CursorType::POINTER; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); @@ -274,23 +249,13 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width); m_imgui->disabled_end(); } else { @@ -306,13 +271,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l triangle_selector->request_update_render_data(); } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); } ImGui::Separator(); @@ -334,13 +294,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 17630e5c6..f7feed44a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -547,13 +547,9 @@ RENDER_AGAIN: ImGui::SameLine(settings_sliders_left); ImGui::PushItemWidth(window_width - settings_sliders_left); m_imgui->slider_float(" ", &offset, offset_min, offset_max, "%.1f mm"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted((_utf8(opts[0].second->tooltip)).c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip((_utf8(opts[0].second->tooltip)).c_str(), max_tooltip_width); + bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider @@ -563,13 +559,9 @@ RENDER_AGAIN: m_imgui->text(m_desc.at("quality")); ImGui::SameLine(settings_sliders_left); m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted((_utf8(opts[1].second->tooltip)).c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip((_utf8(opts[1].second->tooltip)).c_str(), max_tooltip_width); + slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); @@ -580,13 +572,9 @@ RENDER_AGAIN: m_imgui->text(m_desc.at("closing_distance")); ImGui::SameLine(settings_sliders_left); m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted((_utf8(opts[2].second->tooltip)).c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip((_utf8(opts[2].second->tooltip)).c_str(), max_tooltip_width); + slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index f6229d7c9..e3344ad38 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -335,13 +335,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); ImGui::SameLine(tool_type_offset + tool_type_radio_brush); ImGui::PushItemWidth(tool_type_radio_smart_fill); @@ -353,13 +348,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill); ImGui::PushItemWidth(tool_type_radio_bucket_fill); @@ -371,13 +361,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets that have the same color."), max_tooltip_width); ImGui::Separator(); @@ -391,13 +376,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) m_cursor_type = TriangleSelector::CursorType::SPHERE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::PushItemWidth(cursor_type_radio_circle); @@ -405,13 +385,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) m_cursor_type = TriangleSelector::CursorType::CIRCLE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); ImGui::PushItemWidth(cursor_type_radio_pointer); @@ -419,13 +394,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) m_cursor_type = TriangleSelector::CursorType::POINTER; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); @@ -434,23 +404,13 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width); m_imgui->disabled_end(); @@ -468,13 +428,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott triangle_selector->request_update_render_data(); } - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); ImGui::Separator(); } @@ -494,13 +449,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 2f9d16f90..6971e7cdb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -129,13 +129,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_type")); @@ -146,26 +141,16 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) m_cursor_type = TriangleSelector::CursorType::SPHERE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::PushItemWidth(cursor_type_radio_circle); if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) m_cursor_type = TriangleSelector::CursorType::CIRCLE; - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { @@ -186,13 +171,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width); ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 03e5bdc09..a01619d64 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -425,6 +425,24 @@ void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label) this->text_colored(color, label_utf8.c_str()); } +void ImGuiWrapper::tooltip(const char *label, float wrap_width) +{ + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(wrap_width); + ImGui::TextUnformatted(label); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); +} + +void ImGuiWrapper::tooltip(const wxString &label, float wrap_width) +{ + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(wrap_width); + ImGui::TextUnformatted(label.ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); +} + bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/) { bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 441d26ccc..dc7d9692a 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -79,6 +79,8 @@ public: void text_colored(const ImVec4& color, const char* label); void text_colored(const ImVec4& color, const std::string& label); void text_colored(const ImVec4& color, const wxString& label); + void tooltip(const char *label, float wrap_width); + void tooltip(const wxString &label, float wrap_width); // Float sliders: Manually inserted values aren't clamped by ImGui.Using this wrapper function does (when clamp==true). bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true); From 6f3baf92627a4908c1dff0870e72b64ce0d0f076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 18 Oct 2021 10:57:25 +0200 Subject: [PATCH 14/42] Added tooltips to the support painting gizmo. Used multi-line text for the label "Highlight overhang by angle". --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 57 +++++++++++++------- src/slic3r/GUI/ImGuiWrapper.cpp | 29 +++++++++- src/slic3r/GUI/ImGuiWrapper.hpp | 7 ++- 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index bc32d5f31..a0ccab6c5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -8,6 +8,7 @@ #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/format.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -52,7 +53,7 @@ bool GLGizmoFdmSupports::on_init() m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); m_desc["pointer"] = _L("Triangles"); - m_desc["highlight_by_angle"] = _L("Highlight by angle"); + m_desc["highlight_by_angle"] = _L("Highlight overhang by angle"); m_desc["enforce_button"] = _L("Enforce"); m_desc["cancel"] = _L("Cancel"); @@ -92,18 +93,19 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(20.5f); + const float approx_height = m_imgui->scaled(22.f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.f); - const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float autoset_slider_label_max_width = m_imgui->scaled(7.5f); + const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); @@ -131,8 +133,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l total_text_max += caption_max + m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f); - float sliders_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); - float window_width = minimal_slider_width + sliders_width; + float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); + float window_width = minimal_slider_width + sliders_left_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); window_width = std::max(window_width, split_triangles_checkbox_width); @@ -152,14 +154,22 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); + float position_before_text_y = ImGui::GetCursorPos().y; ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["highlight_by_angle"] + ":"); + m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); ImGui::AlignTextToFramePadding(); + float position_after_text_y = ImGui::GetCursorPos().y; std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in FDM supports gizmo," "placed after the number with no whitespace in between."); - ImGui::SameLine(sliders_width); - ImGui::PushItemWidth(window_width - sliders_width); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); + + float slider_height = m_imgui->get_slider_float_height(); + // Makes slider to be aligned to bottom of the multi-line text. + float slider_start_position = std::max(position_before_text_y, position_after_text_y - slider_height); + ImGui::SetCursorPosY(slider_start_position); + if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data())) { m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg); if (! m_parent.is_using_slope()) { @@ -168,6 +178,14 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } } + // Restores the cursor position to be below the multi-line text. + ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y)); + + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " + "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]), max_tooltip_width); + m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); ImGui::NewLine(); ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); @@ -183,7 +201,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } m_imgui->disabled_end(); - const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; ImGui::Separator(); @@ -208,6 +225,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); ImGui::Separator(); @@ -246,8 +265,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(sliders_width); - ImGui::PushItemWidth(window_width - sliders_width); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); if (ImGui::IsItemHovered()) m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width); @@ -263,8 +282,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["smart_fill_angle"] + ":"); - ImGui::SameLine(sliders_width); - ImGui::PushItemWidth(window_width - sliders_width); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data())) for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); @@ -288,8 +307,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } } - ImGui::SameLine(sliders_width); - ImGui::PushItemWidth(window_width - sliders_width); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width); auto clp_dist = float(m_c->object_clipper()->get_position()); if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index a01619d64..fe6c18db9 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -280,10 +280,10 @@ void ImGuiWrapper::render() m_new_frame_open = false; } -ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) +ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width) { auto text_utf8 = into_u8(text); - ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str()); + ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width); /*#ifdef __linux__ size.x *= m_style_scaling; @@ -293,6 +293,13 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) return size; } +float ImGuiWrapper::get_slider_float_height() const +{ + const ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + return g.FontSize + style.FramePadding.y * 2.0f + style.ItemSpacing.y; +} + void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y) { ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y)); @@ -425,6 +432,24 @@ void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label) this->text_colored(color, label_utf8.c_str()); } +void ImGuiWrapper::text_wrapped(const char *label, float wrap_width) +{ + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + this->text(label); + ImGui::PopTextWrapPos(); +} + +void ImGuiWrapper::text_wrapped(const std::string &label, float wrap_width) +{ + this->text_wrapped(label.c_str(), wrap_width); +} + +void ImGuiWrapper::text_wrapped(const wxString &label, float wrap_width) +{ + auto label_utf8 = into_u8(label); + this->text_wrapped(label_utf8.c_str(), wrap_width); +} + void ImGuiWrapper::tooltip(const char *label, float wrap_width) { ImGui::BeginTooltip(); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index dc7d9692a..3712ff6a8 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -53,7 +53,9 @@ public: float scaled(float x) const { return x * m_font_size; } ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); } - ImVec2 calc_text_size(const wxString &text); + ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f); + + float get_slider_float_height() const; void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); @@ -79,6 +81,9 @@ public: void text_colored(const ImVec4& color, const char* label); void text_colored(const ImVec4& color, const std::string& label); void text_colored(const ImVec4& color, const wxString& label); + void text_wrapped(const char *label, float wrap_width); + void text_wrapped(const std::string &label, float wrap_width); + void text_wrapped(const wxString &label, float wrap_width); void tooltip(const char *label, float wrap_width); void tooltip(const wxString &label, float wrap_width); From 912f73d79c5f6ff087070a1445338e4d1157fea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 18 Oct 2021 11:33:47 +0200 Subject: [PATCH 15/42] Fixed the positioning of the supports painting gizmo. --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index a0ccab6c5..16856a9e3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -93,7 +93,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(22.f); + const float approx_height = m_imgui->scaled(23.f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); From b45675b4e13a85b87435f78c6ca0e6c128344c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 15 Oct 2021 18:34:31 +0200 Subject: [PATCH 16/42] Follow-up of 6194e67e689d85d4e6d0666dd4c2b993fdeeb90d - Separated the part that computed triangles normals and lighting inside the fragment shader into a separate shader mm_gouraud, which is only used for the multi-material painting gizmo. --- resources/shaders/gouraud.fs | 37 +------------ resources/shaders/gouraud.vs | 29 ++++------ resources/shaders/mm_gouraud.fs | 55 +++++++++++++++++++ resources/shaders/mm_gouraud.vs | 23 ++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 5 +- src/slic3r/GUI/GLShadersManager.cpp | 22 ++++---- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 43 +++++++++++++-- .../GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 2 + src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 37 ++++++++----- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 10 +++- 10 files changed, 179 insertions(+), 84 deletions(-) create mode 100644 resources/shaders/mm_gouraud.fs create mode 100644 resources/shaders/mm_gouraud.vs diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 2f2ca5fb5..ecef16f6d 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -50,8 +50,6 @@ varying float world_pos_z; varying float world_normal_z; varying vec3 eye_normal; -uniform bool compute_triangle_normals_in_fs; - void main() { if (any(lessThan(clipping_planes_dots, ZERO))) @@ -59,36 +57,7 @@ void main() vec3 color = uniform_color.rgb; float alpha = uniform_color.a; - vec2 intensity_fs = intensity; - vec3 eye_normal_fs = eye_normal; - float world_normal_z_fs = world_normal_z; - if (compute_triangle_normals_in_fs) { - vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); -#ifdef FLIP_TRIANGLE_NORMALS - triangle_normal = -triangle_normal; -#endif - - // First transform the normal into camera space and normalize the result. - eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(eye_normal_fs, LIGHT_TOP_DIR), 0.0); - - intensity_fs = vec2(0.0, 0.0); - intensity_fs.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - vec3 position = (gl_ModelViewMatrix * model_pos).xyz; - intensity_fs.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal_fs)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(eye_normal_fs, LIGHT_FRONT_DIR), 0.0); - intensity_fs.x += NdotL * LIGHT_FRONT_DIFFUSE; - - // z component of normal vector in world coordinate used for slope shading - world_normal_z_fs = slope.actived ? (normalize(slope.volume_world_normal_matrix * triangle_normal)).z : 0.0; - } - - if (slope.actived && world_normal_z_fs < slope.normal_z - EPSILON) { + if (slope.actived && world_normal_z < slope.normal_z - EPSILON) { color = vec3(0.7, 0.7, 1.0); alpha = 1.0; } @@ -96,8 +65,8 @@ void main() color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(color, ZERO, 0.3333) : color; #ifdef ENABLE_ENVIRONMENT_MAP if (use_environment_tex) - gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal_fs).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity_fs.x, alpha); + gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha); else #endif - gl_FragColor = vec4(vec3(intensity_fs.y) + color * intensity_fs.x, alpha); + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); } diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index 20a142452..d5e74b60b 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -54,26 +54,22 @@ varying float world_pos_z; varying float world_normal_z; varying vec3 eye_normal; -uniform bool compute_triangle_normals_in_fs; - void main() { - if (!compute_triangle_normals_in_fs) { - // First transform the normal into camera space and normalize the result. - eye_normal = normalize(gl_NormalMatrix * gl_Normal); + // First transform the normal into camera space and normalize the result. + eye_normal = normalize(gl_NormalMatrix * gl_Normal); - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - } + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; model_pos = gl_Vertex; // Point in homogenous coordinates. @@ -90,8 +86,7 @@ void main() } // z component of normal vector in world coordinate used for slope shading - if (!compute_triangle_normals_in_fs) - world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; + world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; gl_Position = ftransform(); // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. diff --git a/resources/shaders/mm_gouraud.fs b/resources/shaders/mm_gouraud.fs new file mode 100644 index 000000000..f7154b419 --- /dev/null +++ b/resources/shaders/mm_gouraud.fs @@ -0,0 +1,55 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); +const float EPSILON = 0.0001; + +uniform vec4 uniform_color; + +varying vec3 clipping_planes_dots; +varying vec4 model_pos; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + vec3 color = uniform_color.rgb; + float alpha = uniform_color.a; + + vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); +#ifdef FLIP_TRIANGLE_NORMALS + triangle_normal = -triangle_normal; +#endif + + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(gl_NormalMatrix * triangle_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + // x = diffuse, y = specular; + vec2 intensity = vec2(0.0, 0.0); + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * model_pos).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); +} diff --git a/resources/shaders/mm_gouraud.vs b/resources/shaders/mm_gouraud.vs new file mode 100644 index 000000000..2847c3136 --- /dev/null +++ b/resources/shaders/mm_gouraud.vs @@ -0,0 +1,23 @@ +#version 110 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform mat4 volume_world_matrix; +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +varying vec3 clipping_planes_dots; +varying vec4 model_pos; + +void main() +{ + model_pos = gl_Vertex; + // Point in homogenous coordinates. + vec4 world_pos = volume_world_matrix * gl_Vertex; + + gl_Position = ftransform(); + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); +} diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 86ec5634f..387c41315 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -456,7 +456,7 @@ private: GLGizmosManager m_gizmos; GLToolbar m_main_toolbar; GLToolbar m_undoredo_toolbar; - ClippingPlane m_clipping_planes[2]; + std::array m_clipping_planes; ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; SlaCap m_sla_caps[2]; @@ -651,6 +651,9 @@ public: void reset_clipping_planes_cache() { m_sla_caps[0].triangles.clear(); m_sla_caps[1].triangles.clear(); } void set_use_clipping_planes(bool use) { m_use_clipping_planes = use; } + bool get_use_clipping_planes() const { return m_use_clipping_planes; } + const std::array &get_clipping_planes() const { return m_clipping_planes; }; + void set_color_by(const std::string& value); void refresh_camera_scene_box(); diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 5dd478b57..65b85564d 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -61,25 +61,23 @@ std::pair GLShadersManager::init() // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); // used to render objects in 3d editor - // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. - // Because of this, objects had darker colors inside the multi-material gizmo. - // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. - if (platform_flavor() == PlatformFlavor::OSXOnArm) - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv + valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } #if ENABLE_ENVIRONMENT_MAP - , "ENABLE_ENVIRONMENT_MAP"sv -#endif - }); - else - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } -#if ENABLE_ENVIRONMENT_MAP - , { "ENABLE_ENVIRONMENT_MAP"sv } + , { "ENABLE_ENVIRONMENT_MAP"sv } #endif ); // used to render variable layers heights in 3d editor valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" }); // used to render highlight contour around selected triangles inside the multi-material gizmo valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" }); + // Used to render painted triangles inside the multi-material gizmo. Triangle normals are computed inside fragment shader. + // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. + // Because of this, objects had darker colors inside the multi-material gizmo. + // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. + if (platform_flavor() == PlatformFlavor::OSXOnArm) + valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}, {"FLIP_TRIANGLE_NORMALS"sv}); + else + valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}); return { valid, error }; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index e3344ad38..54a92210a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -174,6 +174,43 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) } } +void GLGizmoMmuSegmentation::render_triangles(const Selection &selection, const bool use_polygon_offset_fill) const +{ + ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data(); + auto *shader = wxGetApp().get_shader("mm_gouraud"); + if (!shader) + return; + shader->start_using(); + shader->set_uniform("clipping_plane", clp_data.clp_dataf); + shader->set_uniform("z_range", clp_data.z_range); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); + + const ModelObject *mo = m_c->selection_info()->model_object(); + int mesh_id = -1; + for (const ModelVolume *mv : mo->volumes) { + if (!mv->is_model_part()) + continue; + + ++mesh_id; + + const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix(); + + bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo_matrix.data())); + + shader->set_uniform("volume_world_matrix", trafo_matrix); + m_triangle_selectors[mesh_id]->render(m_imgui); + + glsafe(::glPopMatrix()); + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + } +} + static void render_extruders_combo(const std::string &label, const std::vector &extruders, const std::vector> &extruders_colors, @@ -554,9 +591,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) auto *shader = wxGetApp().get_current_shader(); if (!shader) return; - assert(shader->get_name() == "gouraud"); - ScopeGuard guard([shader]() { if (shader) shader->set_uniform("compute_triangle_normals_in_fs", false);}); - shader->set_uniform("compute_triangle_normals_in_fs", true); + assert(shader->get_name() == "mm_gouraud"); for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx) if (m_gizmo_scene.has_VBOs(color_idx)) { @@ -569,7 +604,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) } if (m_paint_contour.has_VBO()) { - ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); + ScopeGuard guard_mm_gouraud([shader]() { shader->start_using(); }); shader->stop_using(); auto *contour_shader = wxGetApp().get_shader("mm_contour"); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 604edf64d..a9e5f608e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -89,6 +89,8 @@ public: void set_painter_gizmo_data(const Selection& selection) override; + void render_triangles(const Selection& selection, bool use_polygon_offset_fill) const override; + // TriangleSelector::serialization/deserialization has a limit to store 19 different states. // EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored. // When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 27b68bc93..3fffede8f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -43,12 +43,29 @@ void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) } } +GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const +{ + ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}}; + // Take care of the clipping plane. The normal of the clipping plane is + // saved with opposite sign than we need to pass to OpenGL (FIXME) + if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) { + const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane(); + for (size_t i = 0; i < 3; ++i) + clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]); + clp_data_out.clp_dataf[3] = float(clp->get_data()[3]); + } + // z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) + if (m_c->get_canvas()->get_use_clipping_planes()) { + const std::array &clps = m_c->get_canvas()->get_clipping_planes(); + clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])}; + } + + return clp_data_out; +} void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool use_polygon_offset_fill) const { - const ModelObject* mo = m_c->selection_info()->model_object(); - ScopeGuard offset_fill_guard([&use_polygon_offset_fill]() { if (use_polygon_offset_fill) glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); @@ -58,27 +75,17 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool glsafe(::glPolygonOffset(-5.0, -5.0)); } - // Take care of the clipping plane. The normal of the clipping plane is - // saved with opposite sign than we need to pass to OpenGL (FIXME) - bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; - float clp_dataf[4] = {0.f, 0.f, 1.f, FLT_MAX}; - if (clipping_plane_active) { - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - for (size_t i=0; i<3; ++i) - clp_dataf[i] = -1.f * float(clp->get_data()[i]); - clp_dataf[3] = float(clp->get_data()[3]); - } - auto *shader = wxGetApp().get_shader("gouraud"); if (! shader) return; shader->start_using(); shader->set_uniform("slope.actived", false); shader->set_uniform("print_box.actived", false); - shader->set_uniform("clipping_plane", clp_dataf, 4); + shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf); ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); - int mesh_id = -1; + const ModelObject *mo = m_c->selection_info()->model_object(); + int mesh_id = -1; for (const ModelVolume* mv : mo->volumes) { if (! mv->is_model_part()) continue; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index cf12e9359..d02c2030d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -126,7 +126,7 @@ public: virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); protected: - void render_triangles(const Selection& selection, const bool use_polygon_offset_fill = true) const; + virtual void render_triangles(const Selection& selection, bool use_polygon_offset_fill = true) const; void render_cursor() const; void render_cursor_circle() const; void render_cursor_sphere(const Transform3d& trafo) const; @@ -176,6 +176,14 @@ protected: Right }; + struct ClippingPlaneDataWrapper + { + std::array clp_dataf; + std::array z_range; + }; + + ClippingPlaneDataWrapper get_clipping_plane_data() const; + private: bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const; void update_raycast_cache(const Vec2d& mouse_position, From 0c2d9f01a62624e38496c0d0d0404bbf046962d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 15 Oct 2021 18:50:56 +0200 Subject: [PATCH 17/42] Fixed z-fighting between contour around the area selected by smart fill and painted triangles inside the FDM support painting gizmo. --- resources/shaders/gouraud.fs | 7 +++++++ src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 16 ++++------------ src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 2 +- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index ecef16f6d..b2c38e4a1 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -32,6 +32,8 @@ struct SlopeDetection uniform vec4 uniform_color; uniform SlopeDetection slope; +uniform bool offset_depth_buffer; + #ifdef ENABLE_ENVIRONMENT_MAP uniform sampler2D environment_tex; uniform bool use_environment_tex; @@ -69,4 +71,9 @@ void main() else #endif gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); + + // In the support painting gizmo and the seam painting gizmo are painted triangles rendered over the already + // rendered object. To resolved z-fighting between previously rendered object and painted triangles, values + // inside the depth buffer are offset by small epsilon for painted triangles inside those gizmos. + gl_FragDepth = gl_FragCoord.z - (offset_depth_buffer ? EPSILON : 0.0); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 54a92210a..ee15dab88 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -143,7 +143,7 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - render_triangles(selection, false); + render_triangles(selection); m_c->object_clipper()->render_cut(); m_c->instances_hider()->render_cut(); @@ -174,7 +174,7 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) } } -void GLGizmoMmuSegmentation::render_triangles(const Selection &selection, const bool use_polygon_offset_fill) const +void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const { ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data(); auto *shader = wxGetApp().get_shader("mm_gouraud"); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index a9e5f608e..0991527f1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -89,7 +89,7 @@ public: void set_painter_gizmo_data(const Selection& selection) override; - void render_triangles(const Selection& selection, bool use_polygon_offset_fill) const override; + void render_triangles(const Selection& selection) const override; // TriangleSelector::serialization/deserialization has a limit to store 19 different states. // EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 3fffede8f..0fda1b877 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -64,17 +64,8 @@ GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_pl return clp_data_out; } -void GLGizmoPainterBase::render_triangles(const Selection& selection, const bool use_polygon_offset_fill) const +void GLGizmoPainterBase::render_triangles(const Selection& selection) const { - ScopeGuard offset_fill_guard([&use_polygon_offset_fill]() { - if (use_polygon_offset_fill) - glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); - }); - if (use_polygon_offset_fill) { - glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); - glsafe(::glPolygonOffset(-5.0, -5.0)); - } - auto *shader = wxGetApp().get_shader("gouraud"); if (! shader) return; @@ -585,7 +576,8 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) if (! shader) return; assert(shader->get_name() == "gouraud"); - + ScopeGuard guard([shader]() { if (shader) shader->set_uniform("offset_depth_buffer", false);}); + shader->set_uniform("offset_depth_buffer", true); for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), std::make_pair(&m_iva_blockers, blockers_color)}) { if (iva.first->has_VBOs()) { @@ -611,7 +603,7 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) auto *contour_shader = wxGetApp().get_shader("mm_contour"); contour_shader->start_using(); - glsafe(::glDepthFunc(GL_GEQUAL)); + glsafe(::glDepthFunc(GL_LEQUAL)); m_paint_contour.render(); glsafe(::glDepthFunc(GL_LESS)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index d02c2030d..3093b0bec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -126,7 +126,7 @@ public: virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); protected: - virtual void render_triangles(const Selection& selection, bool use_polygon_offset_fill = true) const; + virtual void render_triangles(const Selection& selection) const; void render_cursor() const; void render_cursor_circle() const; void render_cursor_sphere(const Transform3d& trafo) const; From 4d47e9a1840ef667564d1f9be8639cc6afa95b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 24 Feb 2021 07:52:11 +0100 Subject: [PATCH 18/42] Allow travels processed by the avoid crossing perimeters move further away from the outer perimeter. --- .../GCode/AvoidCrossingPerimeters.cpp | 326 ++++++++++++++---- .../GCode/AvoidCrossingPerimeters.hpp | 6 +- src/libslic3r/Line.hpp | 28 +- 3 files changed, 287 insertions(+), 73 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index ef3e18825..1f538862b 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -11,6 +11,7 @@ #include #include +#include namespace Slic3r { @@ -33,6 +34,16 @@ struct Intersection float distance; }; +struct ClosestLine +{ + // Index of the polygon containing this line. + size_t border_idx; + // Index of this line on the polygon containing it. + size_t line_idx; + // Closest point on the line. + Point point; +}; + // Finding all intersections of a set of contours with a line segment. struct AllIntersectionsVisitor { @@ -53,7 +64,7 @@ struct AllIntersectionsVisitor bool operator()(coord_t iy, coord_t ix) { - // Called with a row and colum of the grid cell, which is intersected by a line. + // Called with a row and column of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { Point intersection_point; @@ -82,7 +93,7 @@ struct FirstIntersectionVisitor { assert(pt_current != nullptr); assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. + // Called with a row and column of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); this->intersect = false; for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { @@ -103,6 +114,180 @@ struct FirstIntersectionVisitor bool intersect = false; }; +// Visitor to create a list of closet lines to a defined point. +struct MinDistanceVisitor +{ + explicit MinDistanceVisitor(const EdgeGrid::Grid &grid, const Point ¢er, double max_distance_squared) + : grid(grid), center(center), max_distance_squared(max_distance_squared) + {} + + void init() + { + this->closest_lines.clear(); + this->closest_lines_set.clear(); + } + + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and column of the grid cell, which is inside a bounding box. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + Point closest_point; + if (closest_lines_set.find(*it_contour_and_segment) == closest_lines_set.end() && + line_alg::distance_to_squared(Line(segment.first, segment.second), center, &closest_point) <= this->max_distance_squared) { + closest_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, closest_point}); + closest_lines_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid & grid; + const Slic3r::Point center; + std::vector closest_lines; + std::unordered_set, boost::hash>> closest_lines_set; + double max_distance_squared = std::numeric_limits::max(); +}; + +// Returns sorted list of closest lines to a passed point within a passed radius +static std::vector get_closest_lines_in_radius(const EdgeGrid::Grid &grid, const Point ¢er, float search_radius) +{ + Point radius_vector(search_radius, search_radius); + MinDistanceVisitor visitor(grid, center, search_radius * search_radius); + grid.visit_cells_intersecting_box(BoundingBox(center - radius_vector, center + radius_vector), visitor); + std::sort(visitor.closest_lines.begin(), visitor.closest_lines.end(), [¢er](const auto &l, const auto &r) { + return (center - l.point).template cast().squaredNorm() < (center - r.point).template cast().squaredNorm(); + }); + + return visitor.closest_lines; +} + +// When the offset is too big, then original travel doesn't have to cross created boundaries. +// For these cases, this function adds another intersection with lines around the start and the end point of the original travel. +static std::vector extend_for_closest_lines(const std::vector &intersections, + const AvoidCrossingPerimeters::Boundary &boundary, + const Point &start, + const Point &end, + const float search_radius) +{ + const std::vector start_lines = get_closest_lines_in_radius(boundary.grid, start, search_radius); + const std::vector end_lines = get_closest_lines_in_radius(boundary.grid, end, search_radius); + + // Compute distance to the closest point in the ClosestLine from begin of contour. + auto compute_distance = [&boundary](const ClosestLine &closest_line) -> float { + float dist_from_line_begin = (closest_line.point - boundary.boundaries[closest_line.border_idx][closest_line.line_idx]).cast().norm(); + return boundary.boundaries_params[closest_line.border_idx][closest_line.line_idx] + dist_from_line_begin; + }; + + // It tries to find closest lines for both start point and end point of the travel which has the same border_idx + auto endpoints_close_to_same_boundary = [&start_lines, &end_lines]() -> std::pair { + std::unordered_set boundaries_from_start; + for (const ClosestLine &cl_start : start_lines) + boundaries_from_start.insert(cl_start.border_idx); + for (const ClosestLine &cl_end : end_lines) + if (boundaries_from_start.find(cl_end.border_idx) != boundaries_from_start.end()) + for (const ClosestLine &cl_start : start_lines) + if (cl_start.border_idx == cl_end.border_idx) { + size_t cl_start_idx = &cl_start - &start_lines.front(); + size_t cl_end_idx = &cl_end - &end_lines.front(); + return std::make_pair(cl_start_idx, cl_end_idx); + } + return std::make_pair(std::numeric_limits::max(), std::numeric_limits::max()); + }; + + // If the existing two lines within the search radius start and end point belong to the same boundary, + // discard all intersection points because the whole detour could be on one boundary. + if (!start_lines.empty() && !end_lines.empty()) { + std::pair cl_indices = endpoints_close_to_same_boundary(); + if (cl_indices.first != std::numeric_limits::max()) { + assert(cl_indices.second != std::numeric_limits::max()); + const ClosestLine &cl_start = start_lines[cl_indices.first]; + const ClosestLine &cl_end = end_lines[cl_indices.second]; + std::vector new_intersections; + new_intersections.push_back({cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)}); + new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)}); + return new_intersections; + } + } + + // Returns ClosestLine which is closer to the point "close_to" then point inside passed Intersection. + auto get_closer = [&search_radius](const std::vector &closest_lines, const Intersection &intersection, + const Point &close_to) -> size_t { + for (const ClosestLine &cl : closest_lines) { + double old_dist = (close_to - intersection.point).cast().squaredNorm(); + if (cl.border_idx == intersection.border_idx && old_dist <= (search_radius * search_radius) && + (close_to - cl.point).cast().squaredNorm() < old_dist) + return &cl - &closest_lines.front(); + } + return std::numeric_limits::max(); + }; + + // Try to find ClosestLine with same boundary_idx as any existing Intersection + auto find_closest_line_with_same_boundary_idx = [](const std::vector & closest_lines, + const std::vector &intersections, const bool reverse) -> size_t { + std::unordered_set boundaries_indices; + for (const ClosestLine &closest_line : closest_lines) + boundaries_indices.insert(closest_line.border_idx); + + // This function must be called only in the case that exists closest_line with boundary_idx equals to intersection.border_idx + auto find_closest_line_index = [&closest_lines](const Intersection &intersection) -> size_t { + for (const ClosestLine &closest_line : closest_lines) + if (closest_line.border_idx == intersection.border_idx) return &closest_line - &closest_lines.front(); + // This is an invalid state. + assert(false); + return std::numeric_limits::max(); + }; + + if (reverse) { + for (const Intersection &intersection : boost::adaptors::reverse(intersections)) + if (boundaries_indices.find(intersection.border_idx) != boundaries_indices.end()) + return find_closest_line_index(intersection); + } else { + for (const Intersection &intersection : intersections) + if (boundaries_indices.find(intersection.border_idx) != boundaries_indices.end()) + return find_closest_line_index(intersection); + } + return std::numeric_limits::max(); + }; + + std::vector new_intersections = intersections; + if (!intersections.empty() && !start_lines.empty()) { + size_t cl_start_idx = get_closer(start_lines, new_intersections.front(), start); + if (cl_start_idx != std::numeric_limits::max()) { + // If there is any ClosestLine around the start point closer to the Intersection, then replace this Intersection with ClosestLine. + const ClosestLine &cl_start = start_lines[cl_start_idx]; + new_intersections.front() = {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)}; + } else { + // Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the + // vector of intersections. This allows in some cases when it is more than one around ClosestLine start point chose that one which + // minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then + // use the first one, which is the closest one to the start point. + size_t start_closest_lines_idx = find_closest_line_with_same_boundary_idx(start_lines, intersections, true); + const ClosestLine &cl_start = (start_closest_lines_idx != std::numeric_limits::max()) ? start_lines[start_closest_lines_idx] : start_lines.front(); + new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)}); + } + } else if (!intersections.empty() && !end_lines.empty()) { + size_t cl_end_idx = get_closer(end_lines, new_intersections.back(), end); + if (cl_end_idx != std::numeric_limits::max()) { + // If there is any ClosestLine around the end point closer to the Intersection, then replace this Intersection with ClosestLine. + const ClosestLine &cl_end = end_lines[cl_end_idx]; + new_intersections.back() = {cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)}; + } else { + // Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the + // vector of intersections. This allows in some cases when it is more than one around ClosestLine end point chose that one which + // minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then + // use the first one, which is the closest one to the end point. + size_t end_closest_lines_idx = find_closest_line_with_same_boundary_idx(end_lines, intersections, false); + const ClosestLine &cl_end = (end_closest_lines_idx != std::numeric_limits::max()) ? end_lines[end_closest_lines_idx] : end_lines.front(); + new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)}); + } + } + return new_intersections; +} + // point_idx is the index from which is different vertex is searched. template static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) @@ -268,10 +453,63 @@ static std::vector simplify_travel(const AvoidCrossingPerimeters::B return simplified_path; } +// called by get_perimeter_spacing() / get_perimeter_spacing_external() +static inline float get_default_perimeter_spacing(const PrintObject &print_object) +{ + std::vector printing_extruders = print_object.object_extruders(); + assert(!printing_extruders.empty()); + float avg_extruder = 0; + for(unsigned int extruder_id : printing_extruders) + avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id))); + avg_extruder /= printing_extruders.size(); + return avg_extruder; +} + +// called by get_boundary() / avoid_perimeters_inner() +static float get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const LayerRegion *layer_region : layer.regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); + return perimeter_spacing; +} + +// called by get_boundary_external() +static float get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const PrintObject *object : layer.object()->print()->objects()) + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); + return perimeter_spacing; +} + // Called by avoid_perimeters() and by simplify_travel_heuristics(). static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary, const Point &start, const Point &end, + const Layer &layer, std::vector &result_out) { const Polygons &boundaries = boundary.boundaries; @@ -288,23 +526,31 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx] + dist_from_line_begin; } std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); + + // Search radius should always be at least equals to the value of offset used for computing boundaries. + const float search_radius = 2.f * get_perimeter_spacing(layer); + // When the offset is too big, then original travel doesn't have to cross created boundaries. + // These cases are fixed by calling extend_for_closest_lines. + intersections = extend_for_closest_lines(intersections, boundary, start, end, search_radius); } std::vector result; result.push_back({start, -1}); +#if 0 auto crossing_boundary_from_inside = [&boundary](const Point &start, const Intersection &intersection) { const Polygon &poly = boundary.boundaries[intersection.border_idx]; Vec2d poly_line = Line(poly[intersection.line_idx], poly[(intersection.line_idx + 1) % poly.size()]).normal().cast(); Vec2d intersection_vec = (intersection.point - start).cast(); return poly_line.normalized().dot(intersection_vec.normalized()) >= 0; }; +#endif for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { // The entry point to the boundary polygon const Intersection &intersection_first = *it_first; - if(!crossing_boundary_from_inside(start, intersection_first)) - continue; +// if(!crossing_boundary_from_inside(start, intersection_first)) +// continue; // Skip the it_first from the search for the farthest exit point from the boundary polygon auto it_last_item = std::make_reverse_iterator(it_first) - 1; // Search for the farthest intersection different from it_first but with the same border_idx @@ -353,8 +599,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundaries, Line(start, end), result, intersections, - debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); + export_travel_to_svg(boundaries, Line(start, end), result, intersections, debug_out_path("AvoidCrossingPerimetersInner-initial-%d-%d.svg", layer.id(), iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ @@ -365,7 +610,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo { static int iRun = 0; export_travel_to_svg(boundaries, Line(start, end), result, intersections, - debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); + debug_out_path("AvoidCrossingPerimetersInner-final-%d-%d.svg", layer.id(), iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ @@ -377,17 +622,18 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary, const Point &start, const Point &end, + const Layer &layer, Polyline &result_out) { // Travel line is completely or partially inside the bounding box. std::vector path; - size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path); + size_t num_intersections = avoid_perimeters_inner(boundary, start, end, layer, path); result_out = to_polyline(path); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); + export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d-%d.svg", layer.id(), iRun ++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ @@ -482,58 +728,6 @@ static bool need_wipe(const GCode &gcodegen, return wipe_needed; } -// called by get_perimeter_spacing() / get_perimeter_spacing_external() -static inline float get_default_perimeter_spacing(const PrintObject &print_object) -{ - std::vector printing_extruders = print_object.object_extruders(); - assert(!printing_extruders.empty()); - float avg_extruder = 0; - for(unsigned int extruder_id : printing_extruders) - avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id))); - avg_extruder /= printing_extruders.size(); - return avg_extruder; -} - -// called by get_boundary() -static float get_perimeter_spacing(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const LayerRegion *layer_region : layer.regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()); - return perimeter_spacing; -} - -// called by get_boundary_external() -static float get_perimeter_spacing_external(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const PrintObject *object : layer.object()->print()->objects()) - if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) - for (const LayerRegion *layer_region : l->regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++ regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()); - return perimeter_spacing; -} - // Adds points around all vertices so that the offset affects only small sections around these vertices. static void resample_polygon(Polygon &polygon, double dist_from_vertex) { @@ -795,14 +989,14 @@ static ExPolygons get_boundary(const Layer &layer) const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; auto const *support_layer = dynamic_cast(&layer); - ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); + ExPolygons boundary = union_ex(inner_offset(layer.lslices, 1.5 * perimeter_spacing)); if(support_layer) { #ifdef INCLUDE_SUPPORTS_IN_BOUNDARY - append(boundary, inner_offset(support_layer->support_islands.expolygons, perimeter_offset)); + append(boundary, inner_offset(support_layer->support_islands.expolygons, 1.5 * perimeter_spacing)); #endif auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON); if (layer_below) - append(boundary, inner_offset(layer_below->lslices, perimeter_offset)); + append(boundary, inner_offset(layer_below->lslices, 1.5 * perimeter_spacing)); // After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons boundary = union_ex(boundary); } @@ -925,7 +1119,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & // Trim the travel line by the bounding box. if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { - travel_intersection_count = avoid_perimeters(m_internal, startf.cast(), endf.cast(), result_pl); + travel_intersection_count = avoid_perimeters(m_internal, startf.cast(), endf.cast(), *gcodegen.layer(), result_pl); result_pl.points.front() = start; result_pl.points.back() = end; } @@ -936,7 +1130,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & // Trim the travel line by the bounding box. if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) { - travel_intersection_count = avoid_perimeters(m_external, startf.cast(), endf.cast(), result_pl); + travel_intersection_count = avoid_perimeters(m_external, startf.cast(), endf.cast(), *gcodegen.layer(), result_pl); result_pl.points.front() = start; result_pl.points.back() = end; } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index d178e3c89..412822c66 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -35,13 +35,13 @@ public: struct Boundary { // Collection of boundaries used for detection of crossing perimeters for travels - Polygons boundaries; + Polygons boundaries; // Bounding box of boundaries - BoundingBoxf bbox; + BoundingBoxf bbox; // Precomputed distances of all points in boundaries std::vector> boundaries_params; // Used for detection of intersection between line and any polygon from boundaries - EdgeGrid::Grid grid; + EdgeGrid::Grid grid; void clear() { diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index b62775bfe..bc902ed85 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -40,23 +40,42 @@ template auto get_b(L &&l) { return Traits>::get_b(l) // Distance to the closest point of line. template -double distance_to_squared(const L &line, const Vec, Scalar> &point) +double distance_to_squared(const L &line, const Vec, Scalar> &point, Vec, Scalar> *nearest_point) { const Vec, double> v = (get_b(line) - get_a(line)).template cast(); const Vec, double> va = (point - get_a(line)).template cast(); const double l2 = v.squaredNorm(); // avoid a sqrt - if (l2 == 0.0) + if (l2 == 0.0) { // a == b case + *nearest_point = get_a(line); return va.squaredNorm(); + } // Consider the line extending the segment, parameterized as a + t (b - a). // We find projection of this point onto the line. // It falls where t = [(this-a) . (b-a)] / |b-a|^2 const double t = va.dot(v) / l2; - if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment - else if (t > 1.0) return (point - get_b(line)).template cast().squaredNorm(); // beyond the 'b' end of the segment + if (t < 0.0) { + // beyond the 'a' end of the segment + *nearest_point = get_a(line); + return va.squaredNorm(); + } else if (t > 1.0) { + // beyond the 'b' end of the segment + *nearest_point = get_b(line); + return (point - get_b(line)).template cast().squaredNorm(); + } + + *nearest_point = (get_a(line).template cast() + t * v).template cast>(); return (t * v - va).squaredNorm(); } +// Distance to the closest point of line. +template +double distance_to_squared(const L &line, const Vec, Scalar> &point) +{ + Vec, Scalar> nearest_point; + return distance_to_squared(line, point, &nearest_point); +} + template double distance_to(const L &line, const Vec, Scalar> &point) { @@ -81,6 +100,7 @@ public: bool intersection_infinite(const Line &other, Point* point) const; bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; } double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); } + double distance_to_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_squared(*this, point, closest_point); } double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); } double perp_distance_to(const Point &point) const; bool parallel_to(double angle) const; From f494ad565ba7140281274e3a6955d24cffe8bc97 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 18 Oct 2021 14:38:19 +0200 Subject: [PATCH 19/42] Fix some builds that fail with cgal 5.2.3 --- src/libslic3r/MeshBoolean.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index e2e5e254a..95daa33a6 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -159,8 +159,9 @@ template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) int i = 0; Vec3i facet; for (auto v : vtc) { - if (i > 2 || v < 0 || v >= cgalmesh.vertices().size()) { i = 0; break; } - facet(i++) = v; + int iv = v; + if (i > 2 || iv < 0 || iv >= int(cgalmesh.vertices().size())) { i = 0; break; } + facet(i++) = iv; } if (i == 3) From 556e0c53c7a026a9a701a302d179764f0be2c41b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 18 Oct 2021 14:51:09 +0200 Subject: [PATCH 20/42] Some more refactoring of ClipperLib / closing() / opening() --- src/libslic3r/Brim.cpp | 2 +- src/libslic3r/ClipperUtils.cpp | 27 +++++++++++++++++++++++++++ src/libslic3r/ClipperUtils.hpp | 27 ++++++++++++++++++++++----- src/libslic3r/LayerRegion.cpp | 5 ++--- src/libslic3r/PrintObject.cpp | 4 ++-- 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 69a9e87f9..a13d578a3 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -50,7 +50,7 @@ static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &pr { ExPolygons ex_polygons; for (LayerRegion *region : print_object.layers().front()->regions()) - Slic3r::append(ex_polygons, offset_ex(offset_ex(region->slices.surfaces, float(SCALED_EPSILON)), -float(SCALED_EPSILON))); + Slic3r::append(ex_polygons, closing_ex(region->slices.surfaces, float(SCALED_EPSILON))); return ex_polygons; } diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 9b95bfed6..28fb09313 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -452,6 +452,11 @@ ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const fl { return PolyTreeToExPolygons(offset_paths(expolygons_offset(expolygons, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } +ExPolygons offset2_ex(const Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + //FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces). + return PolyTreeToExPolygons(offset_paths(expolygons_offset(surfaces, delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} // 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) @@ -466,6 +471,13 @@ Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delt assert(delta2 > 0); return PolyTreeToExPolygons(shrink_paths(expand_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } +Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + //FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces). + return PolyTreeToExPolygons(shrink_paths(expand_paths(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} // 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) @@ -474,6 +486,19 @@ Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, c assert(delta2 > 0); return to_polygons(expand_paths(shrink_paths(ClipperUtils::PolygonsProvider(polygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); } +Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + return to_polygons(expand_paths(shrink_paths(ClipperUtils::ExPolygonsProvider(expolygons), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} +Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) +{ + assert(delta1 > 0); + assert(delta2 > 0); + //FIXME it may be more efficient to offset to_expolygons(surfaces) instead of to_polygons(surfaces). + return to_polygons(expand_paths(shrink_paths(ClipperUtils::SurfacesProvider(surfaces), delta1, joinType, miterLimit), delta2, joinType, miterLimit)); +} // Fix of #117: A large fractal pyramid takes ages to slice // The Clipper library has difficulties processing overlapping polygons. @@ -525,6 +550,8 @@ Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) + { return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 829611176..bbd91c0fd 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -345,19 +345,35 @@ inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const floa // 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); +Slic3r::ExPolygons offset2_ex(const Slic3r::Surfaces &surfaces, 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); } +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); } +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) + { assert(delta > 0); return offset2_ex(polygons, delta, - delta, joinType, miterLimit); } +inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(surfaces, 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::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, 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::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return opening(expolygons, delta, delta, joinType, miterLimit); } +inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { return opening(surfaces, delta, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(polygons, - delta, delta, joinType, miterLimit); } +inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset2_ex(surfaces, - delta, delta, joinType, miterLimit); } Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip); @@ -366,6 +382,7 @@ Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 356811b74..4dbffe7b0 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -431,9 +431,8 @@ void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_comp for (const Surface &surface : this->slices.surfaces) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - ExPolygons surfaces = to_expolygons(std::move(this->slices.surfaces)); - Polygons tmp = intersection(surfaces, trimming_polygons); - append(tmp, diff(surfaces, offset(offset_ex(surfaces, -elephant_foot_compensation_perimeter_step), elephant_foot_compensation_perimeter_step))); + Polygons tmp = intersection(this->slices.surfaces, trimming_polygons); + append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step))); this->slices.set(union_ex(tmp), stInternal); } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dd5a2b573..fd98feff7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1088,7 +1088,7 @@ void PrintObject::discover_vertical_shells() // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. if (perimeter_offset > 0.) { // The layer.lslices are forced to merge by expanding them first. - polygons_append(cache.holes, offset(offset_ex(layer.lslices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing)); + polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices)); @@ -1325,7 +1325,7 @@ void PrintObject::discover_vertical_shells() #if 1 // Intentionally inflate a bit more than how much the region has been shrunk, // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). - shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); + shell = opening(union_(shell), 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); if (shell.empty()) continue; #else From 32ebfa66e936a67160615aec5f2556fc8956a4d7 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 18 Oct 2021 14:56:02 +0200 Subject: [PATCH 21/42] Fix of M106 on every new layer #7094 after parallelization of CoolingBuffer: Remember the last fan speed emitted at the previous layer. --- src/libslic3r/GCode/CoolingBuffer.cpp | 12 ++++++------ src/libslic3r/GCode/CoolingBuffer.hpp | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 9ca85c728..3dcc121c1 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -35,6 +35,7 @@ void CoolingBuffer::reset(const Vec3d &position) m_current_pos[1] = float(position.y()); m_current_pos[2] = float(position.z()); m_current_pos[4] = float(m_config.travel_speed.value); + m_fan_speed = -1; } struct CoolingLine @@ -689,10 +690,9 @@ std::string CoolingBuffer::apply_layer_cooldown( // Second generate the adjusted G-code. std::string new_gcode; new_gcode.reserve(gcode.size() * 2); - int fan_speed = -1; bool bridge_fan_control = false; int bridge_fan_speed = 0; - auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { + auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed ]() { #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; @@ -733,9 +733,9 @@ std::string CoolingBuffer::apply_layer_cooldown( bridge_fan_speed = 0; fan_speed_new = 0; } - if (fan_speed_new != fan_speed) { - fan_speed = fan_speed_new; - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); + if (fan_speed_new != m_fan_speed) { + m_fan_speed = fan_speed_new; + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed); } }; @@ -759,7 +759,7 @@ std::string CoolingBuffer::apply_layer_cooldown( new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed); } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) { if (bridge_fan_control) - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed); } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { // Just remove this comment. } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 5f49ef455..1fe040518 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -41,6 +41,8 @@ private: // X,Y,Z,E,F std::vector m_axis; std::vector m_current_pos; + // Current known fan speed or -1 if not known yet. + int m_fan_speed; // Cached from GCodeWriter. // Printing extruder IDs, zero based. std::vector m_extruder_ids; From 5946989c2130a8499c92832cd72b8e775ea0f7fc Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 18 Oct 2021 15:02:13 +0200 Subject: [PATCH 22/42] Stop giving notifications focus on hover. --- src/slic3r/GUI/NotificationManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 2a27ce74a..40f5c3116 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -215,7 +215,8 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init } if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) { - ImGui::SetNextWindowFocus(); + // Uncomment if imgui window focus is needed on hover. I cant find any case. + //ImGui::SetNextWindowFocus(); set_hovered(); } From 56c3ea0261497618259220d205657cf7d0a0ac02 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 6 Oct 2021 09:27:50 +0200 Subject: [PATCH 23/42] SendSystemInfo: Use /proc/info instead on lscpu on Linux, center dialog after resizing --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index db6ebad57..e1a6d2d92 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -36,7 +36,9 @@ #include #pragma comment(lib, "iphlpapi.lib") #elif __APPLE__ -#import + #import +#else // Linux/BSD + #include #endif namespace Slic3r { @@ -381,11 +383,15 @@ static std::string generate_system_info_json() cpu_node.put("Model", sysctl["machdep.cpu.brand_string"]); cpu_node.put("Vendor", sysctl["machdep.cpu.vendor"]); #else // linux/BSD - std::map lscpu = parse_lscpu_etc("lscpu", ':'); - cpu_node.put("Arch", lscpu["Architecture"]); - cpu_node.put("Cores", lscpu["CPU(s)"]); - cpu_node.put("Model", lscpu["Model name"]); - cpu_node.put("Vendor", lscpu["Vendor ID"]); + std::map lscpu = parse_lscpu_etc("cat /proc/cpuinfo", ':'); + if (auto ncpu_it = lscpu.find("processor"); ncpu_it != lscpu.end()) { + std::string& ncpu = ncpu_it->second; + if (int num=0; std::from_chars(ncpu.data(), ncpu.data() + ncpu.size(), num).ec != std::errc::invalid_argument) + ncpu = std::to_string(num + 1); + } + cpu_node.put("Cores", lscpu["processor"]); + cpu_node.put("Model", lscpu["model name"]); + cpu_node.put("Vendor", lscpu["vendor_id"]); #endif hw_node.add_child("CPU", cpu_node); @@ -547,6 +553,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) const auto size = GetSize(); SetSize(std::max(size.GetWidth(), MIN_WIDTH * em), std::max(size.GetHeight(), MIN_HEIGHT * em)); + CenterOnParent(); m_btn_send->Bind(wxEVT_BUTTON, [this](const wxEvent&) { From f72a5cf1e7059d5d257a895e47cc9a6a39553ca5 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 6 Oct 2021 15:48:35 +0200 Subject: [PATCH 24/42] SendSystemInfo: Only get the scaling on Win, not on mac or Linux --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index e1a6d2d92..9aaef88b2 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -398,20 +398,17 @@ static std::string generate_system_info_json() pt::ptree monitors_node; for (int i=0; i Date: Wed, 6 Oct 2021 15:49:17 +0200 Subject: [PATCH 25/42] SendSystemInfo: Open the dialog based on appconfig, even in alphas --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 9aaef88b2..6add98e62 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -131,16 +131,14 @@ public: // current version is newer. Only major and minor versions are compared. static bool should_dialog_be_shown() { - return false; - std::string last_sent_version = wxGetApp().app_config->get("version_system_info_sent"); Semver semver_current(SLIC3R_VERSION); Semver semver_last_sent; if (! last_sent_version.empty()) semver_last_sent = Semver(last_sent_version); - if (semver_current.prerelease() && std::string(semver_current.prerelease()) == "alpha") - return false; // Don't show in alphas. + // if (semver_current.prerelease() && std::string(semver_current.prerelease()) == "alpha") + // return false; // Don't show in alphas. // Show the dialog if current > last, but they differ in more than just patch. return ((semver_current.maj() > semver_last_sent.maj()) From ea25461a95bcf0cfab92a909f6f4084d3757129f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 7 Oct 2021 12:15:31 +0200 Subject: [PATCH 26/42] An attempt to fix the SendSystemInfo dialog on GTK3 --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 6add98e62..7bdf1496d 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -54,8 +54,8 @@ std::string gl_get_string_safe(GLenum param, const std::string& default_value); class SendSystemInfoDialog : public DPIDialog { enum { - MIN_WIDTH = 80, - MIN_HEIGHT = 50 + MIN_WIDTH = 70, + MIN_HEIGHT = 34 }; public: @@ -454,6 +454,8 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) GUI::DPIDialog(parent, wxID_ANY, _L("Send system info"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) { + const int em = GUI::wxGetApp().em_unit(); + // Get current PrusaSliver version info. std::string app_name; { @@ -501,7 +503,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) std::string("") + filename + ""); wxString label3 = _L("Show verbatim data that will be sent"); - auto* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); + auto* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(70*em, 34*em), wxHW_SCROLLBAR_NEVER); wxString html = GUI::format_wxstr( "" "
" @@ -515,7 +517,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) + "" + label3 + "
" + "", bgr_clr_str, text_clr_str); html_window->SetPage(html); - html_window->Bind(wxEVT_HTML_LINK_CLICKED, [this](wxHtmlLinkEvent &evt) { + html_window->Bind(wxEVT_HTML_LINK_CLICKED, [this](wxHtmlLinkEvent&) { ShowJsonDialog dlg(this, m_system_info_json, GetSize().Scale(0.9, 0.7)); dlg.ShowModal(); }); @@ -527,7 +529,6 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) m_btn_send = new wxButton(this, wxID_ANY, _L("Send system info")); auto* hsizer = new wxBoxSizer(wxHORIZONTAL); - const int em = GUI::wxGetApp().em_unit(); hsizer->Add(m_btn_ask_later); hsizer->AddSpacer(em); hsizer->Add(m_btn_dont_send); @@ -548,6 +549,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) const auto size = GetSize(); SetSize(std::max(size.GetWidth(), MIN_WIDTH * em), std::max(size.GetHeight(), MIN_HEIGHT * em)); + CenterOnParent(); m_btn_send->Bind(wxEVT_BUTTON, [this](const wxEvent&) From 8d115def760fb63f3731159b7a26fb1d56bf127b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 7 Oct 2021 13:57:00 +0200 Subject: [PATCH 27/42] SendSystemInfo: Trim leading/trailing whitespace from all the values --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 7bdf1496d..908b52252 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -436,15 +436,23 @@ static std::string generate_system_info_json() pt::ptree root; root.add_child("data", data_node); + // Now go through all the values and trim leading/trailing whitespace. + // Some CPU names etc apparently have trailing spaces... + std::function remove_whitespace; + remove_whitespace = [&remove_whitespace](pt::ptree& t) -> void + { + if (t.empty()) // Trim whitespace + boost::algorithm::trim(t.data()); + else + for (auto it = t.begin(); it != t.end(); ++it) + remove_whitespace(it->second); + }; + remove_whitespace(root); + // Serialize the tree into JSON and return it. std::stringstream ss; pt::write_json(ss, root); return ss.str(); - - // FURTHER THINGS TO CONSIDER: - //std::cout << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << std::endl; // Unix - // ? CPU, GPU, UNKNOWN ? - // printers? will they be installed already? } From 13ff92335b6e9423537f71f3e38c36b4bdfab44e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 8 Oct 2021 12:23:02 +0200 Subject: [PATCH 28/42] Several fixes and improvements in SendSystemInfoDialog: - do not show memory in MB, show it in GiB rounded to one decimal place - when sending fails, the HTTP error code is not presented to the user (it is logged though) - when the user cancels the sending, no extra "sending cancelled" message is shown - in case there is no internet connection, the dialog is not shown at all - a 6 second timeout for a case that connection is lost during sending - the dialog is only shown when the wizard does not show on startup --- src/slic3r/GUI/GUI_App.cpp | 12 ++++--- src/slic3r/GUI/SendSystemInfoDialog.cpp | 43 +++++++++++++++++++------ src/slic3r/Utils/Http.cpp | 15 +++++++++ src/slic3r/Utils/Http.hpp | 2 ++ 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a98998a84..7d6940bd0 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -676,16 +676,18 @@ void GUI_App::post_init() if (this->preset_updater) { this->check_updates(false); CallAfter([this] { - this->config_wizard_startup(); + bool cw_showed = this->config_wizard_startup(); this->preset_updater->slic3r_update_notify(); this->preset_updater->sync(preset_bundle); + if (! cw_showed) { + // The CallAfter is needed as well, without it, GL extensions did not show. + // Also, we only want to show this when the wizard does not, so the new user + // sees something else than "we want something" on the first start. + show_send_system_info_dialog_if_needed(); + } }); } - // 'Send system info' dialog. Again, a CallAfter is needed on mac. - // Without it, GL extensions did not show. - CallAfter([] { show_send_system_info_dialog_if_needed(); }); - #ifdef _WIN32 // Sets window property to mainframe so other instances can indentify it. OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 908b52252..d90d2223f 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,8 @@ namespace Slic3r { namespace GUI { +static const std::string SEND_SYSTEM_INFO_DOMAIN = "prusa3d.com"; +static const std::string SEND_SYSTEM_INFO_URL = "https://files." + SEND_SYSTEM_INFO_DOMAIN + "/wp-json/v1/ps"; // Declaration of a free function defined in OpenGLManager.cpp: @@ -140,9 +143,24 @@ static bool should_dialog_be_shown() // if (semver_current.prerelease() && std::string(semver_current.prerelease()) == "alpha") // return false; // Don't show in alphas. - // Show the dialog if current > last, but they differ in more than just patch. - return ((semver_current.maj() > semver_last_sent.maj()) + // New version means current > last, but they must differ in more than just patch. + bool new_version = ((semver_current.maj() > semver_last_sent.maj()) || (semver_current.maj() == semver_last_sent.maj() && semver_current.min() > semver_last_sent.min() )); + + if (! new_version) + return false; + + std::cout << "Sending system info was not confirmed/declined in this version yet.\n" + "Pinging prusa3d.com to see if it can be offered now." << std::endl; + bool is_internet = + #ifdef _WIN32 + std::system((std::string("ping /n 1 /w 1 ") + SEND_SYSTEM_INFO_DOMAIN).data()) == 0; // 1 packet, 1 sec timeout + #else + std::system((std::string("ping -c 1 -q -w 1 ") + SEND_SYSTEM_INFO_DOMAIN).data()) == 0; // 1 packet, quiet output, 1 sec timeout + #endif + std::cout << "Pinging prusa3d.com was " << (is_internet ? "" : "NOT ") << "successful." << std::endl; + + return is_internet; } @@ -364,9 +382,13 @@ static std::string generate_system_info_json() data_node.put("SystemLanguage", sys_language); data_node.put("TranslationLanguage: ", wxGetApp().app_config->get("translation_language")); + pt::ptree hw_node; - hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName()); - hw_node.put("RAM_MB", size_t(Slic3r::total_physical_memory()/1000000)); + { + hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName()); + // Round MiB to hundreds,then present in GiB + hw_node.put("RAM_GiB", std::round(Slic3r::total_physical_memory()/104857600.)/10.); + } // Now get some CPU info: pt::ptree cpu_node; @@ -604,15 +626,16 @@ bool SendSystemInfoDialog::send_info() } result; // No synchronization needed, UI thread reads only after worker is joined. auto send = [&job_done, &result](const std::string& data) { - const std::string url = "https://files.prusa3d.com/wp-json/v1/ps"; - Http http = Http::post(url); + Http http = Http::post(SEND_SYSTEM_INFO_URL); http.header("Content-Type", "application/json") + .timeout_max(6) // seconds .set_post_body(data) .on_complete([&result](std::string body, unsigned status) { result = { Result::Success, _L("System info sent successfully. Thank you.") }; }) .on_error([&result](std::string body, std::string error, unsigned status) { - result = { Result::Error, GUI::format_wxstr(_L("Sending system info failed! Status: %1%"), status) }; + result = { Result::Error, _L("Sending system info failed!") }; + BOOST_LOG_TRIVIAL(error) << "Sending system info failed! STATUS: " << status; }) .on_progress([&job_done, &result](Http::Progress, bool &cancel) { if (job_done) // UI thread wants us to cancel. @@ -634,8 +657,10 @@ bool SendSystemInfoDialog::send_info() job_done = true; // In case the user closed the dialog, let the other thread know sending_thread.join(); // and wait until it terminates. - InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str); - info_dlg.ShowModal(); + if (result.value != Result::Cancelled) { // user knows he cancelled, no need to tell him. + InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str); + info_dlg.ShowModal(); + } return result.value == Result::Success; } diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index f1614017f..fc7afbb34 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -104,6 +104,7 @@ struct Http::priv { enum { DEFAULT_TIMEOUT_CONNECT = 10, + DEFAULT_TIMEOUT_MAX = 0, DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024, }; @@ -137,6 +138,7 @@ struct Http::priv static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp); void set_timeout_connect(long timeout); + void set_timeout_max(long timeout); void form_add_file(const char *name, const fs::path &path, const char* filename); void set_post_body(const fs::path &path); void set_post_body(const std::string &body); @@ -163,6 +165,7 @@ Http::priv::priv(const std::string &url) } set_timeout_connect(DEFAULT_TIMEOUT_CONNECT); + set_timeout_max(DEFAULT_TIMEOUT_MAX); ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_APP_NAME "/" SLIC3R_VERSION); ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer.front()); @@ -253,6 +256,11 @@ void Http::priv::set_timeout_connect(long timeout) ::curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout); } +void Http::priv::set_timeout_max(long timeout) +{ + ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); +} + void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename) { // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows @@ -409,6 +417,13 @@ Http& Http::timeout_connect(long timeout) return *this; } +Http& Http::timeout_max(long timeout) +{ + if (timeout < 1) { timeout = priv::DEFAULT_TIMEOUT_MAX; } + if (p) { p->set_timeout_max(timeout); } + return *this; +} + Http& Http::size_limit(size_t sizeLimit) { if (p) { p->limit = sizeLimit; } diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index 52e48a394..61d84c51e 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -58,6 +58,8 @@ public: // Sets a maximum connection timeout in seconds Http& timeout_connect(long timeout); + // Sets a maximum total request timeout in seconds + Http& timeout_max(long timeout); // Sets a maximum size of the data that can be received. // A value of zero sets the default limit, which is is 5MB. Http& size_limit(size_t sizeLimit); From 5b20406a33bab21adc7da876880f92f784162355 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 8 Oct 2021 15:15:10 +0200 Subject: [PATCH 29/42] SendSystemInfo: Reporting RAM in GiB --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index d90d2223f..7c444f34a 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -386,8 +386,8 @@ static std::string generate_system_info_json() pt::ptree hw_node; { hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName()); - // Round MiB to hundreds,then present in GiB - hw_node.put("RAM_GiB", std::round(Slic3r::total_physical_memory()/104857600.)/10.); + size_t num = std::round(Slic3r::total_physical_memory()/107374100.); + hw_node.put("RAM_GiB", std::to_string(num / 10) + "." + std::to_string(num % 10)); } // Now get some CPU info: From 692a0dade79b656b8a4b82d19f5a8969b67d445b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 8 Oct 2021 15:27:45 +0200 Subject: [PATCH 30/42] SendSystemInfo macOS fixes (get system language, fix ping) --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 7c444f34a..72316215a 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -38,6 +38,7 @@ #pragma comment(lib, "iphlpapi.lib") #elif __APPLE__ #import + #include #else // Linux/BSD #include #endif @@ -155,7 +156,9 @@ static bool should_dialog_be_shown() bool is_internet = #ifdef _WIN32 std::system((std::string("ping /n 1 /w 1 ") + SEND_SYSTEM_INFO_DOMAIN).data()) == 0; // 1 packet, 1 sec timeout - #else + #elif __APPLE__ + std::system((std::string("ping -c 1 -q -t 1 ") + SEND_SYSTEM_INFO_DOMAIN).data()) == 0; // 1 packet, quiet output, 1 sec timeout + #else // Linux/BSD std::system((std::string("ping -c 1 -q -w 1 ") + SEND_SYSTEM_INFO_DOMAIN).data()) == 0; // 1 packet, quiet output, 1 sec timeout #endif std::cout << "Pinging prusa3d.com was " << (is_internet ? "" : "NOT ") << "successful." << std::endl; @@ -336,11 +339,20 @@ static std::string generate_system_info_json() std::string unique_id = get_unique_id(); // Get system language. - std::string sys_language = "Unknown"; - const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); - if (lang_system != wxLANGUAGE_UNKNOWN) - sys_language = wxLocale::GetLanguageInfo(lang_system)->CanonicalName.ToUTF8().data(); - + std::string sys_language = "Unknown"; // important to init, see the __APPLE__ block. + #ifndef __APPLE__ + // Following apparently does not work on macOS. + const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); + if (lang_system != wxLANGUAGE_UNKNOWN) + sys_language = wxLocale::GetLanguageInfo(lang_system)->CanonicalName.ToUTF8().data(); + #else // __APPLE__ + CFLocaleRef cflocale = CFLocaleCopyCurrent(); + CFStringRef value = (CFStringRef)CFLocaleGetValue(cflocale, kCFLocaleLanguageCode); + char temp[10] = ""; + CFStringGetCString(value, temp, 10, kCFStringEncodingUTF8); + sys_language = temp; + CFRelease(cflocale); + #endif // Build a property tree with all the information. namespace pt = boost::property_tree; From 1afa18d7198aad23b1ee0dcf0dd35ba4b6e6fa41 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 12 Oct 2021 15:34:18 +0200 Subject: [PATCH 31/42] SendSystemInfo: Use GET instead of ping to check internet connection --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 22 ++++++++++------------ src/slic3r/Utils/PresetUpdater.cpp | 4 ---- src/slic3r/Utils/PresetUpdater.hpp | 2 ++ 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 72316215a..0b63d52de 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/Utils/Http.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" #include "GUI_App.hpp" #include "GUI_Utils.hpp" @@ -151,18 +152,15 @@ static bool should_dialog_be_shown() if (! new_version) return false; - std::cout << "Sending system info was not confirmed/declined in this version yet.\n" - "Pinging prusa3d.com to see if it can be offered now." << std::endl; - bool is_internet = - #ifdef _WIN32 - std::system((std::string("ping /n 1 /w 1 ") + SEND_SYSTEM_INFO_DOMAIN).data()) == 0; // 1 packet, 1 sec timeout - #elif __APPLE__ - std::system((std::string("ping -c 1 -q -t 1 ") + SEND_SYSTEM_INFO_DOMAIN).data()) == 0; // 1 packet, quiet output, 1 sec timeout - #else // Linux/BSD - std::system((std::string("ping -c 1 -q -w 1 ") + SEND_SYSTEM_INFO_DOMAIN).data()) == 0; // 1 packet, quiet output, 1 sec timeout - #endif - std::cout << "Pinging prusa3d.com was " << (is_internet ? "" : "NOT ") << "successful." << std::endl; - + // We'll misuse the version check to check internet connection here. + bool is_internet = false; + Http::get(wxGetApp().app_config->version_check_url()) + .size_limit(SLIC3R_VERSION_BODY_MAX) + .timeout_max(2) + .on_complete([&](std::string, unsigned) { + is_internet = true; + }) + .perform_sync(); return is_internet; } diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 13c631c9c..76ecc76d9 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -46,10 +46,6 @@ using Slic3r::GUI::Config::SnapshotDB; namespace Slic3r { -enum { - SLIC3R_VERSION_BODY_MAX = 256, -}; - static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".download"; diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index b7937c574..d0d18a7d8 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -13,6 +13,8 @@ class AppConfig; class PresetBundle; class Semver; +const int SLIC3R_VERSION_BODY_MAX = 256; + class PresetUpdater { public: From 99bf3d0a25763caf0c64b1def8f5b0e6de24fe58 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 12 Oct 2021 15:34:41 +0200 Subject: [PATCH 32/42] SendSystemInfo: Show also in alphas, fixed alpha detection --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 0b63d52de..3b9c529a1 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -142,8 +142,12 @@ static bool should_dialog_be_shown() if (! last_sent_version.empty()) semver_last_sent = Semver(last_sent_version); - // if (semver_current.prerelease() && std::string(semver_current.prerelease()) == "alpha") - // return false; // Don't show in alphas. + // set whether to show in alpha builds, or only betas/rcs/finals: + const bool show_in_alphas = true; + + if (! show_in_alphas && semver_current.prerelease() + && std::string(semver_current.prerelease()).find("alpha") != std::string::npos) + return false; // New version means current > last, but they must differ in more than just patch. bool new_version = ((semver_current.maj() > semver_last_sent.maj()) From e30e7ffdef7c24fc336f017b7318eb467fe15b06 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 15 Oct 2021 15:16:39 +0200 Subject: [PATCH 33/42] SendSystemInfo: improved error handling --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 3b9c529a1..106617e76 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -185,9 +185,10 @@ static std::map get_cpu_info_from_registry() std::map out; int idx = -1; - constexpr DWORD bufsize_ = 200; - DWORD bufsize = bufsize_; + constexpr DWORD bufsize_ = 500; + DWORD bufsize = bufsize_-1; // Ensure a terminating zero. char buf[bufsize_] = ""; + memset(buf, 0, bufsize_); const std::string reg_dir = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\"; std::string reg_path = reg_dir; @@ -209,7 +210,7 @@ static std::map get_cpu_info_from_registry() } ++idx; reg_path = reg_dir + std::to_string(idx) + "\\"; - bufsize = bufsize_; + bufsize = bufsize_-1; } return out; } @@ -217,7 +218,7 @@ static std::map get_cpu_info_from_registry() static std::map parse_lscpu_etc(const std::string& name, char delimiter) { std::map out; - constexpr size_t max_len = 100; + constexpr size_t max_len = 1000; char cline[max_len] = ""; FILE* fp = popen(name.data(), "r"); if (fp != NULL) { @@ -283,10 +284,12 @@ static std::string get_unique_id() char buf[buf_size] = ""; memset(&buf, 0, sizeof(buf)); io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/"); - CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); - IOObjectRelease(ioRegistryRoot); - CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman); - CFRelease(uuidCf); + if (ioRegistryRoot != MACH_PORT_NULL) { + CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); + IOObjectRelease(ioRegistryRoot); + CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman); + CFRelease(uuidCf); + } // Now convert the string to std::vector. for (char* c = buf; *c != 0; ++c) unique.emplace_back((unsigned char)(*c)); From c313e6793a4d837960949fdd26a2511c0066c749 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 18 Oct 2021 15:46:13 +0200 Subject: [PATCH 34/42] Follow-up to 1ca24f0bd03d7f97d576bfac43022733459a9c92 Fixed visualization of G-code in G-code viewer after 07e7e115901c80f282b06ea6b86bc56b28e1a02b The line end positions were not extracted correctly from G-code imported into a stand-alone G-code viewer. --- src/libslic3r/GCodeReader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index 7b106463a..aa04e69f2 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -152,7 +152,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine auto it_end = it; for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) if (*it_end == '\n') - line_end_callback((it_end - buffer.begin()) + 1); + line_end_callback(file_pos + (it_end - buffer.begin()) + 1); // End of line is indicated also if end of file was reached. eol |= eof && it_end == it_bufend; if (eol) { @@ -173,7 +173,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine if (it != it_bufend && *it == '\r') ++ it; if (it != it_bufend && *it == '\n') { - line_end_callback((it - buffer.begin()) + 1); + line_end_callback(file_pos + (it - buffer.begin()) + 1); ++ it; } } From d3c38fc6039c83a9208d6239d6336ddbe14dab5f Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 18 Oct 2021 15:47:32 +0200 Subject: [PATCH 35/42] Fix of crashing Preferences in Gcode Viewer --- src/slic3r/GUI/Preferences.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index ee80131e0..3748e2251 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -452,8 +452,10 @@ void PreferencesDialog::build(size_t selected_tab) 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) { + 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(); From 80ccb77b00d39752301692196bdc2b908b6ffbba Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 18 Oct 2021 16:01:32 +0200 Subject: [PATCH 36/42] live preview in simplification --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 35 ++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp | 3 ++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index c09d67317..88ff8dc9e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -143,8 +143,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::Separator(); if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) { - m_is_valid_result = false; m_configuration.use_count = !m_configuration.use_count; + live_preview(); } ImGui::SameLine(); m_imgui->disabled_begin(m_configuration.use_count); @@ -160,7 +160,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::SetNextItemWidth(m_gui_cfg->input_width); static int reduction = 2; if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) { - m_is_valid_result = false; if (reduction < 0) reduction = 0; if (reduction > 4) reduction = 4; switch (reduction) { @@ -170,12 +169,13 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi case 3: m_configuration.max_error = 0.5f; break; case 4: m_configuration.max_error = 1.f; break; } + live_preview(); } m_imgui->disabled_end(); // !use_count if (ImGui::RadioButton("##use_count", m_configuration.use_count)) { - m_is_valid_result = false; m_configuration.use_count = !m_configuration.use_count; + live_preview(); } ImGui::SameLine(); @@ -192,13 +192,14 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::SetNextItemWidth(m_gui_cfg->input_width); const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%": ((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%"); + if (ImGui::SliderFloat("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) { - m_is_valid_result = false; if (m_configuration.decimate_ratio < 0.f) m_configuration.decimate_ratio = 0.01f; if (m_configuration.decimate_ratio > 100.f) m_configuration.decimate_ratio = 100.f; m_configuration.fix_count_by_ratio(triangle_count); + live_preview(); } ImGui::NewLine(); @@ -221,12 +222,16 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } } ImGui::SameLine(m_gui_cfg->bottom_left_width); + + m_imgui->disabled_begin(m_configuration.live_preview || m_is_valid_result); if (m_imgui->button(_L("Preview"))) { m_state = State::preview; // simplify but not apply on mesh process(); } + m_imgui->disabled_end(); ImGui::SameLine(); + if (m_imgui->button(_L("Apply"))) { if (!m_is_valid_result) { m_state = State::close_on_end; @@ -238,6 +243,11 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi close(); } } + ImGui::SameLine(); + if(ImGui::Checkbox(_L("Live").c_str(), &m_configuration.live_preview)) { + if (m_configuration.live_preview && !m_is_valid_result) + live_preview(); + } } else { m_imgui->disabled_begin(m_state == State::canceling); if (m_imgui->button(_L("Cancel"))) m_state = State::canceling; @@ -280,6 +290,23 @@ void GLGizmoSimplify::close() { gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); } +void GLGizmoSimplify::live_preview() { + m_is_valid_result = false; + if (!m_configuration.live_preview) return; + + if (m_state != State::settings) { + if (m_state == State::canceling) return; + + // wait until cancel + if (m_worker.joinable()) { + m_state = State::canceling; + m_worker.join(); + } + } + + m_state = State::preview; + process(); +} void GLGizmoSimplify::process() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index d624e3351..3ee99a4d4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -39,6 +39,7 @@ protected: private: void after_apply(); void close(); + void live_preview(); void process(); void set_its(indexed_triangle_set &its); void create_gui_cfg(); @@ -73,6 +74,8 @@ private: struct Configuration { + bool live_preview = false; + bool use_count = false; // minimal triangle count float decimate_ratio = 50.f; // in percent From c12eff19d8d9566e987fa048f938c3a76a37d0bd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 18 Oct 2021 16:24:13 +0200 Subject: [PATCH 37/42] Fixed a possible deadlock: The thread counter should be modified under a mutex, atomic is not enough here --- src/libslic3r/Thread.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 106da4a78..4e7bd073a 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -208,7 +208,7 @@ void name_tbb_thread_pool_threads_set_locale() nthreads = 1; #endif - std::atomic nthreads_running(0); + size_t nthreads_running(0); std::condition_variable cv; std::mutex cv_m; auto master_thread_id = std::this_thread::get_id(); @@ -216,13 +216,13 @@ void name_tbb_thread_pool_threads_set_locale() tbb::blocked_range(0, nthreads, 1), [&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range &range) { assert(range.begin() + 1 == range.end()); - if (nthreads_running.fetch_add(1) + 1 == nthreads) { + if (std::unique_lock lk(cv_m); ++nthreads_running == nthreads) { + lk.unlock(); // All threads are spinning. // Wake them up. cv.notify_all(); } else { // Wait for the last thread to wake the others. - std::unique_lock lk(cv_m); cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;}); } auto thread_id = std::this_thread::get_id(); From a9bd989edaefae0573008dcb7946a040503fafda Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 18 Oct 2021 16:47:25 +0200 Subject: [PATCH 38/42] Add [esc] to interupt preview in simplify --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 31 ++++++++++------------- src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp | 3 +-- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 4 +++ 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 88ff8dc9e..70e8b042a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -41,6 +41,14 @@ GLGizmoSimplify::~GLGizmoSimplify() { free_gpu(); } +bool GLGizmoSimplify::on_esc_key_down() { + if (m_state == State::settings || m_state == State::canceling) + return false; + + m_state = State::canceling; + return true; +} + bool GLGizmoSimplify::on_init() { //m_grabbers.emplace_back(); @@ -207,7 +215,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count); m_imgui->disabled_end(); // use_count - if (ImGui::Checkbox(_L("Show wireframe").c_str(), &m_show_wireframe)) { + if (ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe)) { if (m_show_wireframe) init_wireframe(); else free_gpu(); } @@ -221,17 +229,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi close(); } } - ImGui::SameLine(m_gui_cfg->bottom_left_width); - - m_imgui->disabled_begin(m_configuration.live_preview || m_is_valid_result); - if (m_imgui->button(_L("Preview"))) { - m_state = State::preview; - // simplify but not apply on mesh - process(); - } - m_imgui->disabled_end(); ImGui::SameLine(); - if (m_imgui->button(_L("Apply"))) { if (!m_is_valid_result) { m_state = State::close_on_end; @@ -243,11 +241,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi close(); } } - ImGui::SameLine(); - if(ImGui::Checkbox(_L("Live").c_str(), &m_configuration.live_preview)) { - if (m_configuration.live_preview && !m_is_valid_result) - live_preview(); - } } else { m_imgui->disabled_begin(m_state == State::canceling); if (m_imgui->button(_L("Cancel"))) m_state = State::canceling; @@ -292,9 +285,8 @@ void GLGizmoSimplify::close() { void GLGizmoSimplify::live_preview() { m_is_valid_result = false; - if (!m_configuration.live_preview) return; - if (m_state != State::settings) { + // already canceling process if (m_state == State::canceling) return; // wait until cancel @@ -435,6 +427,9 @@ void GLGizmoSimplify::create_gui_cfg() { cfg.input_width = cfg.bottom_left_width * 1.5; cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2; cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5; + + float checkbox_width = ImGui::GetFrameHeight(); + m_gui_cfg = cfg; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 3ee99a4d4..0681b5f18 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -24,6 +24,7 @@ class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GL public: GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); virtual ~GLGizmoSimplify(); + bool on_esc_key_down(); protected: virtual bool on_init() override; virtual std::string on_get_name() const override; @@ -74,8 +75,6 @@ private: struct Configuration { - bool live_preview = false; - bool use_count = false; // minimal triangle count float decimate_ratio = 50.f; // in percent diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 08a94a97d..0c8f161a9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -924,6 +924,10 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; } default: { break; } } + } else if (m_current == Simplify && keyCode == WXK_ESCAPE) { + GLGizmoSimplify *simplify = dynamic_cast(get_current()); + if (simplify != nullptr) + processed = simplify->on_esc_key_down(); } } From 88f9a387e3c395a523113e2c85ae5133fbbe8d80 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 18 Oct 2021 19:05:52 +0200 Subject: [PATCH 39/42] Do not disapeared apply button --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 66 ++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp | 8 +-- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 70e8b042a..68d6b2cad 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -24,15 +24,16 @@ GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent, , m_obj_index(0) , m_need_reload(false) , m_show_wireframe(false) - + // translation for GUI size , tr_mesh_name(_u8L("Mesh name")) , tr_triangles(_u8L("Triangles")) , tr_preview(_u8L("Preview")) , tr_detail_level(_u8L("Detail level")) , tr_decimate_ratio(_u8L("Decimate ratio")) - + // for wireframe , m_wireframe_VBO_id(0) , m_wireframe_IBO_id(0) + , m_wireframe_IBO_size(0) {} GLGizmoSimplify::~GLGizmoSimplify() { @@ -49,22 +50,11 @@ bool GLGizmoSimplify::on_esc_key_down() { return true; } -bool GLGizmoSimplify::on_init() -{ - //m_grabbers.emplace_back(); - //m_shortcut_key = WXK_CONTROL_C; - return true; -} - std::string GLGizmoSimplify::on_get_name() const { return _u8L("Simplify"); } -void GLGizmoSimplify::on_render() { } - -void GLGizmoSimplify::on_render_for_picking() {} - void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { create_gui_cfg(); @@ -220,32 +210,43 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi else free_gpu(); } - if (m_state == State::settings) { - if (m_imgui->button(_L("Cancel"))) { - if (m_original_its.has_value()) { + bool is_canceling = m_state == State::canceling; + m_imgui->disabled_begin(is_canceling); + if (m_imgui->button(_L("Cancel"))) { + if (m_state == State::settings) { + if (m_original_its.has_value()) { set_its(*m_original_its); m_state = State::close_on_end; } else { close(); } + } else { + m_state = State::canceling; } - ImGui::SameLine(); - if (m_imgui->button(_L("Apply"))) { - if (!m_is_valid_result) { - m_state = State::close_on_end; - process(); - } else if (m_exist_preview) { - // use preview and close - after_apply(); - } else { // no changes made - close(); - } - } - } else { - m_imgui->disabled_begin(m_state == State::canceling); - if (m_imgui->button(_L("Cancel"))) m_state = State::canceling; - m_imgui->disabled_end(); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_canceling) + ImGui::SetTooltip(_L("Operation already canceling. Please wait few seconds.").c_str()); + m_imgui->disabled_end(); // state canceling + ImGui::SameLine(); + + bool is_processing = m_state != State::settings; + m_imgui->disabled_begin(is_processing); + if (m_imgui->button(_L("Apply"))) { + if (!m_is_valid_result) { + m_state = State::close_on_end; + process(); + } else if (m_exist_preview) { + // use preview and close + after_apply(); + } else { // no changes made + close(); + } + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_processing) + ImGui::SetTooltip(_L("Can't apply when proccess preview.").c_str()); + m_imgui->disabled_end(); // state !settings + + // draw progress bar + if (is_processing) { // apply or preview ImGui::SameLine(m_gui_cfg->bottom_left_width); // draw progress bar char buf[32]; @@ -254,6 +255,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } m_imgui->end(); + // refresh view when needed if (m_need_reload) { m_need_reload = false; bool close_on_end = (m_state == State::close_on_end); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 0681b5f18..b609c5cdd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -26,15 +26,17 @@ public: virtual ~GLGizmoSimplify(); bool on_esc_key_down(); protected: - virtual bool on_init() override; virtual std::string on_get_name() const override; - virtual void on_render() override; - virtual void on_render_for_picking() override; virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual bool on_is_activable() const override; virtual bool on_is_selectable() const override { return false; } virtual void on_set_state() override; + // must implement + virtual bool on_init() override { return true;}; + virtual void on_render() override{}; + virtual void on_render_for_picking() override{}; + // GLGizmoPainterBase virtual void render_painter_gizmo() const override{ render_wireframe(); } private: From 76cbb7c17ebeddbb7926635c582b41cd33dc9556 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 18 Oct 2021 20:07:13 +0200 Subject: [PATCH 40/42] Fix ../src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp:433:11: warning: unused variable 'checkbox_width' [-Wunused-variable] ../src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp:227:27: warning: format string is not a string literal (potentially insecure) [-Wformat-security] ../src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp:245:27: warning: format string is not a string literal (potentially insecure) [-Wformat-security] --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 68d6b2cad..c43f215d9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -224,7 +224,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_state = State::canceling; } } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_canceling) - ImGui::SetTooltip(_L("Operation already canceling. Please wait few seconds.").c_str()); + ImGui::SetTooltip("%s", _u8L("Operation already canceling. Please wait few seconds.").c_str()); m_imgui->disabled_end(); // state canceling ImGui::SameLine(); @@ -242,7 +242,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi close(); } } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_processing) - ImGui::SetTooltip(_L("Can't apply when proccess preview.").c_str()); + ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str()); m_imgui->disabled_end(); // state !settings // draw progress bar @@ -429,8 +429,6 @@ void GLGizmoSimplify::create_gui_cfg() { cfg.input_width = cfg.bottom_left_width * 1.5; cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2; cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5; - - float checkbox_width = ImGui::GetFrameHeight(); m_gui_cfg = cfg; } From 5e735a59d02a17bab3bac8a06d9f64e99cc8a399 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 18 Oct 2021 16:59:32 +0200 Subject: [PATCH 41/42] Fixed planning of support interface layers with rafts and larger Z gap for supports than for the raft. --- src/libslic3r/SupportMaterial.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 1322b4071..a668a385b 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -1703,15 +1703,18 @@ static inline std::pair 1 ? slicing_params.raft_interface_top_z + support_layer_height_min + EPSILON : slicing_params.first_print_layer_height - EPSILON; - if (print_z < min_print_z) { + if (print_z < slicing_params.first_print_layer_height - EPSILON) { // This contact layer is below the first layer height, therefore not printable. Don't support this surface. return std::pair(nullptr, nullptr); - } else if (print_z < slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - print_z = slicing_params.first_print_layer_height; - bottom_z = 0; - height = slicing_params.first_print_layer_height; + } + const bool has_raft = slicing_params.raft_layers() > 1; + const coordf_t min_print_z = has_raft ? slicing_params.raft_contact_top_z : slicing_params.first_print_layer_height; + if (print_z < min_print_z + support_layer_height_min) { + // Align the layer with the 1st layer height or the raft contact layer. + // With raft active, any contact layer below the raft_contact_top_z will be brought to raft_contact_top_z to extend the raft area. + print_z = min_print_z; + bottom_z = has_raft ? slicing_params.raft_interface_top_z : 0; + height = has_raft ? slicing_params.contact_raft_layer_height : min_print_z; } else { // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and // its height will be set adaptively later on. @@ -1727,9 +1730,9 @@ static inline std::pair= min_print_z) { // Not below the first layer height means this layer is printable. - if (print_z < slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - bridging_print_z = slicing_params.first_print_layer_height; + if (print_z < min_print_z + support_layer_height_min) { + // Align the layer with the 1st layer height or the raft contact layer. + bridging_print_z = min_print_z; } if (bridging_print_z < print_z - EPSILON) { // Allocate the new layer. @@ -3108,7 +3111,7 @@ std::pair Date: Tue, 19 Oct 2021 09:19:47 +0200 Subject: [PATCH 42/42] Fixed visualization of the "sinking contours" for complex objects. --- src/slic3r/GUI/3DScene.cpp | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 27eb69363..98665177f 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -319,19 +319,7 @@ void GLVolume::SinkingContours::update() MeshSlicingParams slicing_params; slicing_params.trafo = m_parent.world_matrix(); Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); - for (Polygon& polygon : polygons) { - if (polygon.is_clockwise()) - polygon.reverse(); - Polygons outer_polys = offset(polygon, float(scale_(HalfWidth))); - assert(outer_polys.size() == 1); - if (outer_polys.empty()) - // no outer contour, skip - continue; - - ExPolygon expoly(std::move(outer_polys.front())); - expoly.holes = offset(polygon, -float(scale_(HalfWidth))); - polygons_reverse(expoly.holes); - + for (ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { GUI::GLModel::InitializationData::Entity entity; entity.type = GUI::GLModel::PrimitiveType::Triangles; const std::vector triangulation = triangulate_expolygon_3d(expoly);