diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 1e94e0f482..3b01673605 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -32,18 +32,12 @@ use Moo 1.003001; use Slic3r::XS; # import all symbols (constants etc.) before they get parsed use Slic3r::Config; -use Slic3r::ExPolygon; -use Slic3r::ExtrusionLoop; -use Slic3r::ExtrusionPath; use Slic3r::GCode::Reader; -use Slic3r::Layer; use Slic3r::Line; use Slic3r::Model; use Slic3r::Point; use Slic3r::Polygon; use Slic3r::Polyline; -use Slic3r::Print::Object; -use Slic3r::Surface; our $build = eval "use Slic3r::Build; 1"; # Scaling between the float and integer coordinates. diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm deleted file mode 100644 index 1b55849ebf..0000000000 --- a/lib/Slic3r/ExPolygon.pm +++ /dev/null @@ -1,12 +0,0 @@ -package Slic3r::ExPolygon; -use strict; -use warnings; - -# an ExPolygon is a polygon with holes - -sub bounding_box { - my $self = shift; - return $self->contour->bounding_box; -} - -1; diff --git a/lib/Slic3r/ExtrusionLoop.pm b/lib/Slic3r/ExtrusionLoop.pm deleted file mode 100644 index f0a8577858..0000000000 --- a/lib/Slic3r/ExtrusionLoop.pm +++ /dev/null @@ -1,12 +0,0 @@ -package Slic3r::ExtrusionLoop; -use strict; -use warnings; - -use parent qw(Exporter); - -our @EXPORT_OK = qw(EXTRL_ROLE_DEFAULT - EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER EXTRL_ROLE_SKIRT); -our %EXPORT_TAGS = (roles => \@EXPORT_OK); - - -1; diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm deleted file mode 100644 index 3e9e6b8c72..0000000000 --- a/lib/Slic3r/ExtrusionPath.pm +++ /dev/null @@ -1,13 +0,0 @@ -package Slic3r::ExtrusionPath; -use strict; -use warnings; - -use parent qw(Exporter); - -our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER - EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_GAPFILL EXTR_ROLE_BRIDGE - EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE - EXTR_ROLE_NONE); -our %EXPORT_TAGS = (roles => \@EXPORT_OK); - -1; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 2e4f5a0972..6a2161d288 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -34,21 +34,4 @@ sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } -# 2D -sub bounding_box { - my ($points) = @_; - - my @x = map $_->x, @$points; - my @y = map $_->y, @$points; #,, - my @bb = (undef, undef, undef, undef); - for (0..$#x) { - $bb[X1] = $x[$_] if !defined $bb[X1] || $x[$_] < $bb[X1]; - $bb[X2] = $x[$_] if !defined $bb[X2] || $x[$_] > $bb[X2]; - $bb[Y1] = $y[$_] if !defined $bb[Y1] || $y[$_] < $bb[Y1]; - $bb[Y2] = $y[$_] if !defined $bb[Y2] || $y[$_] > $bb[Y2]; - } - - return @bb[X1,Y1,X2,Y2]; -} - 1; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm deleted file mode 100644 index a4146f169a..0000000000 --- a/lib/Slic3r/Layer.pm +++ /dev/null @@ -1,18 +0,0 @@ -# Extends the C++ class Slic3r::Layer. - -package Slic3r::Layer; -use strict; -use warnings; - -# the following two were previously generated by Moo -sub print { - my $self = shift; - return $self->object->print; -} - -sub config { - my $self = shift; - return $self->object->config; -} - -1; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm deleted file mode 100644 index b4e53245ae..0000000000 --- a/lib/Slic3r/Print/Object.pm +++ /dev/null @@ -1,14 +0,0 @@ -package Slic3r::Print::Object; -# extends c++ class Slic3r::PrintObject (Print.xsp) -use strict; -use warnings; - -use List::Util qw(min max sum first); -use Slic3r::Surface ':types'; - -sub layers { - my $self = shift; - return [ map $self->get_layer($_), 0..($self->layer_count - 1) ]; -} - -1; diff --git a/lib/Slic3r/Surface.pm b/lib/Slic3r/Surface.pm deleted file mode 100644 index 0c5bb5d233..0000000000 --- a/lib/Slic3r/Surface.pm +++ /dev/null @@ -1,15 +0,0 @@ -package Slic3r::Surface; -use strict; -use warnings; - -require Exporter; -our @ISA = qw(Exporter); -our @EXPORT_OK = qw(S_TYPE_TOP S_TYPE_BOTTOM S_TYPE_BOTTOMBRIDGE S_TYPE_INTERNAL S_TYPE_INTERNALSOLID S_TYPE_INTERNALBRIDGE S_TYPE_INTERNALVOID); -our %EXPORT_TAGS = (types => \@EXPORT_OK); - -sub p { - my $self = shift; - return @{$self->polygons}; -} - -1; diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 2ca6438821..518b4b7c35 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -225,7 +225,7 @@ int PointInPolygon(const IntPoint &pt, const Path &path) if (ipNext.x() > pt.x()) result = 1 - result; else { - double d = (double)(ip.x() - pt.x()) * (ipNext.y() - pt.y()) - (double)(ipNext.x() - pt.x()) * (ip.y() - pt.y()); + auto d = CrossProductType(ip.x() - pt.x()) * CrossProductType(ipNext.y() - pt.y()) - CrossProductType(ipNext.x() - pt.x()) * CrossProductType(ip.y() - pt.y()); if (!d) return -1; if ((d > 0) == (ipNext.y() > ip.y())) result = 1 - result; } @@ -233,7 +233,7 @@ int PointInPolygon(const IntPoint &pt, const Path &path) { if (ipNext.x() > pt.x()) { - double d = (double)(ip.x() - pt.x()) * (ipNext.y() - pt.y()) - (double)(ipNext.x() - pt.x()) * (ip.y() - pt.y()); + auto d = CrossProductType(ip.x() - pt.x()) * CrossProductType(ipNext.y() - pt.y()) - CrossProductType(ipNext.x() - pt.x()) * CrossProductType(ip.y() - pt.y()); if (!d) return -1; if ((d > 0) == (ipNext.y() > ip.y())) result = 1 - result; } @@ -246,7 +246,7 @@ int PointInPolygon(const IntPoint &pt, const Path &path) //------------------------------------------------------------------------------ // Called by Poly2ContainsPoly1() -int PointInPolygon (const IntPoint &pt, OutPt *op) +int PointInPolygon(const IntPoint &pt, OutPt *op) { //returns 0 if false, +1 if true, -1 if pt ON polygon boundary int result = 0; @@ -265,7 +265,7 @@ int PointInPolygon (const IntPoint &pt, OutPt *op) if (op->Next->Pt.x() > pt.x()) result = 1 - result; else { - double d = (double)(op->Pt.x() - pt.x()) * (op->Next->Pt.y() - pt.y()) - (double)(op->Next->Pt.x() - pt.x()) * (op->Pt.y() - pt.y()); + auto d = CrossProductType(op->Pt.x() - pt.x()) * CrossProductType(op->Next->Pt.y() - pt.y()) - CrossProductType(op->Next->Pt.x() - pt.x()) * CrossProductType(op->Pt.y() - pt.y()); if (!d) return -1; if ((d > 0) == (op->Next->Pt.y() > op->Pt.y())) result = 1 - result; } @@ -273,7 +273,7 @@ int PointInPolygon (const IntPoint &pt, OutPt *op) { if (op->Next->Pt.x() > pt.x()) { - double d = (double)(op->Pt.x() - pt.x()) * (op->Next->Pt.y() - pt.y()) - (double)(op->Next->Pt.x() - pt.x()) * (op->Pt.y() - pt.y()); + auto d = CrossProductType(op->Pt.x() - pt.x()) * CrossProductType(op->Next->Pt.y() - pt.y()) - CrossProductType(op->Next->Pt.x() - pt.x()) * CrossProductType(op->Pt.y() - pt.y()); if (!d) return -1; if ((d > 0) == (op->Next->Pt.y() > op->Pt.y())) result = 1 - result; } diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index b1dae3c248..641476c8b0 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -85,9 +85,11 @@ enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; // Point coordinate type #ifdef CLIPPERLIB_INT32 // Coordinates and their differences (vectors of the coordinates) have to fit int32_t. - typedef int32_t cInt; + using cInt = int32_t; + using CrossProductType = int64_t; #else - typedef int64_t cInt; + using cInt = int64_t; + using CrossProductType = double; // Maximum cInt value to allow a cross product calculation using 32bit expressions. static constexpr cInt const loRange = 0x3FFFFFFF; // 0x3FFFFFFF = 1 073 741 823 // Maximum allowed cInt value. diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 04dede5469..04a1042d8c 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -274,7 +274,7 @@ std::vector SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_ Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments); coord_t d = (right_point - left_point).cast().norm(); Point middle = (left_point + right_point) / 2; - Point x_axis_dir = Point(right_point - left_point).rotate_90_degree_ccw(); + Point x_axis_dir = perp(Point(right_point - left_point)); coord_t x_axis_length = x_axis_dir.cast().norm(); const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge. diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp index cfdbfecdaf..efda323837 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp @@ -10,6 +10,11 @@ #include "SkeletalTrapezoidationEdge.hpp" #include "SkeletalTrapezoidationJoint.hpp" +namespace Slic3r +{ +class Line; +}; + namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp index 75e4d53389..de5251639b 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp @@ -268,13 +268,13 @@ void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extr { for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) { ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path); - Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON)).paths); + Slic3r::append(dst, PerimeterGenerator::thick_polyline_to_multi_path(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON)).paths); } } void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow) { ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion); - Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON)).paths); + Slic3r::append(dst, PerimeterGenerator::thick_polyline_to_multi_path(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON)).paths); } } // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp index 82bd79523f..069e1f5ad5 100644 --- a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp +++ b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp @@ -165,7 +165,7 @@ std::vector VoronoiUtils::discretizeParabola(const Point& p, const Segmen Line(a, b).distance_to_infinite_squared(p, &pxx); const Point ppxx = pxx - p; const coord_t d = ppxx.cast().norm(); - const PointMatrix rot = PointMatrix(ppxx.rotate_90_degree_ccw()); + const PointMatrix rot = PointMatrix(perp(ppxx)); if (d == 0) { diff --git a/src/libslic3r/Arachne/utils/linearAlg2D.hpp b/src/libslic3r/Arachne/utils/linearAlg2D.hpp index 797bae0b97..304984b1ca 100644 --- a/src/libslic3r/Arachne/utils/linearAlg2D.hpp +++ b/src/libslic3r/Arachne/utils/linearAlg2D.hpp @@ -38,15 +38,11 @@ inline static bool isInsideCorner(const Point &a, const Point &b, const Point &c return (p0.cast() * int64_t(len) / _len).cast(); }; - auto rotate_90_degree_ccw = [](const Vec2d &p) -> Vec2d { - return {-p.y(), p.x()}; - }; - constexpr coord_t normal_length = 10000; //Create a normal vector of reasonable length in order to reduce rounding error. const Point ba = normal(a - b, normal_length); const Point bc = normal(c - b, normal_length); const Vec2d bq = query_point.cast() - b.cast(); - const Vec2d perpendicular = rotate_90_degree_ccw(bq); //The query projects to this perpendicular to coordinate 0. + const Vec2d perpendicular = perp(bq); //The query projects to this perpendicular to coordinate 0. const double project_a_perpendicular = ba.cast().dot(perpendicular); //Project vertex A on the perpendicular line. const double project_c_perpendicular = bc.cast().dot(perpendicular); //Project vertex C on the perpendicular line. diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 4818ff9eee..6c16d08b78 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -187,6 +187,8 @@ public: friend BoundingBox get_extents_rotated(const Points &points, double angle); }; +using BoundingBoxes = std::vector; + class BoundingBox3 : public BoundingBox3Base { public: @@ -242,6 +244,39 @@ auto cast(const BoundingBox3Base &b) b.max.template cast()}; } +// Distance of a point to a bounding box. Zero inside and on the boundary, positive outside. +inline double bbox_point_distance(const BoundingBox &bbox, const Point &pt) +{ + if (pt.x() < bbox.min.x()) + return pt.y() < bbox.min.y() ? (bbox.min - pt).cast().norm() : + pt.y() > bbox.max.y() ? (Point(bbox.min.x(), bbox.max.y()) - pt).cast().norm() : + double(bbox.min.x() - pt.x()); + else if (pt.x() > bbox.max.x()) + return pt.y() < bbox.min.y() ? (Point(bbox.max.x(), bbox.min.y()) - pt).cast().norm() : + pt.y() > bbox.max.y() ? (bbox.max - pt).cast().norm() : + double(pt.x() - bbox.max.x()); + else + return pt.y() < bbox.min.y() ? bbox.min.y() - pt.y() : + pt.y() > bbox.max.y() ? pt.y() - bbox.max.y() : + coord_t(0); +} + +inline double bbox_point_distance_squared(const BoundingBox &bbox, const Point &pt) +{ + if (pt.x() < bbox.min.x()) + return pt.y() < bbox.min.y() ? (bbox.min - pt).cast().squaredNorm() : + pt.y() > bbox.max.y() ? (Point(bbox.min.x(), bbox.max.y()) - pt).cast().squaredNorm() : + Slic3r::sqr(double(bbox.min.x() - pt.x())); + else if (pt.x() > bbox.max.x()) + return pt.y() < bbox.min.y() ? (Point(bbox.max.x(), bbox.min.y()) - pt).cast().squaredNorm() : + pt.y() > bbox.max.y() ? (bbox.max - pt).cast().squaredNorm() : + Slic3r::sqr(pt.x() - bbox.max.x()); + else + return Slic3r::sqr(pt.y() < bbox.min.y() ? bbox.min.y() - pt.y() : + pt.y() > bbox.max.y() ? pt.y() - bbox.max.y() : + coord_t(0)); +} + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 62d8867851..061cf1423c 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -53,7 +53,7 @@ static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &pr { ExPolygons ex_polygons; for (LayerRegion *region : print_object.layers().front()->regions()) - Slic3r::append(ex_polygons, closing_ex(region->slices.surfaces, float(SCALED_EPSILON))); + Slic3r::append(ex_polygons, closing_ex(region->slices().surfaces, float(SCALED_EPSILON))); return ex_polygons; } diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 650cfca156..ea50157986 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -48,6 +48,100 @@ err: namespace ClipperUtils { Points EmptyPathsProvider::s_empty_points; Points SinglePathProvider::s_end; + + // Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon. + // Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by one + // with a set of polygons covering the whole layer below. + template + inline void clip_clipper_polygon_with_subject_bbox_templ(const std::vector &src, const BoundingBox &bbox, std::vector &out) + { + out.clear(); + const size_t cnt = src.size(); + if (cnt < 3) + return; + + enum class Side { + Left = 1, + Right = 2, + Top = 4, + Bottom = 8 + }; + + auto sides = [bbox](const PointType &p) { + return int(p.x() < bbox.min.x()) * int(Side::Left) + + int(p.x() > bbox.max.x()) * int(Side::Right) + + int(p.y() < bbox.min.y()) * int(Side::Bottom) + + int(p.y() > bbox.max.y()) * int(Side::Top); + }; + + int sides_prev = sides(src.back()); + int sides_this = sides(src.front()); + const size_t last = cnt - 1; + for (size_t i = 0; i < last; ++ i) { + int sides_next = sides(src[i + 1]); + if (// This point is inside. Take it. + sides_this == 0 || + // Either this point is outside and previous or next is inside, or + // the edge possibly cuts corner of the bounding box. + (sides_prev & sides_this & sides_next) == 0) { + out.emplace_back(src[i]); + sides_prev = sides_this; + } else { + // All the three points (this, prev, next) are outside at the same side. + // Ignore this point. + } + sides_this = sides_next; + } + + // Never produce just a single point output polygon. + if (! out.empty()) + if (int sides_next = sides(out.front()); + // The last point is inside. Take it. + sides_this == 0 || + // Either this point is outside and previous or next is inside, or + // the edge possibly cuts corner of the bounding box. + (sides_prev & sides_this & sides_next) == 0) + out.emplace_back(src.back()); + } + + void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out) + { clip_clipper_polygon_with_subject_bbox_templ(src, bbox, out); } + void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out) + { clip_clipper_polygon_with_subject_bbox_templ(src, bbox, out); } + + template + [[nodiscard]] std::vector clip_clipper_polygon_with_subject_bbox_templ(const std::vector &src, const BoundingBox &bbox) + { + std::vector out; + clip_clipper_polygon_with_subject_bbox(src, bbox, out); + return out; + } + + [[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox) + { return clip_clipper_polygon_with_subject_bbox_templ(src, bbox); } + [[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox) + { return clip_clipper_polygon_with_subject_bbox_templ(src, bbox); } + + void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out) + { + clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points); + } + + [[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox) + { + Polygon out; + clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points); + return out; + } + + [[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox) + { + Polygons out; + out.reserve(src.size()); + for (const Polygon &p : src) + out.emplace_back(clip_clipper_polygon_with_subject_bbox(p, bbox)); + return out; + } } static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree) @@ -432,6 +526,8 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d { return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) { return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); } +Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); } Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { @@ -535,6 +631,8 @@ Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &cli { return _clipper(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons diff_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) + { return diff(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) @@ -833,7 +931,7 @@ ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear if (! preserve_collinear) return union_ex(simplify_polygons(subject, false)); - ClipperLib::PolyTree polytree; + ClipperLib::PolyTree polytree; ClipperLib::Clipper c; c.PreserveCollinear(true); c.StrictlySimple(true); diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 50d96142da..4017a73f1a 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -24,6 +24,8 @@ using Slic3r::ClipperLib::jtSquare; namespace Slic3r { +class BoundingBox; + static constexpr const float ClipperSafetyOffset = 10.f; static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter; @@ -306,6 +308,21 @@ namespace ClipperUtils { const SurfacesPtr &m_surfaces; size_t m_size; }; + + // For ClipperLib with Z coordinates. + using ZPoint = Vec3i32; + using ZPoints = std::vector; + + // Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon. + // Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by one + // with a set of polygons covering the whole layer below. + void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out); + void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out); + [[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox); + [[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox); + void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out); + [[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox); + [[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox); } // offset Polygons @@ -326,6 +343,7 @@ Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); } inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); } @@ -390,6 +408,9 @@ Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &su Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox(). +// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon. +Slic3r::Polygons diff_clipped(const Slic3r::Polygons &src, const Slic3r::Polygons &clipping, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp index 4d3bc6793d..6d8daa00ad 100644 --- a/src/libslic3r/Color.cpp +++ b/src/libslic3r/Color.cpp @@ -218,7 +218,7 @@ ColorRGBA ColorRGBA::operator * (float value) const for (size_t i = 0; i < 3; ++i) { ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); } - ret.m_data[3] = this->m_data[3]; + ret.m_data[3] = m_data[3]; return ret; } diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 4de88101c1..8dc0ff547b 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -98,34 +98,53 @@ bool ExPolygon::contains(const Polylines &polylines) const return pl_out.empty(); } -bool ExPolygon::contains(const Point &point) const +bool ExPolygon::contains(const Point &point, bool border_result /* = true */) const { - if (! this->contour.contains(point)) + if (! Slic3r::contains(contour, point, border_result)) + // Outside the outer contour, not on the contour boundary. return false; for (const Polygon &hole : this->holes) - if (hole.contains(point)) + if (Slic3r::contains(hole, point, ! border_result)) + // Inside a hole, not on the hole boundary. return false; return true; } -// inclusive version of contains() that also checks whether point is on boundaries -bool ExPolygon::contains_b(const Point &point) const +bool ExPolygon::on_boundary(const Point &point, double eps) const { - return this->contains(point) || this->has_boundary_point(point); + if (this->contour.on_boundary(point, eps)) + return true; + for (const Polygon &hole : this->holes) + if (hole.on_boundary(point, eps)) + return true; + return false; } -bool -ExPolygon::has_boundary_point(const Point &point) const +// Projection of a point onto the polygon. +Point ExPolygon::point_projection(const Point &point) const { - if (this->contour.has_boundary_point(point)) return true; - for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) { - if (h->has_boundary_point(point)) return true; + if (this->holes.empty()) { + return this->contour.point_projection(point); + } else { + double dist_min2 = std::numeric_limits::max(); + Point closest_pt_min; + for (size_t i = 0; i < this->num_contours(); ++ i) { + Point closest_pt = this->contour_or_hole(i).point_projection(point); + double d2 = (closest_pt - point).cast().squaredNorm(); + if (d2 < dist_min2) { + dist_min2 = d2; + closest_pt_min = closest_pt; + } + } + return closest_pt_min; } - return false; } bool ExPolygon::overlaps(const ExPolygon &other) const { + if (this->empty() || other.empty()) + return false; + #if 0 BoundingBox bbox = get_extents(other); bbox.merge(get_extents(*this)); @@ -135,14 +154,18 @@ bool ExPolygon::overlaps(const ExPolygon &other) const svg.draw_outline(*this); svg.draw_outline(other, "blue"); #endif + Polylines pl_out = intersection_pl(to_polylines(other), *this); + #if 0 svg.draw(pl_out, "red"); #endif - if (! pl_out.empty()) - return true; - //FIXME ExPolygon::overlaps() shall be commutative, it is not! - return ! other.contour.points.empty() && this->contains_b(other.contour.points.front()); + + // See unit test SCENARIO("Clipper diff with polyline", "[Clipper]") + // for in which case the intersection_pl produces any intersection. + return ! pl_out.empty() || + // If *this is completely inside other, then pl_out is empty, but the expolygons overlap. Test for that situation. + other.contains(this->contour.points.front()); } void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const @@ -183,12 +206,10 @@ void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const append(*expolygons, this->simplify(tolerance)); } -void -ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const +void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* polylines) const { // init helper object - Slic3r::Geometry::MedialAxis ma(max_width, min_width, this); - ma.lines = this->lines(); + Slic3r::Geometry::MedialAxis ma(min_width, max_width, *this); // compute the Voronoi diagram and extract medial axis polylines ThickPolylines pp; @@ -219,7 +240,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl call, so we keep the inner point until we perform the second intersection() as well */ Point new_front = polyline.points.front(); Point new_back = polyline.points.back(); - if (polyline.endpoints.first && !this->has_boundary_point(new_front)) { + if (polyline.endpoints.first && !this->on_boundary(new_front, SCALED_EPSILON)) { Vec2d p1 = polyline.points.front().cast(); Vec2d p2 = polyline.points[1].cast(); // prevent the line from touching on the other side, otherwise intersection() might return that solution @@ -229,7 +250,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl p1 -= (p2 - p1).normalized() * max_width; this->contour.intersection(Line(p1.cast(), p2.cast()), &new_front); } - if (polyline.endpoints.second && !this->has_boundary_point(new_back)) { + if (polyline.endpoints.second && !this->on_boundary(new_back, SCALED_EPSILON)) { Vec2d p1 = (polyline.points.end() - 2)->cast(); Vec2d p2 = polyline.points.back().cast(); // prevent the line from touching on the other side, otherwise intersection() might return that solution @@ -295,11 +316,10 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl polylines->insert(polylines->end(), pp.begin(), pp.end()); } -void -ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const +void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polylines) const { ThickPolylines tp; - this->medial_axis(max_width, min_width, &tp); + this->medial_axis(min_width, max_width, &tp); polylines->insert(polylines->end(), tp.begin(), tp.end()); } @@ -313,6 +333,18 @@ Lines ExPolygon::lines() const return lines; } +// Do expolygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool expolygons_match(const ExPolygon &l, const ExPolygon &r) +{ + if (l.holes.size() != r.holes.size() || ! polygons_match(l.contour, r.contour)) + return false; + for (size_t hole_idx = 0; hole_idx < l.holes.size(); ++ hole_idx) + if (! polygons_match(l.holes[hole_idx], r.holes[hole_idx])) + return false; + return true; +} + BoundingBox get_extents(const ExPolygon &expolygon) { return get_extents(expolygon.contour); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index edfe9c8745..98ad9e2e62 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -51,23 +51,29 @@ public: bool contains(const Line &line) const; bool contains(const Polyline &polyline) const; bool contains(const Polylines &polylines) const; - bool contains(const Point &point) const; - bool contains_b(const Point &point) const; - bool has_boundary_point(const Point &point) const; + bool contains(const Point &point, bool border_result = true) const; + // Approximate on boundary test. + bool on_boundary(const Point &point, double eps) const; + // Projection of a point onto the polygon. + Point point_projection(const Point &point) const; // Does this expolygon overlap another expolygon? // Either the ExPolygons intersect, or one is fully inside the other, // and it is not inside a hole of the other expolygon. + // The test may not be commutative if the two expolygons touch by a boundary only, + // see unit test SCENARIO("Clipper diff with polyline", "[Clipper]"). + // Namely expolygons touching at a vertical boundary are considered overlapping, while expolygons touching + // at a horizontal boundary are NOT considered overlapping. bool overlaps(const ExPolygon &other) const; void simplify_p(double tolerance, Polygons* polygons) const; Polygons simplify_p(double tolerance) const; ExPolygons simplify(double tolerance) const; void simplify(double tolerance, ExPolygons* expolygons) const; - void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const; - void medial_axis(double max_width, double min_width, Polylines* polylines) const; - Polylines medial_axis(double max_width, double min_width) const - { Polylines out; this->medial_axis(max_width, min_width, &out); return out; } + void medial_axis(double min_width, double max_width, ThickPolylines* polylines) const; + void medial_axis(double min_width, double max_width, Polylines* polylines) const; + Polylines medial_axis(double min_width, double max_width) const + { Polylines out; this->medial_axis(min_width, max_width, &out); return out; } Lines lines() const; // Number of contours (outer contour with holes). @@ -420,14 +426,14 @@ inline void expolygons_append(ExPolygons &dst, ExPolygons &&src) inline void expolygons_rotate(ExPolygons &expolys, double angle) { - for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p) - p->rotate(angle); + for (ExPolygon &expoly : expolys) + expoly.rotate(angle); } -inline bool expolygons_contain(ExPolygons &expolys, const Point &pt) +inline bool expolygons_contain(ExPolygons &expolys, const Point &pt, bool border_result = true) { - for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p) - if (p->contains(pt)) + for (const ExPolygon &expoly : expolys) + if (expoly.contains(pt, border_result)) return true; return false; } @@ -441,6 +447,10 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc return out; } +// Do expolygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool expolygons_match(const ExPolygon &l, const ExPolygon &r); + BoundingBox get_extents(const ExPolygon &expolygon); BoundingBox get_extents(const ExPolygons &expolygons); BoundingBox get_extents_rotated(const ExPolygon &poly, double angle); diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 0591e8a269..47c33938a8 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -186,7 +186,7 @@ public: ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); } // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); } - virtual bool can_reverse() const { return false; } + virtual bool can_reverse() const override { return false; } }; typedef std::vector ExtrusionPaths; diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 16211ca42d..cabf4e2f93 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -44,7 +44,14 @@ public: } ~ExtrusionEntityCollection() override { clear(); } explicit operator ExtrusionPaths() const; - + + ExtrusionEntitiesPtr::const_iterator cbegin() const { return this->entities.cbegin(); } + ExtrusionEntitiesPtr::const_iterator cend() const { return this->entities.cend(); } + ExtrusionEntitiesPtr::const_iterator begin() const { return this->entities.cbegin(); } + ExtrusionEntitiesPtr::const_iterator end() const { return this->entities.cend(); } + ExtrusionEntitiesPtr::iterator begin() { return this->entities.begin(); } + ExtrusionEntitiesPtr::iterator end() { return this->entities.end(); } + bool is_collection() const override { return true; } ExtrusionRole role() const override { ExtrusionRole out = erNone; @@ -106,6 +113,9 @@ public: { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } + size_t size() const { return entities.size(); } + // Recursively count paths and loops contained in this collection. + // this->items_count() >= this->size() size_t items_count() const; /// Returns a flattened copy of this ExtrusionEntityCollection. That is, all of the items in its entities vector are not collections. /// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy). @@ -121,12 +131,12 @@ public: }; void collect_polylines(Polylines &dst) const override { - for (ExtrusionEntity* extrusion_entity : this->entities) + for (const ExtrusionEntity *extrusion_entity : this->entities) extrusion_entity->collect_polylines(dst); } void collect_points(Points &dst) const override { - for (ExtrusionEntity* extrusion_entity : this->entities) + for (const ExtrusionEntity *extrusion_entity : this->entities) extrusion_entity->collect_points(dst); } diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 8278db9600..c962bbeb01 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -8,6 +8,7 @@ #include "../Print.hpp" #include "../PrintConfig.hpp" #include "../Surface.hpp" +// for Arachne based infills #include "../PerimeterGenerator.hpp" #include "FillBase.hpp" @@ -121,8 +122,8 @@ std::vector group_fills(const Layer &layer) bool has_internal_voids = false; for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { const LayerRegion &layerm = *layer.regions()[region_id]; - region_to_surface_params[region_id].assign(layerm.fill_surfaces.size(), nullptr); - for (const Surface &surface : layerm.fill_surfaces.surfaces) + region_to_surface_params[region_id].assign(layerm.fill_surfaces().size(), nullptr); + for (const Surface &surface : layerm.fill_surfaces()) if (surface.surface_type == stInternalVoid) has_internal_voids = true; else { @@ -180,7 +181,7 @@ std::vector group_fills(const Layer &layer) auto it_params = set_surface_params.find(params); if (it_params == set_surface_params.end()) it_params = set_surface_params.insert(it_params, params); - region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()] = &(*it_params); + region_to_surface_params[region_id][&surface - &layerm.fill_surfaces().surfaces.front()] = &(*it_params); } } @@ -192,9 +193,9 @@ std::vector group_fills(const Layer &layer) for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { const LayerRegion &layerm = *layer.regions()[region_id]; - for (const Surface &surface : layerm.fill_surfaces.surfaces) + for (const Surface &surface : layerm.fill_surfaces()) if (surface.surface_type != stInternalVoid) { - const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()]; + const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces().surfaces.front()]; if (params != nullptr) { SurfaceFill &fill = surface_fills[params->idx]; if (fill.region_id == size_t(-1)) { @@ -321,12 +322,104 @@ void export_group_fills_to_svg(const char *path, const std::vector } #endif -// friend to Layer +static void insert_fills_into_islands(Layer &layer, uint32_t fill_region_id, uint32_t fill_begin, uint32_t fill_end) +{ + if (fill_begin < fill_end) { + // Sort the extrusion range into its LayerIsland. + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + auto point_inside_surface = [&layer](const size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = layer.lslices_ex[lslice_idx].bbox; + return point.x() >= bbox.min.x() && point.x() < bbox.max.x() && + point.y() >= bbox.min.y() && point.y() < bbox.max.y() && + layer.lslices[lslice_idx].contour.contains(point); + }; + Point point = layer.get_region(fill_region_id)->fills().entities[fill_begin]->first_point(); + int lslice_idx = int(layer.lslices_ex.size()) - 1; + for (; lslice_idx >= 0; -- lslice_idx) + if (point_inside_surface(lslice_idx, point)) + break; + assert(lslice_idx >= 0); + if (lslice_idx >= 0) { + LayerSlice &lslice = layer.lslices_ex[lslice_idx]; + // Find an island. + LayerIsland *island = nullptr; + if (lslice.islands.size() == 1) { + // Cool, just save the extrusions in there. + island = &lslice.islands.front(); + } else { + // The infill was created for one of the infills. + // In case of ironing, the infill may not fall into any of the infill expolygons either. + // In case of some numerical error, the infill may not fall into any of the infill expolygons either. + // 1) Try an exact test, it should be cheaper than a closest region test. + for (LayerIsland &li : lslice.islands) { + const BoundingBoxes &bboxes = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite_bboxes() : + layer.get_region(li.fill_region_id)->fill_expolygons_bboxes(); + const ExPolygons &expolygons = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite() : + layer.get_region(li.fill_region_id)->fill_expolygons(); + for (uint32_t fill_expolygon_id : li.fill_expolygons) + if (bboxes[fill_expolygon_id].contains(point) && expolygons[fill_expolygon_id].contains(point)) { + island = &li; + goto found; + } + } + // 2) Find closest fill_expolygon, branch and bound by distance to bounding box. + { + struct Island { + uint32_t island_idx; + uint32_t expolygon_idx; + double distance2; + }; + std::vector islands_sorted; + for (uint32_t island_idx = 0; island_idx < uint32_t(lslice.islands.size()); ++ island_idx) { + const LayerIsland &li = lslice.islands[island_idx]; + const BoundingBoxes &bboxes = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite_bboxes() : + layer.get_region(li.fill_region_id)->fill_expolygons_bboxes(); + for (uint32_t fill_expolygon_id : li.fill_expolygons) + islands_sorted.push_back({ island_idx, fill_expolygon_id, bbox_point_distance_squared(bboxes[fill_expolygon_id], point) }); + } + std::sort(islands_sorted.begin(), islands_sorted.end(), [](auto &l, auto &r){ return l.distance2 < r.distance2; }); + auto dist_min2 = std::numeric_limits::max(); + for (uint32_t sorted_bbox_idx = 0; sorted_bbox_idx < uint32_t(islands_sorted.size()); ++ sorted_bbox_idx) { + const Island &isl = islands_sorted[sorted_bbox_idx]; + if (isl.distance2 > dist_min2) + // Branch & bound condition. + break; + LayerIsland &li = lslice.islands[isl.island_idx]; + const ExPolygons &expolygons = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite() : + layer.get_region(li.fill_region_id)->fill_expolygons(); + double d2 = (expolygons[isl.expolygon_idx].point_projection(point) - point).cast().squaredNorm(); + if (d2 < dist_min2) { + dist_min2 = d2; + island = &li; + } + } + } + found:; + } + assert(island); + if (island) + island->add_fill_range(LayerExtrusionRange{ fill_region_id, { fill_begin, fill_end }}); + } + } +} + +void Layer::clear_fills() +{ + for (LayerRegion *layerm : m_regions) + layerm->m_fills.clear(); + for (LayerSlice &lslice : lslices_ex) + for (LayerIsland &island : lslice.islands) + island.fills.clear(); +} + void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) { - for (LayerRegion *layerm : m_regions) - layerm->fills.clear(); - + this->clear_fills(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING // this->export_region_fill_surfaces_to_svg_debug("10_fill-initial"); @@ -381,6 +474,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // Used by the concentric infill pattern to clip the loops to create extrusion paths. f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + LayerRegion &layerm = *m_regions[surface_fill.region_id]; + // apply half spacing using this flow's own spacing and generate infill FillParams params; params.density = float(0.01 * surface_fill.params.density); @@ -389,7 +484,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.anchor_length_max = surface_fill.params.anchor_length_max; params.resolution = resolution; params.use_arachne = perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric; - params.layer_height = m_regions[surface_fill.region_id]->layer()->height; + params.layer_height = layerm.layer()->height; for (ExPolygon &expoly : surface_fill.expolygons) { // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. @@ -420,14 +515,15 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } // Save into layer. ExtrusionEntityCollection* eec = nullptr; - m_regions[surface_fill.region_id]->fills.entities.push_back(eec = new ExtrusionEntityCollection()); + auto fill_begin = uint32_t(layerm.fills().size()); + layerm.m_fills.entities.push_back(eec = new ExtrusionEntityCollection()); // Only concentric fills are not sorted. eec->no_sort = f->no_sort(); if (params.use_arachne) { for (const ThickPolyline &thick_polyline : thick_polylines) { Flow new_flow = surface_fill.params.flow.with_spacing(float(f->spacing)); - ExtrusionMultiPath multi_path = thick_polyline_to_multi_path(thick_polyline, surface_fill.params.extrusion_role, new_flow, scaled(0.05), float(SCALED_EPSILON)); + ExtrusionMultiPath multi_path = PerimeterGenerator::thick_polyline_to_multi_path(thick_polyline, surface_fill.params.extrusion_role, new_flow, scaled(0.05), float(SCALED_EPSILON)); // Append paths to collection. if (!multi_path.empty()) { if (multi_path.paths.front().first_point() == multi_path.paths.back().last_point()) @@ -444,25 +540,47 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: surface_fill.params.extrusion_role, flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); } + insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size())); } } } - // add thin fill regions - // Unpacks the collection, creates multiple collections per path. - // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. - // Why the paths are unpacked? - for (LayerRegion *layerm : m_regions) - for (const ExtrusionEntity *thin_fill : layerm->thin_fills.entities) { - ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); - layerm->fills.entities.push_back(&collection); - collection.entities.push_back(thin_fill->clone()); - } + for (LayerSlice &lslice : this->lslices_ex) + for (LayerIsland &island : lslice.islands) { + if (! island.thin_fills.empty()) { + // Copy thin fills into fills packed as a collection. + // Fills are always stored as collections, the rest of the pipeline (wipe into infill, G-code generator) relies on it. + LayerRegion &layerm = *this->get_region(island.perimeters.region()); + ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); + layerm.m_fills.entities.push_back(&collection); + collection.entities.reserve(island.thin_fills.size()); + for (uint32_t fill_id : island.thin_fills) + collection.entities.push_back(layerm.thin_fills().entities[fill_id]->clone()); + island.add_fill_range({ island.perimeters.region(), { uint32_t(layerm.m_fills.entities.size() - 1), uint32_t(layerm.m_fills.entities.size()) } }); + } + // Sort the fills by region ID. + std::sort(island.fills.begin(), island.fills.end(), [](auto &l, auto &r){ return l.region() < r.region() || (l.region() == r.region() && *l.begin() < *r.begin()); }); + // Compress continuous fill ranges of the same region. + { + size_t k = 0; + for (size_t i = 0; i < island.fills.size();) { + uint32_t region_id = island.fills[i].region(); + uint32_t begin = *island.fills[i].begin(); + uint32_t end = *island.fills[i].end(); + size_t j = i + 1; + for (; j < island.fills.size() && island.fills[j].region() == region_id && *island.fills[j].begin() == end; ++ j) + end = *island.fills[j].end(); + island.fills[k ++] = { region_id, { begin, end } }; + i = j; + } + island.fills.erase(island.fills.begin() + k, island.fills.end()); + } + } #ifndef NDEBUG for (LayerRegion *layerm : m_regions) - for (size_t i = 0; i < layerm->fills.entities.size(); ++ i) - assert(dynamic_cast(layerm->fills.entities[i]) != nullptr); + for (const ExtrusionEntity *e : layerm->fills()) + assert(dynamic_cast(e) != nullptr); #endif } @@ -518,7 +636,8 @@ void Layer::make_ironing() this->angle == rhs.angle; } - LayerRegion *layerm = nullptr; + LayerRegion *layerm; + uint32_t region_id; // IdeaMaker: ironing // ironing flowrate (5% percent) @@ -538,8 +657,8 @@ void Layer::make_ironing() std::vector by_extruder; double default_layer_height = this->object()->config().layer_height; - for (LayerRegion *layerm : m_regions) - if (! layerm->slices.empty()) { + for (uint32_t region_id = 0; region_id < uint32_t(this->regions().size()); ++region_id) + if (LayerRegion *layerm = this->get_region(region_id); ! layerm->slices().empty()) { IroningParams ironing_params; const PrintRegionConfig &config = layerm->region().config(); if (config.ironing && @@ -563,6 +682,7 @@ void Layer::make_ironing() ironing_params.speed = config.ironing_speed; ironing_params.angle = config.fill_angle * M_PI / 180.; ironing_params.layerm = layerm; + ironing_params.region_id = region_id; by_extruder.emplace_back(ironing_params); } } @@ -602,7 +722,7 @@ void Layer::make_ironing() if (iron_everything) { // Check whether there is any non-solid hole in the regions. bool internal_infill_solid = region_config.fill_density.value > 95.; - for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces) + for (const Surface &surface : ironing_params.layerm->fill_surfaces()) if ((! internal_infill_solid && surface.surface_type == stInternal) || surface.surface_type == stInternalBridge || surface.surface_type == stInternalVoid) { // Some fill region is not quite solid. Don't iron over the whole surface. iron_completely = false; @@ -611,10 +731,10 @@ void Layer::make_ironing() } if (iron_completely) { // Iron everything. This is likely only good for solid transparent objects. - for (const Surface &surface : ironing_params.layerm->slices.surfaces) + for (const Surface &surface : ironing_params.layerm->slices()) polygons_append(polys, surface.expolygon); } else { - for (const Surface &surface : ironing_params.layerm->slices.surfaces) + for (const Surface &surface : ironing_params.layerm->slices()) if (surface.surface_type == stTop || (iron_everything && surface.surface_type == stBottom)) // stBottomBridge is not being ironed on purpose, as it would likely destroy the bridges. polygons_append(polys, surface.expolygon); @@ -622,7 +742,7 @@ void Layer::make_ironing() if (iron_everything && ! iron_completely) { // Add solid fill surfaces. This may not be ideal, as one will not iron perimeters touching these // solid fill surfaces, but it is likely better than nothing. - for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces) + for (const Surface &surface : ironing_params.layerm->fill_surfaces()) if (surface.surface_type == stInternalSolid) polygons_append(infills, surface.expolygon); } @@ -658,14 +778,16 @@ void Layer::make_ironing() } if (! polylines.empty()) { // Save into layer. + auto fill_begin = uint32_t(ironing_params.layerm->fills().size()); ExtrusionEntityCollection *eec = nullptr; - ironing_params.layerm->fills.entities.push_back(eec = new ExtrusionEntityCollection()); + ironing_params.layerm->m_fills.entities.push_back(eec = new ExtrusionEntityCollection()); // Don't sort the ironing infill lines as they are monotonicly ordered. eec->no_sort = true; extrusion_entities_append_paths( eec->entities, std::move(polylines), erIroning, flow_mm3_per_mm, extrusion_width, float(extrusion_height)); + insert_fills_into_islands(*this, ironing_params.region_id, fill_begin, uint32_t(ironing_params.layerm->fills().size())); } } diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 29b343db01..f935d0edfc 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -316,9 +316,9 @@ std::pair adaptive_fill_line_spacing(const PrintObject &print_ob for (const Layer *layer : print_object.layers()) for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) { RegionFillData &rd = region_fill_data[region_id]; - if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) + if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces().empty()) rd.has_adaptive_infill = Tristate::Yes; - if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) + if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces().empty()) rd.has_support_infill = Tristate::Yes; } diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 69f530720f..7b005ee357 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -40,7 +40,7 @@ void FillConcentric::_fill_surface_single( size_t iPathFirst = polylines_out.size(); Point last_pos(0, 0); for (const Polygon &loop : loops) { - polylines_out.emplace_back(loop.split_at_index(last_pos.nearest_point_index(loop.points))); + polylines_out.emplace_back(loop.split_at_index(nearest_point_index(loop.points, last_pos))); last_pos = polylines_out.back().last_point(); } @@ -100,7 +100,7 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms, if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) { thick_polyline.points.pop_back(); assert(thick_polyline.points.size() * 2 == thick_polyline.width.size()); - int nearest_idx = last_pos.nearest_point_index(thick_polyline.points); + int nearest_idx = nearest_point_index(thick_polyline.points, last_pos); std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end()); std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end()); thick_polyline.points.emplace_back(thick_polyline.points.front()); diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index bd83bcfffe..185cb60af4 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -62,7 +62,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object throw_on_cancel_callback(); Polygons infill_area_here; for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions()) - for (const Surface& surface : layerm->fill_surfaces.surfaces) + for (const Surface& surface : layerm->fill_surfaces()) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_area_here, to_polygons(surface.expolygon)); @@ -93,7 +93,7 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--) { throw_on_cancel_callback(); for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions()) - for (const Surface &surface : layerm->fill_surfaces.surfaces) + for (const Surface &surface : layerm->fill_surfaces()) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_outlines[layer_id], to_polygons(surface.expolygon)); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c218826066..06801b0e48 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -119,8 +119,7 @@ namespace Slic3r { Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); // find standby point - Point standby_point; - pos.nearest_point(this->standby_points, &standby_point); + Point standby_point = nearest_point(this->standby_points, pos).first; /* We don't call gcodegen.travel_to() because we don't need retraction (it was already triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates @@ -478,9 +477,9 @@ namespace Slic3r { // Collect pairs of object_layer + support_layer sorted by print_z. // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. -std::vector GCode::collect_layers_to_print(const PrintObject& object) +GCode::ObjectsLayerToPrint GCode::collect_layers_to_print(const PrintObject& object) { - std::vector layers_to_print; + GCode::ObjectsLayerToPrint layers_to_print; layers_to_print.reserve(object.layers().size() + object.support_layers().size()); /* @@ -504,9 +503,9 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Pair the object layers with the support layers by z. size_t idx_object_layer = 0; size_t idx_support_layer = 0; - const LayerToPrint* last_extrusion_layer = nullptr; + const ObjectLayerToPrint* last_extrusion_layer = nullptr; while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { - LayerToPrint layer_to_print; + ObjectLayerToPrint layer_to_print; layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr; layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr; if (layer_to_print.object_layer && layer_to_print.support_layer) { @@ -578,8 +577,8 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z // will be printed for all objects at once. -// Return a list of items. -std::vector>> GCode::collect_layers_to_print(const Print& print) +// Return a list of items. +std::vector> GCode::collect_layers_to_print(const Print& print) { struct OrderingItem { coordf_t print_z; @@ -587,15 +586,15 @@ std::vector>> GCode::collec size_t layer_idx; }; - std::vector> per_object(print.objects().size(), std::vector()); - std::vector ordering; + std::vector per_object(print.objects().size(), ObjectsLayerToPrint()); + std::vector ordering; for (size_t i = 0; i < print.objects().size(); ++i) { per_object[i] = collect_layers_to_print(*print.objects()[i]); OrderingItem ordering_item; ordering_item.object_idx = i; ordering.reserve(ordering.size() + per_object[i].size()); - const LayerToPrint& front = per_object[i].front(); - for (const LayerToPrint& ltp : per_object[i]) { + const ObjectLayerToPrint &front = per_object[i].front(); + for (const ObjectLayerToPrint <p : per_object[i]) { ordering_item.print_z = ltp.print_z(); ordering_item.layer_idx = <p - &front; ordering.emplace_back(ordering_item); @@ -604,7 +603,7 @@ std::vector>> GCode::collec std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; }); - std::vector>> layers_to_print; + std::vector> layers_to_print; // Merge numerically very close Z values. for (size_t i = 0; i < ordering.size();) { @@ -613,10 +612,10 @@ std::vector>> GCode::collec coordf_t zmax = ordering[i].print_z + EPSILON; for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j); // Merge into layers_to_print. - std::pair> merged; + std::pair merged; // Assign an average print_z to the set of layers with nearly equal print_z. merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z); - merged.second.assign(print.objects().size(), LayerToPrint()); + merged.second.assign(print.objects().size(), ObjectLayerToPrint()); for (; i < j; ++i) { const OrderingItem& oi = ordering[i]; assert(merged.second[oi.object_idx].layer() == nullptr); @@ -852,7 +851,7 @@ namespace DoExport { region.config().get_abs_value("small_perimeter_speed") == 0 || region.config().get_abs_value("external_perimeter_speed") == 0 || region.config().get_abs_value("bridge_speed") == 0) - mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm()); + mm3_per_mm.push_back(layerm->perimeters().min_mm3_per_mm()); if (region.config().get_abs_value("infill_speed") == 0 || region.config().get_abs_value("solid_infill_speed") == 0 || region.config().get_abs_value("top_solid_infill_speed") == 0 || @@ -869,7 +868,7 @@ namespace DoExport { return min; }; - mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills)); + mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills())); } } } @@ -1350,7 +1349,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } else { // Sort layers by Z. // All extrusion moves with the same top layer height are extruded uninterrupted. - std::vector>> layers_to_print = collect_layers_to_print(print); + std::vector> layers_to_print = collect_layers_to_print(print); // Prusa Multi-Material wipe tower. if (has_wipe_tower && ! layers_to_print.empty()) { m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); @@ -1478,7 +1477,7 @@ void GCode::process_layers( const Print &print, const ToolOrdering &tool_ordering, const std::vector &print_object_instances_ordering, - const std::vector>> &layers_to_print, + const std::vector> &layers_to_print, GCodeOutputStream &output_stream) { // The pipeline is variable: The vase mode filter is optional. @@ -1496,7 +1495,7 @@ void GCode::process_layers( return LayerResult::make_nop_layer_result(); } } else { - const std::pair>& layer = layers_to_print[layer_to_print_idx++]; + const std::pair &layer = layers_to_print[layer_to_print_idx++]; const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); if (m_wipe_tower && layer_tools.has_wipe_tower) m_wipe_tower->next_layer(); @@ -1562,7 +1561,7 @@ void GCode::process_layers( void GCode::process_layers( const Print &print, const ToolOrdering &tool_ordering, - std::vector layers_to_print, + ObjectsLayerToPrint layers_to_print, const size_t single_object_idx, GCodeOutputStream &output_stream) { @@ -1581,7 +1580,7 @@ void GCode::process_layers( return LayerResult::make_nop_layer_result(); } } else { - LayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; + ObjectLayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; print.throw_if_canceled(); return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx); } @@ -1832,70 +1831,39 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr } } -inline GCode::ObjectByExtruder& object_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects) -{ - std::vector &objects_by_extruder = by_extruder[extruder_id]; - if (objects_by_extruder.empty()) - objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder()); - return objects_by_extruder[object_idx]; -} - -inline std::vector& object_islands_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects, - size_t num_islands) -{ - std::vector &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands; - if (islands.empty()) - islands.assign(num_islands, GCode::ObjectByExtruder::Island()); - return islands; -} - std::vector GCode::sort_print_object_instances( - std::vector &objects_by_extruder, - const std::vector &layers, + const std::vector &object_layers, // Ordering must be defined for normal (non-sequential print). - const std::vector *ordering, + const std::vector *ordering, // For sequential print, the instance of the object to be printing has to be defined. - const size_t single_object_instance_idx) + const size_t single_object_instance_idx) { std::vector out; if (ordering == nullptr) { // Sequential print, single object is being printed. - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object) - out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx); - } + assert(object_layers.size() == 1); + const Layer *layer = object_layers.front().object_layer; + assert(layer != nullptr); + out.emplace_back(0, *layer->object(), single_object_instance_idx); } else { - // Create mapping from PrintObject* to ObjectByExtruder*. - std::vector> sorted; - sorted.reserve(objects_by_extruder.size()); - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object) - sorted.emplace_back(print_object, &object_by_extruder); - } + // Create mapping from PrintObject* to ObjectLayerToPrint ID. + std::vector> sorted; + sorted.reserve(object_layers.size()); + for (const ObjectLayerToPrint &object : object_layers) + if (const PrintObject* print_object = object.object(); print_object) + sorted.emplace_back(print_object, &object - object_layers.data()); std::sort(sorted.begin(), sorted.end()); if (! sorted.empty()) { out.reserve(sorted.size()); for (const PrintInstance *instance : *ordering) { const PrintObject &print_object = *instance->print_object; - std::pair key(&print_object, nullptr); + std::pair key(&print_object, 0); auto it = std::lower_bound(sorted.begin(), sorted.end(), key); if (it != sorted.end() && it->first == &print_object) - // ObjectByExtruder for this PrintObject was found. - out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data()); + // ObjectLayerToPrint for this PrintObject was found. + out.emplace_back(it->second, print_object, instance - print_object.instances().data()); } } } @@ -2062,7 +2030,7 @@ namespace Skirt { LayerResult GCode::process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. - const std::vector &layers, + const ObjectsLayerToPrint &layers, const LayerTools &layer_tools, const bool last_layer, // Pairs of PrintObject index and its instance index. @@ -2079,7 +2047,7 @@ LayerResult GCode::process_layer( const Layer *object_layer = nullptr; const SupportLayer *support_layer = nullptr; const SupportLayer *raft_layer = nullptr; - for (const LayerToPrint &l : layers) { + for (const ObjectLayerToPrint &l : layers) { if (l.object_layer && ! object_layer) object_layer = l.object_layer; if (l.support_layer) { @@ -2089,7 +2057,7 @@ LayerResult GCode::process_layer( raft_layer = support_layer; } } - const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; + const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; LayerResult result { {}, layer.id(), false, last_layer, false}; if (layer_tools.extruders.empty()) // Nothing to extrude. @@ -2111,8 +2079,8 @@ LayerResult GCode::process_layer( if (enable) { for (const LayerRegion *layer_region : layer.regions()) if (size_t(layer_region->region().config().bottom_solid_layers.value) > layer.id() || - layer_region->perimeters.items_count() > 1u || - layer_region->fills.items_count() > 0) { + layer_region->perimeters().items_count() > 1u || + layer_region->fills().items_count() > 0) { enable = false; break; } @@ -2192,167 +2160,9 @@ LayerResult GCode::process_layer( Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done); - // Group extrusions by an extruder, then by an object, an island and a region. - std::map> by_extruder; - bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); - for (const LayerToPrint &layer_to_print : layers) { - if (layer_to_print.support_layer != nullptr) { - const SupportLayer &support_layer = *layer_to_print.support_layer; - const PrintObject &object = *support_layer.object(); - if (! support_layer.support_fills.entities.empty()) { - ExtrusionRole role = support_layer.support_fills.role(); - bool has_support = role == erMixed || role == erSupportMaterial; - bool has_interface = role == erMixed || role == erSupportMaterialInterface; - // Extruder ID of the support base. -1 if "don't care". - unsigned int support_extruder = object.config().support_material_extruder.value - 1; - // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool support_dontcare = object.config().support_material_extruder.value == 0; - // Extruder ID of the support interface. -1 if "don't care". - unsigned int interface_extruder = object.config().support_material_interface_extruder.value - 1; - // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool interface_dontcare = object.config().support_material_interface_extruder.value == 0; - if (support_dontcare || interface_dontcare) { - // Some support will be printed with "don't care" material, preferably non-soluble. - // Is the current extruder assigned a soluble filament? - unsigned int dontcare_extruder = first_extruder_id; - if (print.config().filament_soluble.get_at(dontcare_extruder)) { - // The last extruder printed on the previous layer extrudes soluble filament. - // Try to find a non-soluble extruder on the same layer. - for (unsigned int extruder_id : layer_tools.extruders) - if (! print.config().filament_soluble.get_at(extruder_id)) { - dontcare_extruder = extruder_id; - break; - } - } - if (support_dontcare) - support_extruder = dontcare_extruder; - if (interface_dontcare) - interface_extruder = dontcare_extruder; - } - // Both the support and the support interface are printed with the same extruder, therefore - // the interface may be interleaved with the support base. - bool single_extruder = ! has_support || support_extruder == interface_extruder; - // Assign an extruder to the base. - ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size()); - obj.support = &support_layer.support_fills; - obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial; - if (! single_extruder && has_interface) { - ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size()); - obj_interface.support = &support_layer.support_fills; - obj_interface.support_extrusion_role = erSupportMaterialInterface; - } - } - } - if (layer_to_print.object_layer != nullptr) { - const Layer &layer = *layer_to_print.object_layer; - // We now define a strategy for building perimeters and fills. The separation - // between regions doesn't matter in terms of printing order, as we follow - // another logic instead: - // - we group all extrusions by extruder so that we minimize toolchanges - // - we start from the last used extruder - // - for each extruder, we group extrusions by island - // - for each island, we extrude perimeters first, unless user set the infill_first - // option - // (Still, we have to keep track of regions because we need to apply their config) - size_t n_slices = layer.lslices.size(); - const std::vector &layer_surface_bboxes = layer.lslices_bboxes; - // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, - // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. - std::vector slices_test_order; - slices_test_order.reserve(n_slices); - for (size_t i = 0; i < n_slices; ++ i) - slices_test_order.emplace_back(i); - std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) { - const Vec2d s1 = layer_surface_bboxes[i].size().cast(); - const Vec2d s2 = layer_surface_bboxes[j].size().cast(); - return s1.x() * s1.y() < s2.x() * s2.y(); - }); - auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { - const BoundingBox &bbox = layer_surface_bboxes[i]; - return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && - point(1) >= bbox.min(1) && point(1) < bbox.max(1) && - layer.lslices[i].contour.contains(point); - }; - - for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { - const LayerRegion *layerm = layer.regions()[region_id]; - if (layerm == nullptr) - continue; - // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not - // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. - const PrintRegion ®ion = print.get_print_region(layerm->region().print_region_id()); - - // Now we must process perimeters and infills and create islands of extrusions in by_region std::map. - // It is also necessary to save which extrusions are part of MM wiping and which are not. - // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice: - std::vector printing_extruders; - for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) { - for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills.entities : layerm->perimeters.entities) { - // extrusions represents infill or perimeter extrusions of a single island. - assert(dynamic_cast(ee) != nullptr); - const auto *extrusions = static_cast(ee); - if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail. - continue; - - // This extrusion is part of certain Region, which tells us which extruder should be used for it: - int correct_extruder_id = layer_tools.extruder(*extrusions, region); - - // Let's recover vector of extruder overrides: - const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr; - if (! layer_tools.has_extruder(correct_extruder_id)) { - // this entity is not overridden, but its extruder is not in layer_tools - we'll print it - // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) - correct_extruder_id = layer_tools.extruders.back(); - } - printing_extruders.clear(); - if (is_anything_overridden) { - entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size()); - if (entity_overrides == nullptr) { - printing_extruders.emplace_back(correct_extruder_id); - } else { - printing_extruders.reserve(entity_overrides->size()); - for (int extruder : *entity_overrides) - printing_extruders.emplace_back(extruder >= 0 ? - // at least one copy is overridden to use this extruder - extruder : - // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) - static_cast(- extruder - 1)); - Slic3r::sort_remove_duplicates(printing_extruders); - } - } else - printing_extruders.emplace_back(correct_extruder_id); - - // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: - for (unsigned int extruder : printing_extruders) - { - std::vector &islands = object_islands_by_extruder( - by_extruder, - extruder, - &layer_to_print - layers.data(), - layers.size(), n_slices+1); - for (size_t i = 0; i <= n_slices; ++ i) { - bool last = i == n_slices; - size_t island_idx = last ? n_slices : slices_test_order[i]; - if (// extrusions->first_point does not fit inside any slice - last || - // extrusions->first_point fits inside ith slice - point_inside_surface(island_idx, extrusions->first_point())) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides); - break; - } - } - } - } - } - } // for regions - } - } // for objects - if (this->config().avoid_curled_filament_during_travels) { m_avoid_curled_filaments.clear(); - for (const LayerToPrint &layer_to_print : layers) { + for (const ObjectLayerToPrint &layer_to_print : layers) { m_avoid_curled_filaments.add_obstacles(layer_to_print.object_layer, Point(scaled(this->origin()))); m_avoid_curled_filaments.add_obstacles(layer_to_print.support_layer, Point(scaled(this->origin()))); } @@ -2404,92 +2214,29 @@ LayerResult GCode::process_layer( m_avoid_crossing_perimeters.disable_once(); } - - auto objects_by_extruder_it = by_extruder.find(extruder_id); - if (objects_by_extruder_it == by_extruder.end()) - continue; - - std::vector instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); + std::vector instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx); // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): - std::vector by_region_per_copy_cache; - for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { - if (is_anything_overridden && print_wipe_extrusions == 0) + bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden(); + if (is_anything_overridden) { + // Extrude wipes. + size_t gcode_size_old = gcode.size(); + for (const InstanceToPrint &instance : instances_to_print) + this->process_layer_single_object( + gcode, extruder_id, instance, + layers[instance.object_layer_to_print_id], layer_tools, + is_anything_overridden, true /* print_wipe_extrusions */); + if (gcode_size_old < gcode.size()) gcode+="; PURGING FINISHED\n"; - - for (InstanceToPrint &instance_to_print : instances_to_print) { - const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; - // To control print speed of the 1st object layer printed over raft interface. - bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && - instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); - m_config.apply(instance_to_print.print_object.config(), true); - m_layer = layer_to_print.layer(); - m_object_layer_over_raft = object_layer_over_raft; - if (m_config.avoid_crossing_perimeters) - m_avoid_crossing_perimeters.init_layer(*m_layer); - if (this->config().gcode_label_objects) - gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; - // When starting a new object, use the external motion planner for the first travel move. - const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; - std::pair this_object_copy(&instance_to_print.print_object, offset); - if (m_last_obj_copy != this_object_copy) - m_avoid_crossing_perimeters.use_external_mp_once(); - m_last_obj_copy = this_object_copy; - this->set_origin(unscale(offset)); - if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { - m_layer = layer_to_print.support_layer; - m_object_layer_over_raft = false; - gcode += this->extrude_support( - // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. - instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role)); - m_layer = layer_to_print.layer(); - m_object_layer_over_raft = object_layer_over_raft; - } - //FIXME order islands? - // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) - for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { - const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region; - //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. - if (print.config().infill_first) { - gcode += this->extrude_infill(print, by_region_specific, false); - gcode += this->extrude_perimeters(print, by_region_specific); - } else { - gcode += this->extrude_perimeters(print, by_region_specific); - gcode += this->extrude_infill(print,by_region_specific, false); - } - // ironing - gcode += this->extrude_infill(print,by_region_specific, true); - } - if (this->config().gcode_label_objects) - gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; - } } + // Extrude normal extrusions. + for (const InstanceToPrint &instance : instances_to_print) + this->process_layer_single_object( + gcode, extruder_id, instance, + layers[instance.object_layer_to_print_id], layer_tools, + is_anything_overridden, false /* print_wipe_extrusions */); } -#if 0 - // Apply spiral vase post-processing if this layer contains suitable geometry - // (we must feed all the G-code into the post-processor, including the first - // bottom non-spiral layers otherwise it will mess with positions) - // we apply spiral vase at this stage because it requires a full layer. - // Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only. - if (m_spiral_vase) - gcode = m_spiral_vase->process_layer(std::move(gcode)); - - // Apply cooling logic; this may alter speeds. - if (m_cooling_buffer) - gcode = m_cooling_buffer->process_layer(std::move(gcode), layer.id(), - // Flush the cooling buffer at each object layer or possibly at the last layer, even if it contains just supports (This should not happen). - object_layer || last_layer); - - // Apply pressure equalization if enabled; - // printf("G-code before filter:\n%s\n", gcode.c_str()); - if (m_pressure_equalizer) - gcode = m_pressure_equalizer->process(gcode.c_str(), false); - // printf("G-code after filter:\n%s\n", out.c_str()); - - file.write(gcode); -#endif - BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); @@ -2498,6 +2245,227 @@ LayerResult GCode::process_layer( return result; } +static const auto comment_perimeter = "perimeter"sv; +// Comparing string_view pointer & length for speed. +static inline bool comment_is_perimeter(const std::string_view comment) { + return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size(); +} + +void GCode::process_layer_single_object( + // output + std::string &gcode, + // Index of the extruder currently active. + const unsigned int extruder_id, + // What object and instance is going to be printed. + const InstanceToPrint &print_instance, + // and the object & support layer of the above. + const ObjectLayerToPrint &layer_to_print, + // Container for extruder overrides (when wiping into object or infill). + const LayerTools &layer_tools, + // Is any extrusion possibly marked as wiping extrusion? + const bool is_anything_overridden, + // Round 1 (wiping into object or infill) or round 2 (normal extrusions). + const bool print_wipe_extrusions) +{ + //FIXME what the heck ID is this? Layer ID or Object ID? More likely an Object ID. + uint32_t layer_id = 0; + bool first = true; + // Delay layer initialization as many layers may not print with all extruders. + auto init_layer_delayed = [this, &print_instance, &layer_to_print, layer_id, &first, &gcode]() { + if (first) { + first = false; + const PrintObject &print_object = print_instance.print_object; + const Print &print = *print_object.print(); + m_config.apply(print_object.config(), true); + m_layer = layer_to_print.layer(); + if (print.config().avoid_crossing_perimeters) + m_avoid_crossing_perimeters.init_layer(*m_layer); + // When starting a new object, use the external motion planner for the first travel move. + const Point &offset = print_object.instances()[print_instance.instance_id].shift; + std::pair this_object_copy(&print_object, offset); + if (m_last_obj_copy != this_object_copy) + m_avoid_crossing_perimeters.use_external_mp_once(); + m_last_obj_copy = this_object_copy; + this->set_origin(unscale(offset)); + if (this->config().gcode_label_objects) + gcode += std::string("; printing object ") + print_object.model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; + } + }; + + const PrintObject &print_object = print_instance.print_object; + const Print &print = *print_object.print(); + + if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr) + if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) { + ExtrusionRole role = support_layer.support_fills.role(); + bool has_support = role == erMixed || role == erSupportMaterial; + bool has_interface = role == erMixed || role == erSupportMaterialInterface; + // Extruder ID of the support base. -1 if "don't care". + unsigned int support_extruder = print_object.config().support_material_extruder.value - 1; + // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? + bool support_dontcare = print_object.config().support_material_extruder.value == 0; + // Extruder ID of the support interface. -1 if "don't care". + unsigned int interface_extruder = print_object.config().support_material_interface_extruder.value - 1; + // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? + bool interface_dontcare = print_object.config().support_material_interface_extruder.value == 0; + if (support_dontcare || interface_dontcare) { + // Some support will be printed with "don't care" material, preferably non-soluble. + // Is the current extruder assigned a soluble filament? + unsigned int dontcare_extruder = layer_tools.extruders.front(); + if (print.config().filament_soluble.get_at(dontcare_extruder)) { + // The last extruder printed on the previous layer extrudes soluble filament. + // Try to find a non-soluble extruder on the same layer. + for (unsigned int extruder_id : layer_tools.extruders) + if (! print.config().filament_soluble.get_at(extruder_id)) { + dontcare_extruder = extruder_id; + break; + } + } + if (support_dontcare) + support_extruder = dontcare_extruder; + if (interface_dontcare) + interface_extruder = dontcare_extruder; + } + bool extrude_support = has_support && support_extruder == extruder_id; + bool extrude_interface = interface_extruder && interface_extruder == extruder_id; + if (extrude_support || extrude_interface) { + init_layer_delayed(); + m_layer = layer_to_print.support_layer; + m_object_layer_over_raft = false; + gcode += this->extrude_support( + // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. + support_layer.support_fills.chained_path_from(m_last_pos, has_support ? (has_interface ? erMixed : erSupportMaterial) : erSupportMaterialInterface)); + } + } + + m_layer = layer_to_print.layer(); + // To control print speed of the 1st object layer printed over raft interface. + m_object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && + print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); + + // Check whether this ExtrusionEntityCollection should be printed now with extruder_id, given print_wipe_extrusions + // (wipe extrusions are printed before regular extrusions). + auto shall_print_this_extrusion_collection = [extruder_id, instance_id = print_instance.instance_id, &layer_tools, is_anything_overridden, print_wipe_extrusions](const ExtrusionEntityCollection *eec, const PrintRegion ®ion) -> bool { + assert(eec != nullptr); + if (eec->entities.empty()) + // This shouldn't happen. FIXME why? but first_point() would fail. + return false; + // This extrusion is part of certain Region, which tells us which extruder should be used for it: + int correct_extruder_id = layer_tools.extruder(*eec, region); + if (! layer_tools.has_extruder(correct_extruder_id)) { + // this entity is not overridden, but its extruder is not in layer_tools - we'll print it + // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) + correct_extruder_id = layer_tools.extruders.back(); + } + int extruder_override_id = is_anything_overridden ? layer_tools.wiping_extrusions().get_extruder_override(eec, instance_id) : -1; + return print_wipe_extrusions ? + extruder_override_id == int(extruder_id) : + extruder_override_id < 0 && extruder_id == correct_extruder_id; + }; + + ExtrusionEntitiesPtr temp_fill_extrusions; + if (const Layer *layer = layer_to_print.object_layer; layer) + for (const LayerSlice &lslice : layer->lslices_ex) { + auto extrude_infill_range = [&]( + const LayerRegion &layerm, const ExtrusionEntityCollection &fills, + LayerExtrusionRanges::const_iterator it_fill_ranges_begin, LayerExtrusionRanges::const_iterator it_fill_ranges_end, bool ironing) { + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + temp_fill_extrusions.clear(); + for (auto it_fill_range = it_fill_ranges_begin; it_fill_range != it_fill_ranges_end; ++ it_fill_range) { + assert(it_fill_range->region() == it_fill_ranges_begin->region()); + for (uint32_t fill_id : *it_fill_range) { + assert(dynamic_cast(fills.entities[fill_id])); + if (auto *eec = static_cast(fills.entities[fill_id]); + (eec->role() == erIroning) == ironing && shall_print_this_extrusion_collection(eec, region)) { + if (eec->can_reverse()) + // Flatten the infill collection for better path planning. + for (auto *ee : eec->entities) + temp_fill_extrusions.emplace_back(ee); + else + temp_fill_extrusions.emplace_back(eec); + } + } + } + if (! temp_fill_extrusions.empty()) { + init_layer_delayed(); + m_config.apply(region.config()); + //FIXME The source extrusions may be reversed, thus modifying the extrusions! Is it a problem? How about the initial G-code preview? + // Will parallel access of initial G-code preview to these extrusions while reordering them at backend cause issues? + chain_and_reorder_extrusion_entities(temp_fill_extrusions, &m_last_pos); + const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; + for (const ExtrusionEntity *fill : temp_fill_extrusions) + if (auto *eec = dynamic_cast(fill); eec) { + for (const ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) + gcode += this->extrude_entity(*ee, extrusion_name); + } else + gcode += this->extrude_entity(*fill, extrusion_name); + } + }; + + //FIXME order islands? + // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) + for (const LayerIsland &island : lslice.islands) { + auto process_perimeters = [&]() { + const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + bool first = true; + for (uint32_t perimeter_id : island.perimeters) { + assert(dynamic_cast(layerm.perimeters().entities[perimeter_id])); + if (const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); + shall_print_this_extrusion_collection(eec, region)) { + // This may not apply to Arachne, but maybe the Arachne gap fill should disable reverse as well? + // assert(! eec->can_reverse()); + if (first) { + first = false; + init_layer_delayed(); + m_config.apply(region.config()); + } + for (const ExtrusionEntity *ee : *eec) + gcode += this->extrude_entity(*ee, comment_perimeter, -1.); + } + } + }; + auto process_infill = [&]() { + for (auto it = island.fills.begin(); it != island.fills.end();) { + // Gather range of fill ranges with the same region. + auto it_end = it; + for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; + const LayerRegion &layerm = *layer->get_region(it->region()); + extrude_infill_range(layerm, layerm.fills(), it, it_end, false /* normal extrusions, not ironing */); + it = it_end; + } + }; + if (print.config().infill_first) { + process_infill(); + process_perimeters(); + } else { + process_perimeters(); + process_infill(); + } + } + // ironing + //FIXME move ironing into the loop above over LayerIslands? + // First Ironing changes extrusion rate quickly, second single ironing may be done over multiple perimeter regions. + // Ironing in a second phase is safer, but it may be less efficient. + for (const LayerIsland &island : lslice.islands) { + for (auto it = island.fills.begin(); it != island.fills.end();) { + // Gather range of fill ranges with the same region. + auto it_end = it; + for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; + const LayerRegion &layerm = *layer->get_region(it->region()); + extrude_infill_range(layerm, layerm.fills(), it, it_end, true /* ironing, not normal extrusions */); + it = it_end; + } + } + } + if (! first && this->config().gcode_label_objects) + gcode += std::string("; stop printing object ") + print_object.model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; +} + void GCode::apply_print_config(const PrintConfig &print_config) { m_writer.apply_print_config(print_config); @@ -2587,12 +2555,6 @@ std::string GCode::change_layer(coordf_t print_z) return gcode; } -static const auto comment_perimeter = "perimeter"sv; -// Comparing string_view pointer & length for speed. -static inline bool comment_is_perimeter(const std::string_view comment) { - return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size(); -} - std::string GCode::extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed) { // get a copy; don't modify the orientation of the original loop object otherwise @@ -2759,49 +2721,6 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string_view description return gcode; } -// Extrude perimeters: Decide where to put seams (hide or align seams). -std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region) -{ - std::string gcode; - for (const ObjectByExtruder::Island::Region ®ion : by_region) - if (! region.perimeters.empty()) { - m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); - - for (const ExtrusionEntity* ee : region.perimeters) - gcode += this->extrude_entity(*ee, comment_perimeter, -1.); - } - return gcode; -} - -// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance. -std::string GCode::extrude_infill(const Print &print, const std::vector &by_region, bool ironing) -{ - std::string gcode; - ExtrusionEntitiesPtr extrusions; - const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; - for (const ObjectByExtruder::Island::Region ®ion : by_region) - if (! region.infills.empty()) { - extrusions.clear(); - extrusions.reserve(region.infills.size()); - for (ExtrusionEntity *ee : region.infills) - if ((ee->role() == erIroning) == ironing) - extrusions.emplace_back(ee); - if (! extrusions.empty()) { - m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); - chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); - for (const ExtrusionEntity *fill : extrusions) { - auto *eec = dynamic_cast(fill); - if (eec) { - for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) - gcode += this->extrude_entity(*ee, extrusion_name); - } else - gcode += this->extrude_entity(*fill, extrusion_name); - } - } - } - return gcode; -} - std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills) { static constexpr const auto support_label = "support material"sv; @@ -3326,104 +3245,4 @@ Point GCode::gcode_to_point(const Vec2d &point) const scale_(point(1) - m_origin(1) + extruder_offset(1))); } -// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed -// during infill/perimeter wiping, or normally (depends on wiping_entities parameter) -// Fills in by_region_per_copy_cache and returns its reference. -const std::vector& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const -{ - bool has_overrides = false; - for (const auto& reg : by_region) - if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { - has_overrides = true; - break; - } - - // Data is cleared, but the memory is not. - by_region_per_copy_cache.clear(); - - if (! has_overrides) - // Simple case. No need to copy the regions. - return wiping_entities ? by_region_per_copy_cache : this->by_region; - - // Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions. - // Some of the extrusions of some object instances are printed later - those are the clean print extrusions. - // Filter out the extrusions based on the infill_overrides / perimeter_overrides: - - for (const auto& reg : by_region) { - by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island - - // Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed - // References are used so that we don't have to repeat the same code - for (int iter = 0; iter < 2; ++iter) { - const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters); - ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters); - const std::vector& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides); - - // Now the most important thing - which extrusion should we print. - // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack. - if (wiping_entities) { - // Apply overrides for this region. - for (unsigned int i = 0; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; - // This copy (aka object instance) should be printed with this extruder, which overrides the default one. - if (this_override != nullptr && (*this_override)[copy] == int(extruder)) - target_eec.emplace_back(entities[i]); - } - } else { - // Apply normal extrusions (non-overrides) for this region. - unsigned int i = 0; - for (; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; - // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. - if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1) - target_eec.emplace_back(entities[i]); - } - for (; i < entities.size(); ++ i) - target_eec.emplace_back(entities[i]); - } - } - } - return by_region_per_copy_cache; -} - -// This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter) -// It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy. -void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder) -{ - // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves: - ExtrusionEntitiesPtr* perimeters_or_infills; - std::vector* perimeters_or_infills_overrides; - - switch (type) { - case PERIMETERS: - perimeters_or_infills = &perimeters; - perimeters_or_infills_overrides = &perimeters_overrides; - break; - case INFILL: - perimeters_or_infills = &infills; - perimeters_or_infills_overrides = &infills_overrides; - break; - default: - throw Slic3r::InvalidArgument("Unknown parameter!"); - } - - // First we append the entities, there are eec->entities.size() of them: - size_t old_size = perimeters_or_infills->size(); - size_t new_size = old_size + (eec->can_reverse() ? eec->entities.size() : 1); - perimeters_or_infills->reserve(new_size); - if (eec->can_reverse()) { - for (auto* ee : eec->entities) - perimeters_or_infills->emplace_back(ee); - } else - perimeters_or_infills->emplace_back(const_cast(eec)); - - if (copies_extruder != nullptr) { - // Don't reallocate overrides if not needed. - // Missing overrides are implicitely considered non-overridden. - perimeters_or_infills_overrides->reserve(new_size); - perimeters_or_infills_overrides->resize(old_size, nullptr); - perimeters_or_infills_overrides->resize(new_size, copies_extruder); - } -} - } // namespace Slic3r diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 95153585d5..435941de5f 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -189,15 +189,16 @@ public: // Object and support extrusions of the same PrintObject at the same print_z. // public, so that it could be accessed by free helper functions from GCode.cpp - struct LayerToPrint + struct ObjectLayerToPrint { - LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} + ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} const Layer* object_layer; const SupportLayer* support_layer; const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } }; + using ObjectsLayerToPrint = std::vector; private: class GCodeOutputStream { @@ -240,13 +241,13 @@ private: }; void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb); - static std::vector collect_layers_to_print(const PrintObject &object); - static std::vector>> collect_layers_to_print(const Print &print); + static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object); + static std::vector> collect_layers_to_print(const Print &print); LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. - const std::vector &layers, + const ObjectsLayerToPrint &layers, const LayerTools &layer_tools, const bool last_layer, // Pairs of PrintObject index and its instance index. @@ -258,18 +259,18 @@ private: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. void process_layers( - const Print &print, - const ToolOrdering &tool_ordering, - const std::vector &print_object_instances_ordering, - const std::vector>> &layers_to_print, - GCodeOutputStream &output_stream); + const Print &print, + const ToolOrdering &tool_ordering, + const std::vector &print_object_instances_ordering, + const std::vector> &layers_to_print, + GCodeOutputStream &output_stream); // Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. void process_layers( const Print &print, const ToolOrdering &tool_ordering, - std::vector layers_to_print, + ObjectsLayerToPrint layers_to_print, const size_t single_object_idx, GCodeOutputStream &output_stream); @@ -283,71 +284,43 @@ private: std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.); std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.); - // Extruding multiple objects with soluble / non-soluble / combined supports - // on a multi-material printer, trying to minimize tool switches. - // Following structures sort extrusions by the extruder ID, by an order of objects and object islands. - struct ObjectByExtruder + struct InstanceToPrint { - ObjectByExtruder() : support(nullptr), support_extrusion_role(erNone) {} - const ExtrusionEntityCollection *support; - // erSupportMaterial / erSupportMaterialInterface or erMixed. - ExtrusionRole support_extrusion_role; + InstanceToPrint(size_t object_layer_to_print_id, const PrintObject &print_object, size_t instance_id) : + object_layer_to_print_id(object_layer_to_print_id), print_object(print_object), instance_id(instance_id) {} - struct Island - { - struct Region { - // Non-owned references to LayerRegion::perimeters::entities - // std::vector would be better here, but there is no way in C++ to convert from std::vector std::vector without copying. - ExtrusionEntitiesPtr perimeters; - // Non-owned references to LayerRegion::fills::entities - ExtrusionEntitiesPtr infills; - - std::vector infills_overrides; - std::vector perimeters_overrides; - - enum Type { - PERIMETERS, - INFILL, - }; - - // Appends perimeter/infill entities and writes don't indices of those that are not to be extruder as part of perimeter/infill wiping - void append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copy_extruders); - }; - - - std::vector by_region; // all extrusions for this island, grouped by regions - - // Fills in by_region_per_copy_cache and returns its reference. - const std::vector& by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities = false) const; - }; - std::vector islands; + // Index into std::vector, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. + const size_t object_layer_to_print_id; + const PrintObject &print_object; + // Instance idx of the copy of a print object. + const size_t instance_id; }; - struct InstanceToPrint - { - InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) : - object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {} + std::vector sort_print_object_instances( + // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx); - // Repository - ObjectByExtruder &object_by_extruder; - // Index into std::vector, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. - const size_t layer_id; - const PrintObject &print_object; - // Instance idx of the copy of a print object. - const size_t instance_id; - }; + // This function will be called for each printing extruder, possibly twice: First for wiping extrusions, second for normal extrusions. + void process_layer_single_object( + // output + std::string &gcode, + // Index of the extruder currently active. + const unsigned int extruder_id, + // What object and instance is going to be printed. + const InstanceToPrint &print_instance, + // and the object & support layer of the above. + const ObjectLayerToPrint &layer_to_print, + // Container for extruder overrides (when wiping into object or infill). + const LayerTools &layer_tools, + // Is any extrusion possibly marked as wiping extrusion? + const bool is_anything_overridden, + // Round 1 (wiping into object or infill) or round 2 (normal extrusions). + const bool print_wipe_extrusions); - std::vector sort_print_object_instances( - std::vector &objects_by_extruder, - // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. - const std::vector &layers, - // Ordering must be defined for normal (non-sequential print). - const std::vector *ordering, - // For sequential print, the instance of the object to be printing has to be defined. - const size_t single_object_instance_idx); - - std::string extrude_perimeters(const Print &print, const std::vector &by_region); - std::string extrude_infill(const Print &print, const std::vector &by_region, bool ironing); std::string extrude_support(const ExtrusionEntityCollection &support_fills); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); @@ -440,18 +413,6 @@ private: // To control print speed of 1st object layer over raft interface. bool object_layer_over_raft() const { return m_object_layer_over_raft; } - friend ObjectByExtruder& object_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects); - friend std::vector& object_islands_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects, - size_t num_islands); - friend class Wipe; friend class WipeTowerIntegration; friend class PressureEqualizer; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 9edb35ee8e..c866e13e4f 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -487,7 +487,7 @@ static float get_perimeter_spacing(const Layer &layer) size_t regions_count = 0; float perimeter_spacing = 0.f; for (const LayerRegion *layer_region : layer.regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { + if (layer_region != nullptr && ! layer_region->slices().empty()) { perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); ++regions_count; } @@ -508,7 +508,7 @@ static float get_perimeter_spacing_external(const Layer &layer) for (const PrintObject *object : layer.object()->print()->objects()) if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) for (const LayerRegion *layer_region : l->regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { + if (layer_region != nullptr && ! layer_region->slices().empty()) { perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); ++ regions_count; } @@ -527,7 +527,7 @@ static float get_external_perimeter_width(const Layer &layer) size_t regions_count = 0; float perimeter_width = 0.f; for (const LayerRegion *layer_region : layer.regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { + if (layer_region != nullptr && ! layer_region->slices().empty()) { perimeter_width += float(layer_region->flow(frExternalPerimeter).scaled_width()); ++regions_count; } @@ -1070,14 +1070,14 @@ static ExPolygons get_boundary(const Layer &layer) // Collect all top layers that will not be crossed. size_t polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) + for (const Surface &surface : layer_region->fill_surfaces()) if (surface.is_top()) ++polygons_count; if (polygons_count > 0) { ExPolygons top_layer_polygons; top_layer_polygons.reserve(polygons_count); for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) + for (const Surface &surface : layer_region->fill_surfaces()) if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); top_layer_polygons = union_ex(top_layer_polygons); diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index ef0d63c93a..f8e1dc6d75 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -14,6 +14,8 @@ #include +#include + namespace Slic3r { CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) @@ -339,12 +341,13 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: // for a sequence of extrusion moves. size_t active_speed_modifier = size_t(-1); + std::vector new_pos; for (; *line_start != 0; line_start = line_end) { while (*line_end != '\n' && *line_end != 0) ++ line_end; // sline will not contain the trailing '\n'. - std::string sline(line_start, line_end); + std::string_view sline(line_start, line_end - line_start); // CoolingLine will contain the trailing '\n'. if (*line_end == '\n') ++ line_end; @@ -358,20 +361,18 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: if (line.type) { // G0, G1 or G92 // Parse the G-code line. - std::vector new_pos(current_pos); - const char *c = sline.data() + 3; - for (;;) { + new_pos = current_pos; + for (auto c = sline.begin() + 3;;) { // Skip whitespaces. - for (; *c == ' ' || *c == '\t'; ++ c); - if (*c == 0 || *c == ';') + for (; c != sline.end() && (*c == ' ' || *c == '\t'); ++ c); + if (c == sline.end() || *c == ';') break; - assert(is_decimal_separator_point()); // for atof // Parse the axis. size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); if (axis != size_t(-1)) { - new_pos[axis] = float(atof(++c)); + auto [pend, ec] = fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]); if (axis == 4) { // Convert mm/min to mm/sec. new_pos[4] /= 60.f; @@ -381,7 +382,7 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: } } // Skip this word. - for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); + for (; c != sline.end() && *c != ' ' && *c != '\t'; ++ c); } bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); bool wipe = boost::contains(sline, ";_WIPE"); @@ -460,7 +461,8 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: } active_speed_modifier = size_t(-1); } else if (boost::starts_with(sline, m_toolchange_prefix)) { - unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size()); + unsigned int new_extruder; + auto res = std::from_chars(sline.data() + m_toolchange_prefix.size(), sline.data() + sline.size(), new_extruder); // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored. if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { if (new_extruder != current_extruder) { @@ -485,10 +487,15 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: line.type = CoolingLine::TYPE_G4; size_t pos_S = sline.find('S', 3); size_t pos_P = sline.find('P', 3); - assert(is_decimal_separator_point()); // for atof - line.time = line.time_max = float( - (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : - (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); + bool has_S = pos_S > 0; + bool has_P = pos_P > 0; + if (has_S || has_P) { + auto [pend, ec] = fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time); + if (has_P) + line.time *= 0.001f; + } else + line.time = 0; + line.time_max = line.time; } if (line.type != 0) adjustment->lines.emplace_back(std::move(line)); @@ -778,7 +785,8 @@ std::string CoolingBuffer::apply_layer_cooldown( if (line_start > pos) new_gcode.append(pos, line_start - pos); if (line->type & CoolingLine::TYPE_SET_TOOL) { - unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size()); + unsigned int new_extruder; + auto res = std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; change_extruder_set_fan(); @@ -804,7 +812,10 @@ std::string CoolingBuffer::apply_layer_cooldown( // Remove the F word from the current G-code line. bool remove = false; assert(fpos != nullptr); - new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos); + if (line->slowdown) + new_feedrate = int(floor(60. * line->feedrate + 0.5)); + else + auto res = std::from_chars(fpos, line_end, new_feedrate); if (new_feedrate == current_feedrate) { // No need to change the F value. if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_ADJUSTABLE_EMPTY | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index c82fd38e59..d9492cb2a7 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2735,8 +2735,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (volume_extruded_filament != 0.) m_used_filaments.increase_caches(volume_extruded_filament, - this->m_extruder_id, area_filament_cross_section * this->m_parking_position, - area_filament_cross_section * this->m_extra_loading_move); + m_extruder_id, area_filament_cross_section * m_parking_position, + area_filament_cross_section * m_extra_loading_move); const EMoveType type = move_type(delta_pos); if (type == EMoveType::Extrude) { @@ -4303,7 +4303,7 @@ void GCodeProcessor::process_filaments(CustomGCode::Type code) m_used_filaments.process_color_change_cache(); if (code == CustomGCode::ToolChange) - m_used_filaments.process_extruder_cache(this->m_extruder_id); + m_used_filaments.process_extruder_cache(m_extruder_id); } void GCodeProcessor::simulate_st_synchronize(float additional_time) diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index 7135c0a5c0..c8a9618efe 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -690,7 +690,7 @@ inline bool is_just_line_with_extrude_set_speed_tag(const std::string &line) void PressureEqualizer::push_line_to_output(const size_t line_idx, const float new_feedrate, const char *comment) { - const GCodeLine &line = this->m_gcode_lines[line_idx]; + const GCodeLine &line = m_gcode_lines[line_idx]; if (line_idx > 0 && output_buffer_length > 0) { const std::string prev_line_str = std::string(output_buffer.begin() + int(this->output_buffer_prev_length), output_buffer.begin() + int(this->output_buffer_length) + 1); diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index a86411519f..9a01dc8a6c 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -113,8 +113,8 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object break; BoundingBoxf bbox_this; for (const LayerRegion *layerm : layer->regions()) { - bbox_this.merge(extrusionentity_extents(layerm->perimeters)); - for (const ExtrusionEntity *ee : layerm->fills.entities) + bbox_this.merge(extrusionentity_extents(layerm->perimeters())); + for (const ExtrusionEntity *ee : layerm->fills()) // fill represents infill extrusions of a single island. bbox_this.merge(extrusionentity_extents(*dynamic_cast(ee))); } diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 1d7fd5b3c6..c92ff82121 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -401,7 +401,7 @@ struct GlobalModelInfo { Polygons extract_perimeter_polygons(const Layer *layer, std::vector &corresponding_regions_out) { Polygons polygons; for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters()) { if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { ExtrusionRole role = perimeter->role(); @@ -1060,7 +1060,7 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { size_t regions_with_perimeter = 0; for (const LayerRegion *region : po->layers()[layer_idx]->regions()) { - if (region->perimeters.entities.size() > 0) { + if (region->perimeters().size() > 0) { regions_with_perimeter++; } }; diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index c5554c2fa1..5e14035d78 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -14,8 +14,9 @@ #include #include -#include +#include +#include namespace Slic3r { @@ -103,7 +104,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude } double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height); - // Collect extruders reuqired to print the layers. + // Collect extruders required to print the layers. this->collect_extruders(object, std::vector>()); // Reorder the extruders to minimize tool switches. @@ -187,6 +188,21 @@ void ToolOrdering::initialize_layers(std::vector &zs) } } +// Decides whether this entity could be overridden +[[nodiscard]] static bool is_overriddable(const ExtrusionEntityCollection& eec, const LayerTools& lt, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) +{ + if (print_config.filament_soluble.get_at(lt.extruder(eec, region))) + return false; + + if (object.config().wipe_into_objects) + return true; + + if (!region.config().wipe_into_infill || eec.role() != erInternalInfill) + return false; + + return true; +} + // Collect extruders reuqired to print layers. void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches) { @@ -226,18 +242,20 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto for (const LayerRegion *layerm : layer->regions()) { const PrintRegion ®ion = layerm->region(); - if (! layerm->perimeters.entities.empty()) { + if (! layerm->perimeters().empty()) { bool something_nonoverriddable = true; if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors) something_nonoverriddable = false; - for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities - if (!layer_tools.wiping_extrusions().is_overriddable_and_mark(dynamic_cast(*eec), *m_print_config_ptr, object, region)) + for (const ExtrusionEntity *eec : layerm->perimeters()) // let's check if there are nonoverriddable entities + if (is_overriddable(dynamic_cast(*eec), layer_tools, *m_print_config_ptr, object, region)) + layer_tools.wiping_extrusions_nonconst().set_something_overridable(); + else something_nonoverriddable = true; } if (something_nonoverriddable) - layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().perimeter_extruder.value : extruder_override); + layer_tools.extruders.emplace_back(extruder_override == 0 ? region.config().perimeter_extruder.value : extruder_override); layer_tools.has_object = true; } @@ -245,7 +263,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto bool has_infill = false; bool has_solid_infill = false; bool something_nonoverriddable = false; - for (const ExtrusionEntity *ee : layerm->fills.entities) { + for (const ExtrusionEntity *ee : layerm->fills()) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role(); @@ -255,7 +273,9 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto has_infill = true; if (m_print_config_ptr) { - if (! layer_tools.wiping_extrusions().is_overriddable_and_mark(*fill, *m_print_config_ptr, object, region)) + if (is_overriddable(*fill, layer_tools, *m_print_config_ptr, object, region)) + layer_tools.wiping_extrusions_nonconst().set_something_overridable(); + else something_nonoverriddable = true; } } @@ -598,23 +618,23 @@ const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const // This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual) void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies) { - something_overridden = true; + m_something_overridden = true; - auto entity_map_it = (entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator + auto entity_map_it = (m_entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator ExtruderPerCopy& copies_vector = entity_map_it->second; copies_vector.resize(num_of_copies, -1); + assert(copies_vector[copy_id] == -1); if (copies_vector[copy_id] != -1) - std::cout << "ERROR: Entity extruder overriden multiple times!!!\n"; // A debugging message - this must never happen. + BOOST_LOG_TRIVIAL(error) << "ERROR: Entity extruder overriden multiple times!!!"; copies_vector[copy_id] = extruder; } // Finds first non-soluble extruder on the layer -int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const +[[nodiscard]] static int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config, const LayerTools& layer_tools) { - const LayerTools& lt = *m_layer_tools; - for (auto extruders_it = lt.extruders.begin(); extruders_it != lt.extruders.end(); ++extruders_it) + for (auto extruders_it = layer_tools.extruders.begin(); extruders_it != layer_tools.extruders.end(); ++extruders_it) if (!print_config.filament_soluble.get_at(*extruders_it)) return (*extruders_it); @@ -622,44 +642,33 @@ int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& prin } // Finds last non-soluble extruder on the layer -int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const +[[nodiscard]] static int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config, const LayerTools& layer_tools) { - const LayerTools& lt = *m_layer_tools; - for (auto extruders_it = lt.extruders.rbegin(); extruders_it != lt.extruders.rend(); ++extruders_it) + for (auto extruders_it = layer_tools.extruders.rbegin(); extruders_it != layer_tools.extruders.rend(); ++extruders_it) if (!print_config.filament_soluble.get_at(*extruders_it)) return (*extruders_it); return (-1); } -// Decides whether this entity could be overridden -bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const -{ - if (print_config.filament_soluble.get_at(m_layer_tools->extruder(eec, region))) - return false; - - if (object.config().wipe_into_objects) - return true; - - if (!region.config().wipe_into_infill || eec.role() != erInternalInfill) - return false; - - return true; -} - // Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange // and returns volume that is left to be wiped on the wipe tower. -float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe) +// Switching from old_extruder to new_extruder, trying to wipe volume_to_wipe into not yet extruded extrusions, that may change material (overridable). +float WipingExtrusions::mark_wiping_extrusions(const Print& print, const LayerTools <, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe) { - const LayerTools& lt = *m_layer_tools; const float min_infill_volume = 0.f; // ignore infill with smaller volume than this - if (! this->something_overridable || volume_to_wipe <= 0. || print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder)) - return std::max(0.f, volume_to_wipe); // Soluble filament cannot be wiped in a random infill, neither the filament after it + if (! m_something_overridable || volume_to_wipe <= 0. || + // Don't wipe a soluble filament into another object. + print.config().filament_soluble.get_at(old_extruder) || + // Don't prime a soluble filament into another object. + print.config().filament_soluble.get_at(new_extruder)) + // Soluble filament cannot be wiped in a random infill, neither the filament after it + return std::max(0.f, volume_to_wipe); // we will sort objects so that dedicated for wiping are at the beginning: ConstPrintObjectPtrs object_list(print.objects().begin(), print.objects().end()); - std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects; }); + std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects && ! b->config().wipe_into_objects; }); // We will now iterate through // - first the dedicated objects to mark perimeters or infills (depending on infill_first) @@ -692,10 +701,10 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int bool wipe_into_infill_only = ! object->config().wipe_into_objects && region.config().wipe_into_infill; if (print.config().infill_first != perimeters_done || wipe_into_infill_only) { - for (const ExtrusionEntity* ee : layerm->fills.entities) { // iterate through all infill Collections + for (const ExtrusionEntity* ee : layerm->fills()) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); - if (!is_overriddable(*fill, print.config(), *object, region)) + if (!is_overriddable(*fill, lt, print.config(), *object, region)) continue; if (wipe_into_infill_only && ! print.config().infill_first) @@ -716,9 +725,9 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int // Now the same for perimeters - see comments above for explanation: if (object->config().wipe_into_objects && print.config().infill_first == perimeters_done) { - for (const ExtrusionEntity* ee : layerm->perimeters.entities) { + for (const ExtrusionEntity* ee : layerm->perimeters()) { auto* fill = dynamic_cast(ee); - if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) { + if (is_overriddable(*fill, lt, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) { set_extruder_override(fill, copy, new_extruder, num_of_copies); if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f) // More material was purged already than asked for. @@ -740,14 +749,13 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int // that were not actually overridden. If they are part of a dedicated object, printing them with the extruder // they were initially assigned to might mean violating the perimeter-infill order. We will therefore go through // them again and make sure we override it. -void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) +void WipingExtrusions::ensure_perimeters_infills_order(const Print& print, const LayerTools <) { - if (! this->something_overridable) + if (! m_something_overridable) return; - const LayerTools& lt = *m_layer_tools; - unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config()); - unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config()); + unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config(), lt); + unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config(), lt); for (const PrintObject* object : print.objects()) { // Finds this layer: @@ -762,10 +770,11 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) if (!region.config().wipe_into_infill && !object->config().wipe_into_objects) continue; - for (const ExtrusionEntity* ee : layerm->fills.entities) { // iterate through all infill Collections + for (const ExtrusionEntity* ee : layerm->fills()) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); + assert(fill); - if (!is_overriddable(*fill, print.config(), *object, region) + if (!is_overriddable(*fill, lt, print.config(), *object, region) || is_entity_overridden(fill, copy) ) continue; @@ -775,8 +784,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) // Either way, we will now force-override it with something suitable: if (print.config().infill_first || object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely - || lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints - || ! lt.has_extruder(lt.infill_extruder(region)))) // we have to force override - this could violate infill_first (FIXME) + || lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder) // !infill_first, but perimeter is already printed when last extruder prints + || ! lt.has_extruder(lt.infill_extruder(region))) // we have to force override - this could violate infill_first (FIXME) set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies); else { // In this case we can (and should) leave it to be printed normally. @@ -785,9 +794,10 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) } // Now the same for perimeters - see comments above for explanation: - for (const ExtrusionEntity* ee : layerm->perimeters.entities) { // iterate through all perimeter Collections + for (const ExtrusionEntity* ee : layerm->perimeters()) { // iterate through all perimeter Collections auto* fill = dynamic_cast(ee); - if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, copy)) + assert(fill); + if (is_overriddable(*fill, lt, print.config(), *object, region) && ! is_entity_overridden(fill, copy)) set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); } } @@ -795,24 +805,4 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) } } -// Following function is called from GCode::process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity. -// If this extrusion does not have any override, nullptr is returned. -// Otherwise it modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known, -// so -1 was used as "print as usual"). -// The resulting vector therefore keeps track of which extrusions are the ones that were overridden and which were not. If the extruder used is overridden, -// its number is saved as is (zero-based index). Regular extrusions are saved as -number-1 (unfortunately there is no negative zero). -const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies) -{ - ExtruderPerCopy *overrides = nullptr; - auto entity_map_it = entity_map.find(entity); - if (entity_map_it != entity_map.end()) { - overrides = &entity_map_it->second; - overrides->resize(num_of_copies, -1); - // Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information): - std::replace(overrides->begin(), overrides->end(), -1, -correct_extruder_id-1); - } - return overrides; -} - - } // namespace Slic3r diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 0367303258..4fb6e56c06 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -6,6 +6,7 @@ #include "../libslic3r.h" #include +#include #include @@ -14,6 +15,7 @@ namespace Slic3r { class Print; class PrintObject; class LayerTools; +class ToolOrdering; namespace CustomGCode { struct Item; } class PrintRegion; @@ -24,54 +26,51 @@ class WipingExtrusions { public: bool is_anything_overridden() const { // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case - return something_overridden; + return m_something_overridden; } // When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place. - typedef boost::container::small_vector ExtruderPerCopy; + using ExtruderPerCopy = +#ifdef NDEBUG + boost::container::small_vector; +#else // NDEBUG + std::vector; +#endif // NDEBUG - // This is called from GCode::process_layer - see implementation for further comments: - const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies); + // This is called from GCode::process_layer_single_object() + // Returns positive number if the extruder is overridden. + // Returns -1 if not. + int get_extruder_override(const ExtrusionEntity* entity, uint32_t instance_id) const { + auto entity_map_it = m_entity_map.find(entity); + return entity_map_it == m_entity_map.end() ? -1 : entity_map_it->second[instance_id]; + } // This function goes through all infill entities, decides which ones will be used for wiping and // marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower: - float mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe); + float mark_wiping_extrusions(const Print& print, const LayerTools& lt, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe); - void ensure_perimeters_infills_order(const Print& print); + void ensure_perimeters_infills_order(const Print& print, const LayerTools& lt); - bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const; - bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) { - bool out = this->is_overriddable(ee, print_config, object, region); - this->something_overridable |= out; - return out; - } - - void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; } + void set_something_overridable() { m_something_overridable = true; } private: - int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const; - int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const; - // This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual) void set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies); // Returns true in case that entity is not printed with its usual extruder for a given copy: bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const { - auto it = entity_map.find(entity); - return it == entity_map.end() ? false : it->second[copy_id] != -1; + auto it = m_entity_map.find(entity); + return it == m_entity_map.end() ? false : it->second[copy_id] != -1; } - std::map entity_map; // to keep track of who prints what - bool something_overridable = false; - bool something_overridden = false; - const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to + std::map m_entity_map; // to keep track of who prints what + bool m_something_overridable = false; + bool m_something_overridden = false; }; class LayerTools { public: - LayerTools(const coordf_t z) : print_z(z) {} - // Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other. // In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports). bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; } @@ -109,12 +108,14 @@ public: // Custom G-code (color change, extruder switch, pause) to be performed before this layer starts to print. const CustomGCode::Item *custom_gcode = nullptr; - WipingExtrusions& wiping_extrusions() { - m_wiping_extrusions.set_layer_tools_ptr(this); - return m_wiping_extrusions; - } + WipingExtrusions& wiping_extrusions_nonconst() { return m_wiping_extrusions; } + const WipingExtrusions& wiping_extrusions() const { return m_wiping_extrusions; } private: + // to access LayerTools private constructor + friend class ToolOrdering; + LayerTools(const coordf_t z) : print_z(z) {} + // This object holds list of extrusion that will be used for extruder wiping WipingExtrusions m_wiping_extrusions; }; diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index cd6cedf515..a45ea8439e 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -70,7 +70,7 @@ const char* GCodeReader::parse_line_internal(const char *ptr, const char *end, G if (axis != NUM_AXES_WITH_UNKNOWN) { // Try to parse the numeric value. double v; - c = skip_whitespaces(++c); + c = skip_whitespaces(++ c); auto [pend, ec] = fast_float::from_chars(c, end, v); if (pend != c && is_end_of_word(*pend)) { // The axis value has been parsed correctly. diff --git a/src/libslic3r/Geometry/MedialAxis.cpp b/src/libslic3r/Geometry/MedialAxis.cpp index ce38a6c705..c92796f41f 100644 --- a/src/libslic3r/Geometry/MedialAxis.cpp +++ b/src/libslic3r/Geometry/MedialAxis.cpp @@ -1,6 +1,7 @@ #include "MedialAxis.hpp" #include "clipper.hpp" +#include "VoronoiOffset.hpp" #ifdef SLIC3R_DEBUG namespace boost { namespace polygon { @@ -392,8 +393,7 @@ inline const typename VD::point_type retrieve_cell_point(const typename VD::cell } template -inline std::pair -measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments) +inline std::pair measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments) { typedef typename VD::coord_type T; const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y()); @@ -442,15 +442,21 @@ private: const Lines &lines; }; -void -MedialAxis::build(ThickPolylines* polylines) +MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expolygon) : + m_expolygon(expolygon), m_lines(expolygon.lines()), m_min_width(min_width), m_max_width(max_width) +{} + +void MedialAxis::build(ThickPolylines* polylines) { - construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd); + construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd); + Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines); +// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees +// std::vector skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); /* // DEBUG: dump all Voronoi edges { - for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { + for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); ++edge) { if (edge->is_infinite()) continue; ThickPolyline polyline; @@ -463,73 +469,59 @@ MedialAxis::build(ThickPolylines* polylines) */ //typedef const VD::vertex_type vert_t; - typedef const VD::edge_type edge_t; + using edge_t = const VD::edge_type; // collect valid edges (i.e. prune those not belonging to MAT) // note: this keeps twins, so it inserts twice the number of the valid edges - this->valid_edges.clear(); - { - std::set seen_edges; - for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { - // if we only process segments representing closed loops, none if the - // infinite edges (if any) would be part of our MAT anyway - if (edge->is_secondary() || edge->is_infinite()) continue; - - // don't re-validate twins - if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed? - seen_edges.insert(&*edge); - seen_edges.insert(edge->twin()); - - if (!this->validate_edge(&*edge)) continue; - this->valid_edges.insert(&*edge); - this->valid_edges.insert(edge->twin()); + m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{}); + for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); edge += 2) + if (edge->is_primary() && edge->is_finite() && + (Voronoi::vertex_category(edge->vertex0()) == Voronoi::VertexCategory::Inside || + Voronoi::vertex_category(edge->vertex1()) == Voronoi::VertexCategory::Inside) && + this->validate_edge(&*edge)) { + // Valid skeleton edge. + this->edge_data(*edge).first.active = true; } - } - this->edges = this->valid_edges; // iterate through the valid edges to build polylines - while (!this->edges.empty()) { - const edge_t* edge = *this->edges.begin(); + ThickPolyline reverse_polyline; + for (VD::const_edge_iterator seed_edge = m_vd.edges().begin(); seed_edge != m_vd.edges().end(); seed_edge += 2) + if (EdgeData &seed_edge_data = this->edge_data(*seed_edge).first; seed_edge_data.active) { + // Mark this edge as visited. + seed_edge_data.active = false; + + // Start a polyline. + ThickPolyline polyline; + polyline.points.emplace_back(seed_edge->vertex0()->x(), seed_edge->vertex0()->y()); + polyline.points.emplace_back(seed_edge->vertex1()->x(), seed_edge->vertex1()->y()); + polyline.width.emplace_back(seed_edge_data.width_start); + polyline.width.emplace_back(seed_edge_data.width_end); + // Grow the polyline in a forward direction. + this->process_edge_neighbors(&*seed_edge, &polyline); - // start a polyline - ThickPolyline polyline; - polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); - polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); - polyline.width.push_back(this->thickness[edge].first); - polyline.width.push_back(this->thickness[edge].second); + // Grow the polyline in a backward direction. + reverse_polyline.clear(); + this->process_edge_neighbors(seed_edge->twin(), &reverse_polyline); + polyline.points.insert(polyline.points.begin(), reverse_polyline.points.rbegin(), reverse_polyline.points.rend()); + polyline.width.insert(polyline.width.begin(), reverse_polyline.width.rbegin(), reverse_polyline.width.rend()); + polyline.endpoints.first = reverse_polyline.endpoints.second; - // remove this edge and its twin from the available edges - (void)this->edges.erase(edge); - (void)this->edges.erase(edge->twin()); + assert(polyline.width.size() == polyline.points.size() * 2 - 2); - // get next points - this->process_edge_neighbors(edge, &polyline); - - // get previous points - { - ThickPolyline rpolyline; - this->process_edge_neighbors(edge->twin(), &rpolyline); - polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend()); - polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend()); - polyline.endpoints.first = rpolyline.endpoints.second; + // Prevent loop endpoints from being extended. + if (polyline.first_point() == polyline.last_point()) { + polyline.endpoints.first = false; + polyline.endpoints.second = false; + } + + // Append polyline to result. + polylines->emplace_back(std::move(polyline)); } - - assert(polyline.width.size() == polyline.points.size()*2 - 2); - - // prevent loop endpoints from being extended - if (polyline.first_point() == polyline.last_point()) { - polyline.endpoints.first = false; - polyline.endpoints.second = false; - } - - // append polyline to result - polylines->push_back(polyline); - } #ifdef SLIC3R_DEBUG { static int iRun = 0; - dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str()); + dump_voronoi_to_svg(m_lines, m_vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str()); printf("Thick lines: "); for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) { ThickLines lines = it->thicklines(); @@ -542,56 +534,66 @@ MedialAxis::build(ThickPolylines* polylines) #endif /* SLIC3R_DEBUG */ } -void -MedialAxis::build(Polylines* polylines) +void MedialAxis::build(Polylines* polylines) { ThickPolylines tp; this->build(&tp); polylines->insert(polylines->end(), tp.begin(), tp.end()); } -void -MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline) +void MedialAxis::process_edge_neighbors(const VD::edge_type *edge, ThickPolyline* polyline) { - while (true) { + for (;;) { // Since rot_next() works on the edge starting point but we want // to find neighbors on the ending point, we just swap edge with // its twin. - const VD::edge_type* twin = edge->twin(); + const VD::edge_type *twin = edge->twin(); // count neighbors for this edge - std::vector neighbors; - for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin; - neighbor = neighbor->rot_next()) { - if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor); - } + size_t num_neighbors = 0; + const VD::edge_type *first_neighbor = nullptr; + for (const VD::edge_type *neighbor = twin->rot_next(); neighbor != twin; neighbor = neighbor->rot_next()) + if (this->edge_data(*neighbor).first.active) { + if (num_neighbors == 0) + first_neighbor = neighbor; + ++ num_neighbors; + } // if we have a single neighbor then we can continue recursively - if (neighbors.size() == 1) { - const VD::edge_type* neighbor = neighbors.front(); - - // break if this is a closed loop - if (this->edges.count(neighbor) == 0) return; - - Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y()); - polyline->points.push_back(new_point); - polyline->width.push_back(this->thickness[neighbor].first); - polyline->width.push_back(this->thickness[neighbor].second); - (void)this->edges.erase(neighbor); - (void)this->edges.erase(neighbor->twin()); - edge = neighbor; - } else if (neighbors.size() == 0) { + if (num_neighbors == 1) { + if (std::pair neighbor_data = this->edge_data(*first_neighbor); + neighbor_data.first.active) { + neighbor_data.first.active = false; + polyline->points.emplace_back(first_neighbor->vertex1()->x(), first_neighbor->vertex1()->y()); + if (neighbor_data.second) { + polyline->width.push_back(neighbor_data.first.width_end); + polyline->width.push_back(neighbor_data.first.width_start); + } else { + polyline->width.push_back(neighbor_data.first.width_start); + polyline->width.push_back(neighbor_data.first.width_end); + } + edge = first_neighbor; + // Continue chaining. + continue; + } + } else if (num_neighbors == 0) { polyline->endpoints.second = true; - return; } else { - // T-shaped or star-shaped joint - return; + // T-shaped or star-shaped joint } + // Stop chaining. + break; } } bool MedialAxis::validate_edge(const VD::edge_type* edge) { + auto retrieve_segment = [this](const VD::cell_type* cell) -> const Line& { return m_lines[cell->source_index()]; }; + auto retrieve_endpoint = [retrieve_segment](const VD::cell_type* cell) -> const Point& { + const Line &line = retrieve_segment(cell); + return cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT ? line.a : line.b; + }; + // prevent overflows and detect almost-infinite edges #ifndef CLIPPERLIB_INT32 if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || @@ -602,32 +604,18 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge) #endif // CLIPPERLIB_INT32 // construct the line representing this edge of the Voronoi diagram - const Line line( - Point( edge->vertex0()->x(), edge->vertex0()->y() ), - Point( edge->vertex1()->x(), edge->vertex1()->y() ) - ); - - // discard edge if it lies outside the supplied shape - // this could maybe be optimized (checking inclusion of the endpoints - // might give false positives as they might belong to the contour itself) - if (this->expolygon != NULL) { - if (line.a == line.b) { - // in this case, contains(line) returns a false positive - if (!this->expolygon->contains(line.a)) return false; - } else { - if (!this->expolygon->contains(line)) return false; - } - } + const Line line({ edge->vertex0()->x(), edge->vertex0()->y() }, + { edge->vertex1()->x(), edge->vertex1()->y() }); // retrieve the original line segments which generated the edge we're checking const VD::cell_type* cell_l = edge->cell(); const VD::cell_type* cell_r = edge->twin()->cell(); - const Line &segment_l = this->retrieve_segment(cell_l); - const Line &segment_r = this->retrieve_segment(cell_r); + const Line &segment_l = retrieve_segment(cell_l); + const Line &segment_r = retrieve_segment(cell_r); /* SVG svg("edge.svg"); - svg.draw(*this->expolygon); + svg.draw(m_expolygon); svg.draw(line); svg.draw(segment_l, "red"); svg.draw(segment_r, "blue"); @@ -651,30 +639,30 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge) coordf_t w0 = cell_r->contains_segment() ? segment_r.distance_to(line.a)*2 - : (this->retrieve_endpoint(cell_r) - line.a).cast().norm()*2; + : (retrieve_endpoint(cell_r) - line.a).cast().norm()*2; coordf_t w1 = cell_l->contains_segment() ? segment_l.distance_to(line.b)*2 - : (this->retrieve_endpoint(cell_l) - line.b).cast().norm()*2; + : (retrieve_endpoint(cell_l) - line.b).cast().norm()*2; if (cell_l->contains_segment() && cell_r->contains_segment()) { // calculate the relative angle between the two boundary segments double angle = fabs(segment_r.orientation() - segment_l.orientation()); - if (angle > PI) angle = 2*PI - angle; + if (angle > PI) + angle = 2. * PI - angle; assert(angle >= 0 && angle <= PI); - + // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) // we're interested only in segments close to the second case (facing segments) // so we allow some tolerance. // this filter ensures that we're dealing with a narrow/oriented area (longer than thick) // we don't run it on edges not generated by two segments (thus generated by one segment // and the endpoint of another segment), since their orientation would not be meaningful - if (PI - angle > PI/8) { + if (PI - angle > PI / 8.) { // angle is not narrow enough - // only apply this filter to segments that are not too short otherwise their // angle could possibly be not meaningful - if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width) + if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= m_min_width) return false; } } else { @@ -682,31 +670,17 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge) return false; } - if (w0 < this->min_width && w1 < this->min_width) - return false; - - if (w0 > this->max_width && w1 > this->max_width) - return false; - - this->thickness[edge] = std::make_pair(w0, w1); - this->thickness[edge->twin()] = std::make_pair(w1, w0); - - return true; -} - -const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const -{ - return this->lines[cell->source_index()]; -} - -const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const -{ - const Line& line = this->retrieve_segment(cell); - if (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { - return line.a; - } else { - return line.b; + if ((w0 >= m_min_width || w1 >= m_min_width) && + (w0 <= m_max_width || w1 <= m_max_width)) { + std::pair ed = this->edge_data(*edge); + if (ed.second) + std::swap(w0, w1); + ed.first.width_start = w0; + ed.first.width_end = w1; + return true; } + + return false; } } } // namespace Slicer::Geometry diff --git a/src/libslic3r/Geometry/MedialAxis.hpp b/src/libslic3r/Geometry/MedialAxis.hpp index cfbb5f0805..b1354ddb2f 100644 --- a/src/libslic3r/Geometry/MedialAxis.hpp +++ b/src/libslic3r/Geometry/MedialAxis.hpp @@ -4,30 +4,43 @@ #include "Voronoi.hpp" #include "../ExPolygon.hpp" -namespace Slic3r { namespace Geometry { +namespace Slic3r::Geometry { class MedialAxis { public: - Lines lines; - const ExPolygon* expolygon; - double max_width; - double min_width; - MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL) - : expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {}; + MedialAxis(double min_width, double max_width, const ExPolygon &expolygon); void build(ThickPolylines* polylines); void build(Polylines* polylines); private: + // Input + const ExPolygon &m_expolygon; + Lines m_lines; + // for filtering of the skeleton edges + double m_min_width; + double m_max_width; + + // Voronoi Diagram. using VD = VoronoiDiagram; - VD vd; - std::set edges, valid_edges; - std::map > thickness; + VD m_vd; + + // Annotations of the VD skeleton edges. + struct EdgeData { + bool active { false }; + double width_start { 0 }; + double width_end { 0 }; + }; + // Returns a reference to EdgeData and a "reversed" boolean. + std::pair edge_data(const VD::edge_type &edge) { + size_t edge_id = &edge - &m_vd.edges().front(); + return { m_edge_data[edge_id / 2], (edge_id & 1) != 0 }; + } + std::vector m_edge_data; + void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline); bool validate_edge(const VD::edge_type* edge); - const Line& retrieve_segment(const VD::cell_type* cell) const; - const Point& retrieve_endpoint(const VD::cell_type* cell) const; }; -} } // namespace Slicer::Geometry +} // namespace Slicer::Geometry #endif // slic3r_Geometry_MedialAxis_hpp_ diff --git a/src/libslic3r/Geometry/VoronoiOffset.hpp b/src/libslic3r/Geometry/VoronoiOffset.hpp index 359fe010c8..fa72ae2bc2 100644 --- a/src/libslic3r/Geometry/VoronoiOffset.hpp +++ b/src/libslic3r/Geometry/VoronoiOffset.hpp @@ -1,4 +1,4 @@ -// Polygon offsetting using Voronoi diagram prodiced by boost::polygon. +// Polygon offsetting using Voronoi diagram produced by boost::polygon. #ifndef slic3r_VoronoiOffset_hpp_ #define slic3r_VoronoiOffset_hpp_ diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index d273fde963..2fa0c5c3ca 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -1,4 +1,5 @@ #include "Layer.hpp" +#include #include "ClipperUtils.hpp" #include "Print.hpp" #include "Fill/Fill.hpp" @@ -22,7 +23,7 @@ Layer::~Layer() bool Layer::empty() const { for (const LayerRegion *layerm : m_regions) - if (layerm != nullptr && ! layerm->slices.empty()) + if (layerm != nullptr && ! layerm->slices().empty()) // Non empty layer. return false; return true; @@ -40,11 +41,11 @@ void Layer::make_slices() ExPolygons slices; if (m_regions.size() == 1) { // optimization: if we only have one region, take its slices - slices = to_expolygons(m_regions.front()->slices.surfaces); + slices = to_expolygons(m_regions.front()->slices().surfaces); } else { Polygons slices_p; for (LayerRegion *layerm : m_regions) - polygons_append(slices_p, to_polygons(layerm->slices.surfaces)); + polygons_append(slices_p, to_polygons(layerm->slices().surfaces)); slices = union_safety_offset_ex(slices_p); } @@ -65,6 +66,289 @@ void Layer::make_slices() this->lslices.emplace_back(std::move(slices[i])); } +// used by Layer::build_up_down_graph() +[[nodiscard]] static ClipperLib_Z::Paths expolygons_to_zpaths(const ExPolygons &expolygons, coord_t isrc) +{ + size_t num_paths = 0; + for (const ExPolygon &expolygon : expolygons) + num_paths += expolygon.num_contours(); + + ClipperLib_Z::Paths out; + out.reserve(num_paths); + + for (const ExPolygon &expolygon : expolygons) { + for (size_t icontour = 0; icontour < expolygon.num_contours(); ++ icontour) { + const Polygon &contour = expolygon.contour_or_hole(icontour); + out.emplace_back(); + ClipperLib_Z::Path &path = out.back(); + path.reserve(contour.size()); + for (const Point &p : contour.points) + path.push_back({ p.x(), p.y(), isrc }); + } + ++ isrc; + } + + return out; +} + +// used by Layer::build_up_down_graph() +static void connect_layer_slices( + Layer &below, + Layer &above, + const ClipperLib_Z::PolyTree &polytree, + const std::vector> &intersections, + const coord_t offset_below, + const coord_t offset_above +#ifndef NDEBUG + , const coord_t offset_end +#endif // NDEBUG + ) +{ + class Visitor { + public: + Visitor(const std::vector> &intersections, + Layer &below, Layer &above, const coord_t offset_below, const coord_t offset_above +#ifndef NDEBUG + , const coord_t offset_end +#endif // NDEBUG + ) : + m_intersections(intersections), m_below(below), m_above(above), m_offset_below(offset_below), m_offset_above(offset_above) +#ifndef NDEBUG + , m_offset_end(offset_end) +#endif // NDEBUG + {} + + void visit(const ClipperLib_Z::PolyNode &polynode) + { +#ifndef NDEBUG + auto assert_intersection_valid = [this](int i, int j) { + assert(i != j); + if (i > j) + std::swap(i, j); + assert(i >= m_offset_below); + assert(i < m_offset_above); + assert(j >= m_offset_above); + assert(j < m_offset_end); + return true; + }; +#endif // NDEBUG + if (polynode.Contour.size() >= 3) { + // If there is an intersection point, it should indicate which contours (one from layer below, the other from layer above) intersect. + // Otherwise the contour is fully inside another contour. + int32_t i = 0, j = 0; + for (int icontour = 0; icontour <= polynode.ChildCount(); ++ icontour) { + const bool first = icontour == 0; + const ClipperLib_Z::Path &contour = first ? polynode.Contour : polynode.Childs[icontour - 1]->Contour; + if (contour.size() >= 3) { + if (first) { + i = contour.front().z(); + j = i; + if (i < 0) { + std::tie(i, j) = m_intersections[-i - 1]; + assert(assert_intersection_valid(i, j)); + goto end; + } + } + for (const ClipperLib_Z::IntPoint& pt : contour) { + j = pt.z(); + if (j < 0) { + std::tie(i, j) = m_intersections[-j - 1]; + assert(assert_intersection_valid(i, j)); + goto end; + } + else if (i != j) + goto end; + } + } + } + end: + bool found = false; + if (i == j) { + // The contour is completely inside another contour. + Point pt(polynode.Contour.front().x(), polynode.Contour.front().y()); + if (i < m_offset_above) { + // Index of an island below. Look-it up in the island above. + assert(i >= m_offset_below); + i -= m_offset_below; + for (int l = int(m_above.lslices_ex.size()) - 1; l >= 0; -- l) { + LayerSlice &lslice = m_above.lslices_ex[l]; + if (lslice.bbox.contains(pt) && m_above.lslices[l].contains(pt)) { + found = true; + j = l; + assert(i >= 0 && i < m_below.lslices_ex.size()); + assert(j >= 0 && j < m_above.lslices_ex.size()); + break; + } + } + } else { + // Index of an island above. Look-it up in the island below. + assert(j < m_offset_end); + j -= m_offset_above; + for (int l = int(m_below.lslices_ex.size()) - 1; l >= 0; -- l) { + LayerSlice &lslice = m_below.lslices_ex[l]; + if (lslice.bbox.contains(pt) && m_below.lslices[l].contains(pt)) { + found = true; + i = l; + assert(i >= 0 && i < m_below.lslices_ex.size()); + assert(j >= 0 && j < m_above.lslices_ex.size()); + break; + } + } + } + } else { + assert(assert_intersection_valid(i, j)); + if (i > j) + std::swap(i, j); + i -= m_offset_below; + j -= m_offset_above; + assert(i >= 0 && i < m_below.lslices_ex.size()); + assert(j >= 0 && j < m_above.lslices_ex.size()); + found = true; + } + if (found) { + // Subtract area of holes from the area of outer contour. + double area = ClipperLib_Z::Area(polynode.Contour); + for (int icontour = 0; icontour < polynode.ChildCount(); ++ icontour) + area -= ClipperLib_Z::Area(polynode.Childs[icontour]->Contour); + // Store the links and area into the contours. + LayerSlice::Links &links_below = m_below.lslices_ex[i].overlaps_above; + LayerSlice::Links &links_above = m_above.lslices_ex[j].overlaps_below; + LayerSlice::Link key{ j }; + auto it_below = std::lower_bound(links_below.begin(), links_below.end(), key, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; }); + if (it_below != links_below.end() && it_below->slice_idx == j) { + it_below->area += area; + } else { + auto it_above = std::lower_bound(links_above.begin(), links_above.end(), key, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; }); + if (it_above != links_above.end() && it_above->slice_idx == i) { + it_above->area += area; + } else { + // Insert into one of the two vectors. + bool take_below = false; + if (links_below.size() < LayerSlice::LinksStaticSize) + take_below = false; + else if (links_above.size() >= LayerSlice::LinksStaticSize) { + size_t shift_below = links_below.end() - it_below; + size_t shift_above = links_above.end() - it_above; + take_below = shift_below < shift_above; + } + if (take_below) + links_below.insert(it_below, { j, float(area) }); + else + links_above.insert(it_above, { i, float(area) }); + } + } + } + } + for (int i = 0; i < polynode.ChildCount(); ++ i) + for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++ j) + this->visit(*polynode.Childs[i]->Childs[j]); + } + + private: + const std::vector> &m_intersections; + Layer &m_below; + Layer &m_above; + const coord_t m_offset_below; + const coord_t m_offset_above; +#ifndef NDEBUG + const coord_t m_offset_end; +#endif // NDEBUG + } visitor(intersections, below, above, offset_below, offset_above +#ifndef NDEBUG + , offset_end +#endif // NDEBUG + ); + + for (int i = 0; i < polytree.ChildCount(); ++ i) + visitor.visit(*polytree.Childs[i]); + +#ifndef NDEBUG + // Verify that only one directional link is stored: either from bottom slice up or from upper slice down. + for (int32_t islice = 0; islice < below.lslices_ex.size(); ++ islice) { + LayerSlice::Links &links1 = below.lslices_ex[islice].overlaps_above; + for (LayerSlice::Link &link1 : links1) { + LayerSlice::Links &links2 = above.lslices_ex[link1.slice_idx].overlaps_below; + assert(! std::binary_search(links2.begin(), links2.end(), link1, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; })); + } + } + for (int32_t islice = 0; islice < above.lslices_ex.size(); ++ islice) { + LayerSlice::Links &links1 = above.lslices_ex[islice].overlaps_below; + for (LayerSlice::Link &link1 : links1) { + LayerSlice::Links &links2 = below.lslices_ex[link1.slice_idx].overlaps_above; + assert(! std::binary_search(links2.begin(), links2.end(), link1, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; })); + } + } +#endif // NDEBUG + + // Scatter the links, but don't sort them yet. + for (int32_t islice = 0; islice < below.lslices_ex.size(); ++ islice) + for (LayerSlice::Link &link : below.lslices_ex[islice].overlaps_above) + above.lslices_ex[link.slice_idx].overlaps_below.push_back({ islice, link.area }); + for (int32_t islice = 0; islice < above.lslices_ex.size(); ++ islice) + for (LayerSlice::Link &link : above.lslices_ex[islice].overlaps_below) + below.lslices_ex[link.slice_idx].overlaps_above.push_back({ islice, link.area }); + // Sort the links. + for (LayerSlice &lslice : below.lslices_ex) + std::sort(lslice.overlaps_above.begin(), lslice.overlaps_above.end(), [](const LayerSlice::Link &l, const LayerSlice::Link &r){ return l.slice_idx < r.slice_idx; }); + for (LayerSlice &lslice : above.lslices_ex) + std::sort(lslice.overlaps_below.begin(), lslice.overlaps_below.end(), [](const LayerSlice::Link &l, const LayerSlice::Link &r){ return l.slice_idx < r.slice_idx; }); +} + +void Layer::build_up_down_graph(Layer& below, Layer& above) +{ + coord_t paths_below_offset = 0; + ClipperLib_Z::Paths paths_below = expolygons_to_zpaths(below.lslices, paths_below_offset); + coord_t paths_above_offset = paths_below_offset + coord_t(below.lslices.size()); + ClipperLib_Z::Paths paths_above = expolygons_to_zpaths(above.lslices, paths_above_offset); +#ifndef NDEBUG + coord_t paths_end = paths_above_offset + coord_t(above.lslices.size()); +#endif // NDEBUG + + class ZFill { + public: + ZFill() = default; + void reset() { m_intersections.clear(); } + void operator()( + const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, + const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top, + ClipperLib_Z::IntPoint& pt) { + coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() }; + coord_t* begin = srcs; + coord_t* end = srcs + 4; + std::sort(begin, end); + end = std::unique(begin, end); + assert(begin + 2 == end); + if (begin + 1 == end) + pt.z() = *begin; + else if (begin + 2 <= end) { + // store a -1 based negative index into the "intersections" vector here. + m_intersections.emplace_back(srcs[0], srcs[1]); + pt.z() = -coord_t(m_intersections.size()); + } + } + const std::vector>& intersections() const { return m_intersections; } + + private: + std::vector> m_intersections; + } zfill; + + ClipperLib_Z::Clipper clipper; + ClipperLib_Z::PolyTree result; + clipper.ZFillFunction( + [&zfill](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, + const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) + { return zfill(e1bot, e1top, e2bot, e2top, pt); }); + clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true); + clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true); + clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + + connect_layer_slices(below, above, result, zfill.intersections(), paths_below_offset, paths_above_offset +#ifndef NDEBUG + , paths_end +#endif // NDEBUG + ); +} + static inline bool layer_needs_raw_backup(const Layer *layer) { return ! (layer->regions().size() == 1 && (layer->id() > 0 || layer->object()->config().elefant_foot_compensation.value == 0)); @@ -74,10 +358,10 @@ void Layer::backup_untyped_slices() { if (layer_needs_raw_backup(this)) { for (LayerRegion *layerm : m_regions) - layerm->raw_slices = to_expolygons(layerm->slices.surfaces); + layerm->m_raw_slices = to_expolygons(layerm->slices().surfaces); } else { assert(m_regions.size() == 1); - m_regions.front()->raw_slices.clear(); + m_regions.front()->m_raw_slices.clear(); } } @@ -85,10 +369,10 @@ void Layer::restore_untyped_slices() { if (layer_needs_raw_backup(this)) { for (LayerRegion *layerm : m_regions) - layerm->slices.set(layerm->raw_slices, stInternal); + layerm->m_slices.set(layerm->m_raw_slices, stInternal); } else { assert(m_regions.size() == 1); - m_regions.front()->slices.set(this->lslices, stInternal); + m_regions.front()->m_slices.set(this->lslices, stInternal); } } @@ -101,13 +385,13 @@ void Layer::restore_untyped_slices_no_extra_perimeters() if (layer_needs_raw_backup(this)) { for (LayerRegion *layerm : m_regions) if (! layerm->region().config().extra_perimeters.value) - layerm->slices.set(layerm->raw_slices, stInternal); + layerm->m_slices.set(layerm->m_raw_slices, stInternal); } else { assert(m_regions.size() == 1); LayerRegion *layerm = m_regions.front(); // This optimization is correct, as extra_perimeters are only reused by prepare_infill() with multi-regions. //if (! layerm->region().config().extra_perimeters.value) - layerm->slices.set(this->lslices, stInternal); + layerm->m_slices.set(this->lslices, stInternal); } } @@ -125,7 +409,7 @@ ExPolygons Layer::merged(float offset_scaled) const const PrintRegionConfig &config = layerm->region().config(); // Our users learned to bend Slic3r to produce empty volumes to act as subtracters. Only add the region if it is non-empty. if (config.bottom_solid_layers > 0 || config.top_solid_layers > 0 || config.fill_density > 0. || config.perimeters > 0) - append(polygons, offset(layerm->slices.surfaces, offset_scaled)); + append(polygons, offset(layerm->slices().surfaces, offset_scaled)); } ExPolygons out = union_ex(polygons); if (offset_scaled2 != 0.f) @@ -141,96 +425,333 @@ void Layer::make_perimeters() BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id(); // keep track of regions whose perimeters we have already generated - std::vector done(m_regions.size(), false); - - for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) - if ((*layerm)->slices.empty()) { - (*layerm)->perimeters.clear(); - (*layerm)->fills.clear(); - (*layerm)->thin_fills.clear(); - } else { - size_t region_id = layerm - m_regions.begin(); - if (done[region_id]) - continue; - BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; - done[region_id] = true; - const PrintRegionConfig &config = (*layerm)->region().config(); - - // find compatible regions - LayerRegionPtrs layerms; - layerms.push_back(*layerm); - for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) - if (! (*it)->slices.empty()) { - LayerRegion* other_layerm = *it; - const PrintRegionConfig &other_config = other_layerm->region().config(); - if (config.perimeter_extruder == other_config.perimeter_extruder - && config.perimeters == other_config.perimeters - && config.perimeter_speed == other_config.perimeter_speed - && config.external_perimeter_speed == other_config.external_perimeter_speed - && (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == - (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) - && config.overhangs == other_config.overhangs - && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") - && config.thin_walls == other_config.thin_walls - && config.external_perimeters_first == other_config.external_perimeters_first - && config.infill_overlap == other_config.infill_overlap - && config.fuzzy_skin == other_config.fuzzy_skin - && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness - && config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist) - { - other_layerm->perimeters.clear(); - other_layerm->fills.clear(); - other_layerm->thin_fills.clear(); - layerms.push_back(other_layerm); - done[it - m_regions.begin()] = true; - } - } - - if (layerms.size() == 1) { // optimization - (*layerm)->fill_surfaces.surfaces.clear(); - (*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces); - (*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces); - } else { - SurfaceCollection new_slices; - // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. - LayerRegion *layerm_config = layerms.front(); - { - // group slices (surfaces) according to number of extra perimeters - std::map slices; // extra_perimeters => [ surface, surface... ] - for (LayerRegion *layerm : layerms) { - for (const Surface &surface : layerm->slices.surfaces) - slices[surface.extra_perimeters].emplace_back(surface); - if (layerm->region().config().fill_density > layerm_config->region().config().fill_density) - layerm_config = layerm; - } - // merge the surfaces assigned to each group - for (std::pair &surfaces_with_extra_perimeters : slices) - new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front()); - } - - // make perimeters - SurfaceCollection fill_surfaces; - layerm_config->make_perimeters(new_slices, &fill_surfaces); + std::vector done(m_regions.size(), false); + std::vector layer_region_ids; + std::vector> perimeter_and_gapfill_ranges; + ExPolygons fill_expolygons; + std::vector fill_expolygons_ranges; + SurfacesPtr surfaces_to_merge; + SurfacesPtr surfaces_to_merge_temp; - // assign fill_surfaces to each layer - if (!fill_surfaces.surfaces.empty()) { - for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) { - // Separate the fill surfaces. - ExPolygons expp = intersection_ex(fill_surfaces.surfaces, (*l)->slices.surfaces); - (*l)->fill_expolygons = expp; - (*l)->fill_surfaces.set(std::move(expp), fill_surfaces.surfaces.front()); - } - } - } - } + auto layer_region_reset_perimeters = [](LayerRegion &layerm) { + layerm.m_perimeters.clear(); + layerm.m_fills.clear(); + layerm.m_thin_fills.clear(); + layerm.m_fill_expolygons.clear(); + layerm.m_fill_expolygons_bboxes.clear(); + layerm.m_fill_expolygons_composite.clear(); + layerm.m_fill_expolygons_composite_bboxes.clear(); + }; + + // Remove layer islands, remove references to perimeters and fills from these layer islands to LayerRegion ExtrusionEntities. + for (LayerSlice &lslice : this->lslices_ex) + lslice.islands.clear(); + + for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) + if (size_t region_id = layerm - m_regions.begin(); ! done[region_id]) { + layer_region_reset_perimeters(**layerm); + if (! (*layerm)->slices().empty()) { + BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; + done[region_id] = true; + const PrintRegionConfig &config = (*layerm)->region().config(); + + perimeter_and_gapfill_ranges.clear(); + fill_expolygons.clear(); + fill_expolygons_ranges.clear(); + surfaces_to_merge.clear(); + + // find compatible regions + layer_region_ids.clear(); + layer_region_ids.push_back(region_id); + for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) + if (! (*it)->slices().empty()) { + LayerRegion* other_layerm = *it; + const PrintRegionConfig &other_config = other_layerm->region().config(); + if (config.perimeter_extruder == other_config.perimeter_extruder + && config.perimeters == other_config.perimeters + && config.perimeter_speed == other_config.perimeter_speed + && config.external_perimeter_speed == other_config.external_perimeter_speed + && (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == + (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) + && config.overhangs == other_config.overhangs + && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") + && config.thin_walls == other_config.thin_walls + && config.external_perimeters_first == other_config.external_perimeters_first + && config.infill_overlap == other_config.infill_overlap + && config.fuzzy_skin == other_config.fuzzy_skin + && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness + && config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist) + { + layer_region_reset_perimeters(*other_layerm); + layer_region_ids.push_back(it - m_regions.begin()); + done[it - m_regions.begin()] = true; + } + } + + if (layer_region_ids.size() == 1) { // optimization + (*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); + this->sort_perimeters_into_islands((*layerm)->slices(), region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + } else { + SurfaceCollection new_slices; + // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. + LayerRegion *layerm_config = m_regions[layer_region_ids.front()]; + { + // Merge slices (surfaces) according to number of extra perimeters. + for (uint32_t region_id : layer_region_ids) { + LayerRegion &layerm = *m_regions[region_id]; + for (const Surface &surface : layerm.slices()) + surfaces_to_merge.emplace_back(&surface); + if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) + layerm_config = &layerm; + } + std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r){ return l->extra_perimeters < r->extra_perimeters; }); + for (size_t i = 0; i < surfaces_to_merge.size();) { + size_t j = i; + const Surface &first = *surfaces_to_merge[i]; + size_t extra_perimeters = first.extra_perimeters; + for (; j < surfaces_to_merge.size() && surfaces_to_merge[j]->extra_perimeters == extra_perimeters; ++ j) ; + if (i + 1 == j) + // Nothing to merge, just copy. + new_slices.surfaces.emplace_back(*surfaces_to_merge[i]); + else { + surfaces_to_merge_temp.assign(surfaces_to_merge.begin() + i, surfaces_to_merge.begin() + j); + new_slices.append(offset_ex(surfaces_to_merge_temp, ClipperSafetyOffset), first); + } + i = j; + } + } + // make perimeters + layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); + this->sort_perimeters_into_islands(new_slices, region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + } + } + } BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done"; } +void Layer::sort_perimeters_into_islands( + // Slices for which perimeters and fill_expolygons were just created. + // The slices may have been created by merging multiple source slices with the same perimeter parameters. + const SurfaceCollection &slices, + // Region where the perimeters, gap fills and fill expolygons are stored. + const uint32_t region_id, + // Perimeters and gap fills produced by the perimeter generator for the slices, + // sorted by the source slices. + const std::vector> &perimeter_and_gapfill_ranges, + // Fill expolygons produced for all source slices above. + ExPolygons &&fill_expolygons, + // Fill expolygon ranges sorted by the source slices. + const std::vector &fill_expolygons_ranges, + // If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces. + const std::vector &layer_region_ids) +{ + LayerRegion &this_layer_region = *m_regions[region_id]; + + // Bounding boxes of fill_expolygons. + BoundingBoxes fill_expolygons_bboxes; + fill_expolygons_bboxes.reserve(fill_expolygons.size()); + for (const ExPolygon &expolygon : fill_expolygons) + fill_expolygons_bboxes.emplace_back(get_extents(expolygon)); + + + // Take one sample point for each source slice, to be used to sort source slices into layer slices. + // source slice index + its sample. + std::vector> perimeter_slices_queue; + perimeter_slices_queue.reserve(slices.size()); + for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) { + const std::pair &extrusions = perimeter_and_gapfill_ranges[islice]; + Point sample; + bool sample_set = false; + if (! extrusions.first.empty()) { + sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point(); + sample_set = true; + } else if (! extrusions.second.empty()) { + sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point(); + sample_set = true; + } else { + for (uint32_t iexpoly : fill_expolygons_ranges[islice]) + if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) { + sample = expoly.contour.points.front(); + sample_set = true; + break; + } + } + // There may be a valid empty island. + // assert(sample_set); + if (sample_set) + perimeter_slices_queue.emplace_back(islice, sample); + } + + // Map of source fill_expolygon into region and fill_expolygon of that region. + // -1: not set + std::vector> map_expolygon_to_region_and_fill; + const bool has_multiple_regions = layer_region_ids.size() > 1; + assert(has_multiple_regions || layer_region_ids.size() == 1); + // assign fill_surfaces to each layer + if (! fill_expolygons.empty()) { + if (has_multiple_regions) { + // Sort the bounding boxes lexicographically. + std::vector fill_expolygons_bboxes_sorted(fill_expolygons_bboxes.size()); + std::iota(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), 0); + std::sort(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), [&fill_expolygons_bboxes](uint32_t lhs, uint32_t rhs){ + const BoundingBox &bbl = fill_expolygons_bboxes[lhs]; + const BoundingBox &bbr = fill_expolygons_bboxes[rhs]; + return bbl.min < bbr.min || (bbl.min == bbr.min && bbl.max < bbr.max); + }); + map_expolygon_to_region_and_fill.assign(fill_expolygons.size(), std::make_pair(-1, -1)); + for (uint32_t region_idx : layer_region_ids) { + LayerRegion &l = *m_regions[region_idx]; + l.m_fill_expolygons = intersection_ex(l.slices().surfaces, fill_expolygons); + l.m_fill_expolygons_bboxes.reserve(l.fill_expolygons().size()); + for (const ExPolygon &expolygon : l.fill_expolygons()) { + BoundingBox bbox = get_extents(expolygon); + l.m_fill_expolygons_bboxes.emplace_back(bbox); + auto it_bbox = std::lower_bound(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), bbox, [&fill_expolygons_bboxes](uint32_t lhs, const BoundingBox &bbr){ + const BoundingBox &bbl = fill_expolygons_bboxes[lhs]; + return bbl.min < bbr.min || (bbl.min == bbr.min && bbl.max < bbr.max); + }); + if (it_bbox != fill_expolygons_bboxes_sorted.end()) + if (uint32_t fill_id = *it_bbox; fill_expolygons_bboxes[fill_id] == bbox) { + // With a very high probability the two expolygons match exactly. Confirm that. + if (expolygons_match(expolygon, fill_expolygons[fill_id])) { + std::pair &ref = map_expolygon_to_region_and_fill[fill_id]; + // Only one expolygon produced by intersection with LayerRegion surface may match an expolygon of fill_expolygons. + assert(ref.first == -1); + ref.first = region_idx; + ref.second = int(&expolygon - l.fill_expolygons().data()); + } + } + } + } + } else { + this_layer_region.m_fill_expolygons = std::move(fill_expolygons); + this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes); + } + } + + // Sort perimeter extrusions, thin fill extrusions and fill expolygons into islands. + std::vector region_fill_sorted_last; + auto insert_into_island = [ + // Region where the perimeters, gap fills and fill expolygons are stored. + region_id, + // Whether there are infills with different regions generated for this LayerSlice. + has_multiple_regions, + // Perimeters and gap fills to be sorted into islands. + &perimeter_and_gapfill_ranges, + // Infill regions to be sorted into islands. + &fill_expolygons, &fill_expolygons_bboxes, &fill_expolygons_ranges, + // Mapping of fill_expolygon to region and its infill. + &map_expolygon_to_region_and_fill, + // Output + ®ions = m_regions, &lslices_ex = this->lslices_ex, + // fill_expolygons and fill_expolygons_bboxes need to be sorted into contiguous sequence by island, + // thus region_fill_sorted_last contains last fill_expolygon processed (meaning sorted). + ®ion_fill_sorted_last] + (int lslice_idx, int source_slice_idx) { + lslices_ex[lslice_idx].islands.push_back({}); + LayerIsland &island = lslices_ex[lslice_idx].islands.back(); + island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first); + island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second; + if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) { + if (has_multiple_regions) { + // Check whether the fill expolygons of this island were split into multiple regions. + island.fill_region_id = LayerIsland::fill_region_composite_id; + for (uint32_t fill_idx : fill_range) { + const std::pair &kvp = map_expolygon_to_region_and_fill[fill_idx]; + if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) { + island.fill_region_id = LayerIsland::fill_region_composite_id; + break; + } else + island.fill_region_id = kvp.second; + } + if (island.fill_expolygons_composite()) { + // They were split, thus store the unsplit "composite" expolygons into the region of perimeters. + LayerRegion &this_layer_region = *regions[region_id]; + auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size()); + this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size()); + std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite)); + this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(), + fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end()); + island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size())); + } else { + if (region_fill_sorted_last.empty()) + region_fill_sorted_last.assign(regions.size(), 0); + uint32_t &last = region_fill_sorted_last[island.fill_region_id]; + // They were not split and they belong to the same region. + // Sort the region m_fill_expolygons to a continuous span. + uint32_t begin = last; + LayerRegion &layerm = *regions[island.fill_region_id]; + for (uint32_t fill_id : fill_range) { + uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second; + assert(region_fill_id >= last); + if (region_fill_id > last) { + std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]); + std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]); + } + ++ last; + } + island.fill_expolygons = ExPolygonRange(begin, last); + } + } else { + // Layer island is made of one fill region only. + island.fill_expolygons = fill_range; + island.fill_region_id = region_id; + } + } + }; + + // First sort into islands using exact fit. + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + auto point_inside_surface = [&lslices = this->lslices, &lslices_ex = this->lslices_ex](size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = lslices_ex[lslice_idx].bbox; + return point.x() >= bbox.min.x() && point.x() < bbox.max.x() && + point.y() >= bbox.min.y() && point.y() < bbox.max.y() && + // Exact match: Don't just test whether a point is inside the outer contour of an island, + // test also whether the point is not inside some hole of the same expolygon. + // This is unfortunatelly necessary because the point may be inside an expolygon of one of this expolygon's hole + // and missed due to numerical issues. + lslices[lslice_idx].contains(point); + }; + for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) + for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice) + if (point_inside_surface(lslice_idx, it_source_slice->second)) { + insert_into_island(lslice_idx, it_source_slice->first); + if (std::next(it_source_slice) != perimeter_slices_queue.end()) + // Remove the current slice & point pair from the queue. + *it_source_slice = perimeter_slices_queue.back(); + perimeter_slices_queue.pop_back(); + break; + } + // If anything fails to be sorted in using exact fit, try to find a closest island. + auto point_inside_surface_dist2 = + [&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps = scaled(this->object()->print()->config().gcode_resolution.value) + SCALED_EPSILON] + (const size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = lslices_ex[lslice_idx].bbox; + return + point.x() < bbox.min.x() - bbox_eps || point.x() > bbox.max.x() + bbox_eps || + point.y() < bbox.min.y() - bbox_eps || point.y() > bbox.max.y() + bbox_eps ? + std::numeric_limits::max() : + (lslices[lslice_idx].point_projection(point) - point).cast().squaredNorm(); + }; + for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice) { + double d2min = std::numeric_limits::max(); + int lslice_idx_min = -1; + for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0; -- lslice_idx) + if (double d2 = point_inside_surface_dist2(lslice_idx, it_source_slice->second); d2 < d2min) { + d2min = d2; + lslice_idx_min = lslice_idx; + } + assert(lslice_idx_min != -1); + insert_into_island(lslice_idx_min, it_source_slice->first); + } +} + void Layer::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; for (const auto *region : m_regions) - for (const auto &surface : region->slices.surfaces) + for (const auto &surface : region->slices()) bbox.merge(get_extents(surface.expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min(0), bbox.max(1)); @@ -239,7 +760,7 @@ void Layer::export_region_slices_to_svg(const char *path) const SVG svg(path, bbox); const float transparency = 0.5f; for (const auto *region : m_regions) - for (const auto &surface : region->slices.surfaces) + for (const auto &surface : region->slices()) svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); @@ -256,7 +777,7 @@ void Layer::export_region_fill_surfaces_to_svg(const char *path) const { BoundingBox bbox; for (const auto *region : m_regions) - for (const auto &surface : region->slices.surfaces) + for (const auto &surface : region->slices()) bbox.merge(get_extents(surface.expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min(0), bbox.max(1)); @@ -265,7 +786,7 @@ void Layer::export_region_fill_surfaces_to_svg(const char *path) const SVG svg(path, bbox); const float transparency = 0.5f; for (const auto *region : m_regions) - for (const auto &surface : region->slices.surfaces) + for (const auto &surface : region->slices()) svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); @@ -281,9 +802,9 @@ void Layer::export_region_fill_surfaces_to_svg_debug(const char *name) const BoundingBox get_extents(const LayerRegion &layer_region) { BoundingBox bbox; - if (!layer_region.slices.surfaces.empty()) { - bbox = get_extents(layer_region.slices.surfaces.front()); - for (auto it = layer_region.slices.surfaces.cbegin() + 1; it != layer_region.slices.surfaces.cend(); ++it) + if (! layer_region.slices().empty()) { + bbox = get_extents(layer_region.slices().surfaces.front()); + for (auto it = layer_region.slices().surfaces.cbegin() + 1; it != layer_region.slices().surfaces.cend(); ++ it) bbox.merge(get_extents(*it)); } return bbox; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 147012dceb..024ed41a45 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -2,10 +2,13 @@ #define slic3r_Layer_hpp_ #include "libslic3r.h" +#include "BoundingBox.hpp" #include "Flow.hpp" #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" +#include + namespace Slic3r { class ExPolygon; @@ -25,50 +28,109 @@ namespace FillLightning { class Generator; }; -namespace FillLightning { - class Generator; +// Range of indices, providing support for range based loops. +template +class IndexRange +{ +public: + IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} + IndexRange() = default; + + // Just a bare minimum functionality iterator required by range-for loop. + class Iterator { + public: + T operator*() const { return m_idx; } + bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; } + void operator++() { ++ m_idx; } + private: + friend class IndexRange; + Iterator(T idx) : m_idx(idx) {} + T m_idx; + }; + + Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); }; + Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); }; + + bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } + T size() const { assert(m_begin <= m_end); return m_end - m_begin; } + +private: + // Index of the first extrusion in LayerRegion. + T m_begin { 0 }; + // Index of the last extrusion in LayerRegion. + T m_end { 0 }; }; +using ExtrusionRange = IndexRange; +using ExPolygonRange = IndexRange; + +// Range of extrusions, referencing the source region by an index. +class LayerExtrusionRange : public ExtrusionRange +{ +public: + LayerExtrusionRange(uint32_t iregion, ExtrusionRange extrusion_range) : m_region(iregion), ExtrusionRange(extrusion_range) {} + LayerExtrusionRange() = default; + + // Index of LayerRegion in Layer. + uint32_t region() const { return m_region; }; + +private: + // Index of LayerRegion in Layer. + uint32_t m_region { 0 }; +}; + +// One LayerIsland may be filled with solid fill, sparse fill, top / bottom fill. +static constexpr const size_t LayerExtrusionRangesStaticSize = 3; +using LayerExtrusionRanges = +#ifdef NDEBUG + // To reduce memory allocation in release mode. + boost::container::small_vector; +#else // NDEBUG + // To ease debugging. + std::vector; +#endif // NDEBUG + class LayerRegion { public: - Layer* layer() { return m_layer; } - const Layer* layer() const { return m_layer; } - const PrintRegion& region() const { return *m_region; } + [[nodiscard]] Layer* layer() { return m_layer; } + [[nodiscard]] const Layer* layer() const { return m_layer; } + [[nodiscard]] const PrintRegion& region() const { return *m_region; } // collection of surfaces generated by slicing the original geometry // divided by type top/bottom/internal - SurfaceCollection slices; - // Backed up slices before they are split into top/bottom/internal. - // Only backed up for multi-region layers or layers with elephant foot compensation. - //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. - ExPolygons raw_slices; + [[nodiscard]] const SurfaceCollection& slices() const { return m_slices; } + + // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") + // and for re-starting of infills. + [[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; } + // and their bounding boxes + [[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; } + // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. + // Not used for a plain single material print with no infill modifiers. + [[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; } + // and their bounding boxes + [[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; } + + // collection of surfaces generated by slicing the original geometry + // divided by type top/bottom/internal + [[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; } // collection of extrusion paths/loops filling gaps // These fills are generated by the perimeter generator. // They are not printed on their own, but they are copied to this->fills during infill generation. - ExtrusionEntityCollection thin_fills; - - // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") - // and for re-starting of infills. - ExPolygons fill_expolygons; - // collection of surfaces for infill generation - SurfaceCollection fill_surfaces; - - // collection of expolygons representing the bridged areas (thus not - // needing support material) -// Polygons bridged; + [[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; } // collection of polylines representing the unsupported bridge edges - Polylines unsupported_bridge_edges; + [[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; } // ordered collection of extrusion paths/loops to build all perimeters // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection perimeters; + [[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; } // ordered collection of extrusion paths to fill surfaces // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection fills; + [[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; } Flow flow(FlowRole role) const; Flow flow(FlowRole role, double layer_height) const; @@ -76,7 +138,17 @@ public: void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); - void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces); + // Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices. + void make_perimeters( + // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. + const SurfaceCollection &slices, + // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing + // newly created extrusions stored at this LayerRegion. + std::vector> &perimeter_and_gapfill_ranges, + // All fill areas produced for all input slices above. + ExPolygons &fill_expolygons, + // Ranges of fill areas above per input slice. + std::vector &fill_expolygons_ranges); void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); double infill_area_threshold() const; // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. @@ -92,20 +164,149 @@ public: void export_region_fill_surfaces_to_svg_debug(const char *name) const; // Is there any valid extrusion assigned to this LayerRegion? - bool has_extrusions() const { return ! this->perimeters.entities.empty() || ! this->fills.entities.empty(); } + bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); } protected: friend class Layer; friend class PrintObject; LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {} - ~LayerRegion() {} + ~LayerRegion() = default; private: - Layer *m_layer; - const PrintRegion *m_region; + // Modifying m_slices + friend std::string fix_slicing_errors(LayerPtrs&, const std::function&); + template + friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel); + + Layer *m_layer; + const PrintRegion *m_region; + + // Backed up slices before they are split into top/bottom/internal. + // Only backed up for multi-region layers or layers with elephant foot compensation. + //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. + ExPolygons m_raw_slices; + +//FIXME make m_slices public for unit tests +public: + // collection of surfaces generated by slicing the original geometry + // divided by type top/bottom/internal + SurfaceCollection m_slices; + +private: + // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") + // and for re-starting of infills. + ExPolygons m_fill_expolygons; + // and their bounding boxes + BoundingBoxes m_fill_expolygons_bboxes; + // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. + // Not used for a plain single material print with no infill modifiers. + ExPolygons m_fill_expolygons_composite; + // and their bounding boxes + BoundingBoxes m_fill_expolygons_composite_bboxes; + + // Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons. + SurfaceCollection m_fill_surfaces; + + // Collection of extrusion paths/loops filling gaps + // These fills are generated by the perimeter generator. + // They are not printed on their own, but they are copied to this->fills during infill generation. + ExtrusionEntityCollection m_thin_fills; + + // collection of polylines representing the unsupported bridge edges + Polylines m_unsupported_bridge_edges; + + // ordered collection of extrusion paths/loops to build all perimeters + // (this collection contains only ExtrusionEntityCollection objects) + ExtrusionEntityCollection m_perimeters; + + // ordered collection of extrusion paths to fill surfaces + // (this collection contains only ExtrusionEntityCollection objects) + ExtrusionEntityCollection m_fills; + + // collection of expolygons representing the bridged areas (thus not + // needing support material) +// Polygons bridged; }; +// LayerSlice contains one or more LayerIsland objects, +// each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters +// and one or multiple +struct LayerIsland +{ +private: + friend class Layer; + static constexpr const uint32_t fill_region_composite_id = std::numeric_limits::max(); + +public: + // Perimeter extrusions in LayerRegion belonging to this island. + LayerExtrusionRange perimeters; + // Thin fills of the same region as perimeters. Generated by classic perimeter generator, while Arachne puts them into perimeters. + ExtrusionRange thin_fills; + // Infill + gapfill extrusions in LayerRegion belonging to this island. + LayerExtrusionRanges fills; + // Region that is to be filled with the fills above (thin fills, regular fills). + // Pointing to either LayerRegion::fill_expolygons() or LayerRegion::fill_expolygons_composite() + // based on this->fill_expolygons_composite() flag. + ExPolygonRange fill_expolygons; + // Index of LayerRegion with LayerRegion::fill_expolygons() if not fill_expolygons_composite(). + uint32_t fill_region_id; + bool fill_expolygons_composite() const { return this->fill_region_id == fill_region_composite_id; } + // Centroid of this island used for path planning. +// Point centroid; + + bool has_extrusions() const { return ! this->perimeters.empty() || ! this->fills.empty(); } + + void add_fill_range(const LayerExtrusionRange &new_fill_range) { + // Compress ranges. + if (! this->fills.empty() && this->fills.back().region() == new_fill_range.region() && *this->fills.back().end() == *new_fill_range.begin()) + this->fills.back() = { new_fill_range.region(), { *this->fills.back().begin(), *new_fill_range.end() } }; + else + this->fills.push_back(new_fill_range); + } +}; + +static constexpr const size_t LayerIslandsStaticSize = 1; +using LayerIslands = +#ifdef NDEBUG + // To reduce memory allocation in release mode. + boost::container::small_vector; +#else // NDEBUG + // To ease debugging. + std::vector; +#endif // NDEBUG + +// One connected island of a layer. LayerSlice may consist of one or more LayerIslands. +struct LayerSlice +{ + struct Link { + int32_t slice_idx; + float area; + }; + static constexpr const size_t LinksStaticSize = 4; + using Links = +#ifdef NDEBUG + // To reduce memory allocation in release mode. + boost::container::small_vector; +#else // NDEBUG + // To ease debugging. + std::vector; +#endif // NDEBUG + + BoundingBox bbox; + Links overlaps_above; + Links overlaps_below; + // One island for each region or region set that generates its own perimeters. + // For multi-material prints or prints with regions of different perimeter parameters, + // a LayerSlice may be split into multiple LayerIslands. + // For most prints there will be just one island. + LayerIslands islands; + + bool has_extrusions() const { for (const LayerIsland &island : islands) if (island.has_extrusions()) return true; return false; } +}; + +using LayerSlices = std::vector; + class Layer { public: @@ -134,8 +335,8 @@ public: // order will be applied by the G-code generator to the extrusions fitting into these lslices. // These lslices are also used to detect overhangs and overlaps between successive layers, therefore it is important // that the 1st lslice is not compensated by the Elephant foot compensation algorithm. - ExPolygons lslices; - std::vector lslices_bboxes; + ExPolygons lslices; + LayerSlices lslices_ex; size_t region_count() const { return m_regions.size(); } const LayerRegion* get_region(int idx) const { return m_regions[idx]; } @@ -145,6 +346,8 @@ public: // Test whether whether there are any slices assigned to this layer. bool empty() const; void make_slices(); + // After creating the slices on all layers, chain the islands overlapping in Z. + static void build_up_down_graph(Layer &below, Layer &above); // Backup and restore raw sliced regions if needed. //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. void backup_untyped_slices(); @@ -154,11 +357,11 @@ public: // Slices merged into islands, to be used by the elephant foot compensation to trim the individual surfaces with the shrunk merged slices. ExPolygons merged(float offset) const; template bool any_internal_region_slice_contains(const T &item) const { - for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_internal_contains(item)) return true; + for (const LayerRegion *layerm : m_regions) if (layerm->slices().any_internal_contains(item)) return true; return false; } template bool any_bottom_region_slice_contains(const T &item) const { - for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_bottom_contains(item)) return true; + for (const LayerRegion *layerm : m_regions) if (layerm->slices().any_bottom_contains(item)) return true; return false; } void make_perimeters(); @@ -175,6 +378,7 @@ public: // Is there any valid extrusion assigned to this LayerRegion? virtual bool has_extrusions() const { for (auto layerm : m_regions) if (layerm->has_extrusions()) return true; return false; } +// virtual bool has_extrusions() const { for (const LayerSlice &lslice : lslices_ex) if (lslice.has_extrusions()) return true; return false; } protected: friend class PrintObject; @@ -186,8 +390,26 @@ protected: slice_z(slice_z), print_z(print_z), height(height), m_id(id), m_object(object) {} virtual ~Layer(); + // Clear fill extrusions, remove them from layer islands. + void clear_fills(); private: + void sort_perimeters_into_islands( + // Slices for which perimeters and fill_expolygons were just created. + // The slices may have been created by merging multiple source slices with the same perimeter parameters. + const SurfaceCollection &slices, + // Region where the perimeters, gap fills and fill expolygons are stored. + const uint32_t region_id, + // Perimeters and gap fills produced by the perimeter generator for the slices, + // sorted by the source slices. + const std::vector> &perimeter_and_gapfill_ranges, + // Fill expolygons produced for all source slices above. + ExPolygons &&fill_expolygons, + // Fill expolygon ranges sorted by the source slices. + const std::vector &fill_expolygons_ranges, + // If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces. + const std::vector &layer_region_ids); + // Sequential index of layer, 0-based, offsetted by number of raft layers. size_t m_id; PrintObject *m_object; @@ -201,7 +423,7 @@ public: // Used to suppress retraction if moving for a support extrusion over these support_islands. ExPolygons support_islands; // Slightly inflated bounding boxes of the above, for faster intersection query. - std::vector support_islands_bboxes; + BoundingBoxes support_islands_bboxes; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 5ce21d921a..0f42b21076 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -44,30 +44,41 @@ Flow LayerRegion::bridging_flow(FlowRole role) const } } -// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. +// Fill in layerm->fill_surfaces by trimming the layerm->slices by layerm->fill_expolygons. void LayerRegion::slices_to_fill_surfaces_clipped() { - // Note: this method should be idempotent, but fill_surfaces gets modified - // in place. However we're now only using its boundaries (which are invariant) - // so we're safe. This guarantees idempotence of prepare_infill() also in case - // that combine_infill() turns some fill_surface into VOID surfaces. // Collect polygons per surface type. - std::array by_surface; - for (Surface &surface : this->slices.surfaces) + std::array, size_t(stCount)> by_surface; + for (const Surface &surface : this->slices()) by_surface[size_t(surface.surface_type)].emplace_back(&surface); // Trim surfaces by the fill_boundaries. - this->fill_surfaces.surfaces.clear(); + m_fill_surfaces.surfaces.clear(); for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) { - const SurfacesPtr &this_surfaces = by_surface[surface_type]; + const std::vector &this_surfaces = by_surface[surface_type]; if (! this_surfaces.empty()) - this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type)); + m_fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons()), SurfaceType(surface_type)); } } -void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) +// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices. +void LayerRegion::make_perimeters( + // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. + const SurfaceCollection &slices, + // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing + // newly created extrusions stored at this LayerRegion. + std::vector> &perimeter_and_gapfill_ranges, + // All fill areas produced for all input slices above. + ExPolygons &fill_expolygons, + // Ranges of fill areas above per input slice. + std::vector &fill_expolygons_ranges) { - this->perimeters.clear(); - this->thin_fills.clear(); + m_perimeters.clear(); + m_thin_fills.clear(); + + perimeter_and_gapfill_ranges.reserve(perimeter_and_gapfill_ranges.size() + slices.size()); + // There may be more expolygons produced per slice, thus this reserve is conservative. + fill_expolygons.reserve(fill_expolygons.size() + slices.size()); + fill_expolygons_ranges.reserve(fill_expolygons_ranges.size() + slices.size()); const PrintConfig &print_config = this->layer()->object()->print()->config(); const PrintRegionConfig ®ion_config = this->region().config(); @@ -77,35 +88,55 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec (this->layer()->id() >= size_t(region_config.bottom_solid_layers.value) && this->layer()->print_z >= region_config.bottom_solid_min_thickness - EPSILON); - PerimeterGenerator g( - // input: - &slices, + PerimeterGenerator::Parameters params( this->layer()->height, + int(this->layer()->id()), this->flow(frPerimeter), - ®ion_config, - &this->layer()->object()->config(), - &print_config, - spiral_vase, - - // output: - &this->perimeters, - &this->thin_fills, - fill_surfaces + this->flow(frExternalPerimeter), + this->bridging_flow(frPerimeter), + this->flow(frSolidInfill), + region_config, + this->layer()->object()->config(), + print_config, + spiral_vase ); - - if (this->layer()->lower_layer != nullptr) - // Cummulative sum of polygons over all the regions. - g.lower_slices = &this->layer()->lower_layer->lslices; - - g.layer_id = (int)this->layer()->id(); - g.ext_perimeter_flow = this->flow(frExternalPerimeter); - g.overhang_flow = this->bridging_flow(frPerimeter); - g.solid_infill_flow = this->flow(frSolidInfill); - if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase) - g.process_arachne(); - else - g.process_classic(); + // Cummulative sum of polygons over all the regions. + const ExPolygons *lower_slices = this->layer()->lower_layer ? &this->layer()->lower_layer->lslices : nullptr; + // Cache for offsetted lower_slices + Polygons lower_layer_polygons_cache; + + for (const Surface &surface : slices) { + auto perimeters_begin = uint32_t(m_perimeters.size()); + auto gap_fills_begin = uint32_t(m_thin_fills.size()); + auto fill_expolygons_begin = uint32_t(fill_expolygons.size()); + if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase) + PerimeterGenerator::process_arachne( + // input: + params, + surface, + lower_slices, + lower_layer_polygons_cache, + // output: + m_perimeters, + m_thin_fills, + fill_expolygons); + else + PerimeterGenerator::process_classic( + // input: + params, + surface, + lower_slices, + lower_layer_polygons_cache, + // output: + m_perimeters, + m_thin_fills, + fill_expolygons); + perimeter_and_gapfill_ranges.emplace_back( + ExtrusionRange{ perimeters_begin, uint32_t(m_perimeters.size()) }, + ExtrusionRange{ gap_fills_begin, uint32_t(m_thin_fills.size()) }); + fill_expolygons_ranges.emplace_back(ExtrusionRange{ fill_expolygons_begin, uint32_t(fill_expolygons.size()) }); + } } //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. @@ -132,7 +163,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly // Internal surfaces, not grown. Surfaces internal; // Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. - Polygons fill_boundaries = to_polygons(this->fill_expolygons); + Polygons fill_boundaries = to_polygons(this->fill_expolygons()); Polygons lower_layer_covered_tmp; // Collect top surfaces and internal surfaces. @@ -142,7 +173,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly { // Voids are sparse infills if infill rate is zero. Polygons voids; - for (const Surface &surface : this->fill_surfaces.surfaces) { + for (const Surface &surface : this->fill_surfaces()) { if (surface.is_top()) { // Collect the top surfaces, inflate them and trim them by the bottom surfaces. // This gives the priority to bottom surfaces. @@ -314,7 +345,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->has_support()) { // polygons_append(this->bridged, bd.coverage()); - append(this->unsupported_bridge_edges, bd.unsupported_edges()); + append(m_unsupported_bridge_edges, bd.unsupported_edges()); } } else if (custom_angle > 0) { // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in @@ -388,7 +419,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly surfaces_append(new_surfaces, std::move(new_expolys), s1); } - this->fill_surfaces.surfaces = std::move(new_surfaces); + m_fill_surfaces.surfaces = std::move(new_surfaces); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); @@ -412,12 +443,12 @@ void LayerRegion::prepare_fill_surfaces() // For Lightning infill, infill_only_where_needed is ignored because both // do a similar thing, and their combination doesn't make much sense. if (! spiral_vase && this->region().config().top_solid_layers == 0) { - for (Surface &surface : this->fill_surfaces.surfaces) + for (Surface &surface : m_fill_surfaces) if (surface.is_top()) surface.surface_type = this->layer()->object()->config().infill_only_where_needed && this->region().config().fill_pattern != ipLightning ? stInternalVoid : stInternal; } if (this->region().config().bottom_solid_layers == 0) { - for (Surface &surface : this->fill_surfaces.surfaces) + for (Surface &surface : m_fill_surfaces) if (surface.is_bottom()) // (surface.surface_type == stBottom) surface.surface_type = stInternal; } @@ -426,7 +457,7 @@ void LayerRegion::prepare_fill_surfaces() if (! spiral_vase && this->region().config().fill_density.value > 0) { // scaling an area requires two calls! double min_area = scale_(scale_(this->region().config().solid_infill_below_area.value)); - for (Surface &surface : this->fill_surfaces.surfaces) + for (Surface &surface : m_fill_surfaces) if (surface.surface_type == stInternal && surface.area() <= min_area) surface.surface_type = stInternalSolid; } @@ -446,38 +477,38 @@ double LayerRegion::infill_area_threshold() const void LayerRegion::trim_surfaces(const Polygons &trimming_polygons) { #ifndef NDEBUG - for (const Surface &surface : this->slices.surfaces) + for (const Surface &surface : this->slices()) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - this->slices.set(intersection_ex(this->slices.surfaces, trimming_polygons), stInternal); + m_slices.set(intersection_ex(this->slices().surfaces, trimming_polygons), stInternal); } void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons) { #ifndef NDEBUG - for (const Surface &surface : this->slices.surfaces) + for (const Surface &surface : this->slices()) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - Polygons tmp = intersection(this->slices.surfaces, trimming_polygons); - append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step))); - this->slices.set(union_ex(tmp), stInternal); + Polygons tmp = intersection(this->slices().surfaces, trimming_polygons); + append(tmp, diff(this->slices().surfaces, opening(this->slices().surfaces, elephant_foot_compensation_perimeter_step))); + m_slices.set(union_ex(tmp), stInternal); } void LayerRegion::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; - for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) - bbox.merge(get_extents(surface->expolygon)); + for (const Surface &surface : this->slices()) + bbox.merge(get_extents(surface.expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min(0), bbox.max(1)); bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); SVG svg(path, bbox); const float transparency = 0.5f; - for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) - svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); - for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) - svg.draw(surface->expolygon.lines(), surface_type_to_color_name(surface->surface_type)); + for (const Surface &surface : this->slices()) + svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); + for (const Surface &surface : this->fill_surfaces()) + svg.draw(surface.expolygon.lines(), surface_type_to_color_name(surface.surface_type)); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } @@ -493,15 +524,15 @@ void LayerRegion::export_region_slices_to_svg_debug(const char *name) const void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const { BoundingBox bbox; - for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) - bbox.merge(get_extents(surface->expolygon)); + for (const Surface &surface : this->fill_surfaces()) + bbox.merge(get_extents(surface.expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min(0), bbox.max(1)); bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); SVG svg(path, bbox); const float transparency = 0.5f; - for (const Surface &surface : this->fill_surfaces.surfaces) { + for (const Surface &surface : this->fill_surfaces()) { svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05)); } diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 9a939ac382..947c7fcbaf 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -54,11 +54,11 @@ double distance_to_squared(const L &line, const Vec, Scalar> &point, V // We find projection of this point onto the line. // It falls where t = [(this-a) . (b-a)] / |b-a|^2 const double t = va.dot(v) / l2; - if (t < 0.0) { + if (t <= 0.0) { // beyond the 'a' end of the segment *nearest_point = get_a(line); return va.squaredNorm(); - } else if (t > 1.0) { + } else if (t >= 1.0) { // beyond the 'b' end of the segment *nearest_point = get_b(line); return (point - get_b(line)).template cast().squaredNorm(); diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index 43b9782a83..badb3b2c5f 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -6,6 +6,8 @@ #include "libslic3r/SurfaceMesh.hpp" #include +#include + namespace Slic3r { namespace Measure { diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 21b53c40dd..661c2d0fd1 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -713,7 +713,7 @@ struct MMU_Graph [[nodiscard]] const Vec2d &point_double() const { return m_point_double; } [[nodiscard]] const Point &point() const { return m_point; } - bool operator==(const CPoint &rhs) const { return this->m_point_double == rhs.m_point_double && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; } + bool operator==(const CPoint &rhs) const { return m_point_double == rhs.m_point_double && m_contour_idx == rhs.m_contour_idx && m_point_idx == rhs.m_point_idx; } }; struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }}; typedef ClosestPointInRadiusLookup CPointLookupType; @@ -1697,7 +1697,7 @@ std::vector> multi_material_segmentation_by_painting(con throw_on_cancel_callback(); ExPolygons ex_polygons; for (LayerRegion *region : layers[layer_idx]->regions()) - for (const Surface &surface : region->slices.surfaces) + for (const Surface &surface : region->slices()) Slic3r::append(ex_polygons, offset_ex(surface.expolygon, float(10 * SCALED_EPSILON))); // All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON // to ensure that very close polygons will be merged. diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 5ed9eb23cb..f18720bd6f 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -45,16 +45,6 @@ void MultiPoint::rotate(double angle, const Point ¢er) } } -double MultiPoint::length() const -{ - const Lines& lines = this->lines(); - double len = 0; - for (auto it = lines.cbegin(); it != lines.cend(); ++it) { - len += it->length(); - } - return len; -} - int MultiPoint::find_point(const Point &point) const { for (const Point &pt : this->points) @@ -81,12 +71,6 @@ int MultiPoint::find_point(const Point &point, double scaled_epsilon) const return dist2_min < eps2 ? idx_min : -1; } -bool MultiPoint::has_boundary_point(const Point &point) const -{ - double dist = (point.projection_onto(*this) - point).cast().norm(); - return dist < SCALED_EPSILON; -} - BoundingBox MultiPoint::bounding_box() const { return BoundingBox(this->points); @@ -119,49 +103,6 @@ bool MultiPoint::remove_duplicate_points() return false; } -bool MultiPoint::intersection(const Line& line, Point* intersection) const -{ - Lines lines = this->lines(); - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { - if (it->intersection(line, intersection)) return true; - } - return false; -} - -bool MultiPoint::first_intersection(const Line& line, Point* intersection) const -{ - bool found = false; - double dmin = 0.; - for (const Line &l : this->lines()) { - Point ip; - if (l.intersection(line, &ip)) { - if (! found) { - found = true; - dmin = (line.a - ip).cast().norm(); - *intersection = ip; - } else { - double d = (line.a - ip).cast().norm(); - if (d < dmin) { - dmin = d; - *intersection = ip; - } - } - } - } - return found; -} - -bool MultiPoint::intersections(const Line &line, Points *intersections) const -{ - size_t intersections_size = intersections->size(); - for (const Line &polygon_line : this->lines()) { - Point intersection; - if (polygon_line.intersection(line, &intersection)) - intersections->emplace_back(std::move(intersection)); - } - return intersections->size() > intersections_size; -} - std::vector MultiPoint::_douglas_peucker(const std::vector& pts, const double tolerance) { std::vector result_pts; @@ -363,14 +304,6 @@ void MultiPoint3::translate(const Point& vector) this->translate(vector(0), vector(1)); } -double MultiPoint3::length() const -{ - double len = 0.0; - for (const Line3& line : this->lines()) - len += line.length(); - return len; -} - BoundingBox3 MultiPoint3::bounding_box() const { return BoundingBox3(points); diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index bc9cf761d3..778b30c57d 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -18,7 +18,6 @@ public: Points points; MultiPoint() = default; - virtual ~MultiPoint() = default; MultiPoint(const MultiPoint &other) : points(other.points) {} MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} MultiPoint(std::initializer_list list) : points(list) {} @@ -37,11 +36,8 @@ public: const Point& front() const { return this->points.front(); } const Point& back() const { return this->points.back(); } const Point& first_point() const { return this->front(); } - virtual const Point& last_point() const = 0; - virtual Lines lines() const = 0; size_t size() const { return points.size(); } bool empty() const { return points.empty(); } - double length() const; bool is_valid() const { return this->points.size() >= 2; } // Return index of a polygon point exactly equal to point. @@ -50,7 +46,6 @@ public: // Return index of the closest point to point closer than scaled_epsilon. // Return -1 if no such point exists. int find_point(const Point &point, const double scaled_epsilon) const; - bool has_boundary_point(const Point &point) const; int closest_point_index(const Point &point) const { int idx = -1; if (! this->points.empty()) { @@ -86,10 +81,6 @@ public: } } - bool intersection(const Line& line, Point* intersection) const; - bool first_intersection(const Line& line, Point* intersection) const; - bool intersections(const Line &line, Points *intersections) const; - static Points _douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double& tolerance); @@ -110,8 +101,6 @@ public: void translate(double x, double y); void translate(const Point& vector); - virtual Lines3 lines() const = 0; - double length() const; bool is_valid() const { return this->points.size() >= 2; } BoundingBox3 bounding_box() const; diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 55c48ad1d8..490fd54bfe 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -53,7 +53,7 @@ namespace Slic3r { -ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) +ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) { ExtrusionMultiPath multi_path; ExtrusionPath path(role); @@ -146,14 +146,14 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyl return multi_path; } -static void variable_width(const ThickPolylines &polylines, ExtrusionRole role, const Flow &flow, std::vector &out) +static void variable_width_classic(const ThickPolylines &polylines, ExtrusionRole role, const Flow &flow, std::vector &out) { // This value determines granularity of adaptive width, as G-code does not allow // variable extrusion within a single move; this value shall only affect the amount // of segments, and any pruning shall be performed before we apply this tolerance. const auto tolerance = float(scale_(0.05)); for (const ThickPolyline &p : polylines) { - ExtrusionMultiPath multi_path = thick_polyline_to_multi_path(p, role, flow, tolerance, tolerance); + ExtrusionMultiPath multi_path = PerimeterGenerator::thick_polyline_to_multi_path(p, role, flow, tolerance, tolerance); // Append paths to collection. if (!multi_path.paths.empty()) { for (auto it = std::next(multi_path.paths.begin()); it != multi_path.paths.end(); ++it) { @@ -189,7 +189,15 @@ public: // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). bool is_external() const { return this->depth == 0; } // An island, which may have holes, but it does not have another internal island. - bool is_internal_contour() const; + bool is_internal_contour() const { + // An internal contour is a contour containing no other contours + if (! this->is_contour) + return false; + for (const PerimeterGeneratorLoop &loop : this->children) + if (loop.is_contour) + return false; + return true; + } }; // Thanks Cura developers for this function. @@ -275,7 +283,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy using PerimeterGeneratorLoops = std::vector; -static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) +static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) { // loops is an arrayref of ::Loop objects // turn each one into an ExtrusionLoop object @@ -301,30 +309,33 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime const Polygon &polygon = loop.fuzzify ? fuzzified : loop.polygon; if (loop.fuzzify) { fuzzified = loop.polygon; - fuzzy_polygon(fuzzified, scaled(perimeter_generator.config->fuzzy_skin_thickness.value), scaled(perimeter_generator.config->fuzzy_skin_point_dist.value)); + fuzzy_polygon(fuzzified, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); } - if (perimeter_generator.config->overhangs && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers - && ! ((perimeter_generator.object_config->support_material || perimeter_generator.object_config->support_material_enforce_layers > 0) && - perimeter_generator.object_config->support_material_contact_distance.value == 0)) { + if (params.config.overhangs && params.layer_id > params.object_config.raft_layers + && ! ((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) && + params.object_config.support_material_contact_distance.value == 0)) { + BoundingBox bbox(polygon.points); + bbox.offset(SCALED_EPSILON); + Polygons lower_slices_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons_cache, bbox); // get non-overhang paths by intersecting this loop with the grown lower slices extrusion_paths_append( paths, - intersection_pl({ polygon }, perimeter_generator.lower_slices_polygons()), + intersection_pl({ polygon }, lower_slices_polygons_clipped), role, - is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(), - is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(), - (float)perimeter_generator.layer_height); + is_external ? params.ext_mm3_per_mm : params.mm3_per_mm, + is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(), + float(params.layer_height)); // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter extrusion_paths_append( paths, - diff_pl({ polygon }, perimeter_generator.lower_slices_polygons()), + diff_pl({ polygon }, lower_slices_polygons_clipped), erOverhangPerimeter, - perimeter_generator.mm3_per_mm_overhang(), - perimeter_generator.overhang_flow.width(), - perimeter_generator.overhang_flow.height()); + params.mm3_per_mm_overhang, + params.overhang_flow.width(), + params.overhang_flow.height()); // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. @@ -332,9 +343,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } else { ExtrusionPath path(role); path.polyline = polygon.split_at_first_point(); - path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(); - path.width = is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(); - path.height = (float)perimeter_generator.layer_height; + path.mm3_per_mm = is_external ? params.ext_mm3_per_mm : params.mm3_per_mm; + path.width = is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(); + path.height = float(params.layer_height); paths.push_back(path); } @@ -343,7 +354,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime // Append thin walls to the nearest-neighbor search (only for first iteration) if (! thin_walls.empty()) { - variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities); + variable_width_classic(thin_walls, erExternalPerimeter, params.ext_perimeter_flow, coll.entities); thin_walls.clear(); } @@ -363,7 +374,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } else { const PerimeterGeneratorLoop &loop = loops[idx.first]; assert(thin_walls.empty()); - ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls); + ExtrusionEntityCollection children = traverse_loops_classic(params, lower_slices_polygons_cache, loop.children, thin_walls); out.entities.reserve(out.entities.size() + children.entities.size() + 1); ExtrusionLoop *eloop = static_cast(coll.entities[idx.first]); coll.entities[idx.first] = nullptr; @@ -429,8 +440,8 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con Point prev(subject.front().x(), subject.front().y()); for (auto it = std::next(subject.begin()); it != subject.end(); ++it) { Point curr(it->x(), it->y()); - Point projected_pt = pt.projection_onto(Line(prev, curr)); - if (double dist_sqr = (projected_pt - pt).cast().squaredNorm(); dist_sqr < dist_sqr_min) { + Point projected_pt; + if (double dist_sqr = line_alg::distance_to_squared(Line(prev, curr), pt, &projected_pt); dist_sqr < dist_sqr_min) { dist_sqr_min = dist_sqr; projected_pt_min = projected_pt; it_min = std::prev(it); @@ -468,7 +479,7 @@ struct PerimeterGeneratorArachneExtrusion bool fuzzify = false; }; -static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &perimeter_generator, std::vector &pg_extrusions) +static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, std::vector &pg_extrusions) { ExtrusionEntityCollection extrusion_coll; for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) { @@ -480,38 +491,49 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &p ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter; if (pg_extrusion.fuzzify) - fuzzy_extrusion_line(*extrusion, scaled(perimeter_generator.config->fuzzy_skin_thickness.value), scaled(perimeter_generator.config->fuzzy_skin_point_dist.value)); + fuzzy_extrusion_line(*extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); ExtrusionPaths paths; // detect overhanging/bridging perimeters - if (perimeter_generator.config->overhangs && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers - && ! ((perimeter_generator.object_config->support_material || perimeter_generator.object_config->support_material_enforce_layers > 0) && - perimeter_generator.object_config->support_material_contact_distance.value == 0)) { + if (params.config.overhangs && params.layer_id > params.object_config.raft_layers + && ! ((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) && + params.object_config.support_material_contact_distance.value == 0)) { ClipperLib_Z::Path extrusion_path; extrusion_path.reserve(extrusion->size()); - for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) + BoundingBox extrusion_path_bbox; + for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) { extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); + extrusion_path_bbox.merge(Point{ej.p.x(), ej.p.y()}); + } ClipperLib_Z::Paths lower_slices_paths; - lower_slices_paths.reserve(perimeter_generator.lower_slices_polygons().size()); - for (const Polygon &poly : perimeter_generator.lower_slices_polygons()) { - lower_slices_paths.emplace_back(); - ClipperLib_Z::Path &out = lower_slices_paths.back(); - out.reserve(poly.points.size()); - for (const Point &pt : poly.points) - out.emplace_back(pt.x(), pt.y(), 0); + lower_slices_paths.reserve(lower_slices_polygons_cache.size()); + { + Points clipped; + extrusion_path_bbox.offset(SCALED_EPSILON); + for (const Polygon &poly : lower_slices_polygons_cache) { + clipped.clear(); + ClipperUtils::clip_clipper_polygon_with_subject_bbox(poly.points, extrusion_path_bbox, clipped); + if (! clipped.empty()) { + lower_slices_paths.emplace_back(); + ClipperLib_Z::Path &out = lower_slices_paths.back(); + out.reserve(clipped.size()); + for (const Point &pt : clipped) + out.emplace_back(pt.x(), pt.y(), 0); + } + } } // get non-overhang paths by intersecting this loop with the grown lower slices extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctIntersection), role, - is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); + is_external ? params.ext_perimeter_flow : params.perimeter_flow); // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctDifference), erOverhangPerimeter, - perimeter_generator.overhang_flow); + params.overhang_flow); // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. @@ -551,7 +573,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &p chain_and_reorder_extrusion_paths(paths, &start_point); } } else { - extrusion_paths_append(paths, *extrusion, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); + extrusion_paths_append(paths, *extrusion, role, is_external ? params.ext_perimeter_flow : params.perimeter_flow); } // Append paths to collection. @@ -991,268 +1013,275 @@ std::tuple, Polygons> generate_extra_perimeters_over // Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper // "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling" -void PerimeterGenerator::process_arachne() +void PerimeterGenerator::process_arachne( + // Inputs: + const Parameters ¶ms, + const Surface &surface, + const ExPolygons *lower_slices, + // Cache: + Polygons &lower_slices_polygons_cache, + // Output: + // Loops with the external thin walls + ExtrusionEntityCollection &out_loops, + // Gaps without the thin walls + ExtrusionEntityCollection & /* out_gap_fill */, + // Infills without the gap fills + ExPolygons &out_fill_expolygons) { // other perimeters - m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); - coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); - + coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing(); // external perimeters - m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); - coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); - coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); - coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); - - // overhang perimeters - m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); - + coord_t ext_perimeter_width = params.ext_perimeter_flow.scaled_width(); + coord_t ext_perimeter_spacing = params.ext_perimeter_flow.scaled_spacing(); + coord_t ext_perimeter_spacing2 = scaled(0.5f * (params.ext_perimeter_flow.spacing() + params.perimeter_flow.spacing())); // solid infill - coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); + coord_t solid_infill_spacing = params.solid_infill_flow.scaled_spacing(); // prepare grown lower layer slices for overhang detection - if (this->lower_slices != nullptr && this->config->overhangs) { + if (params.config.overhangs && lower_slices != nullptr && lower_slices_polygons_cache.empty()) { // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer - double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); - m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2))); + double nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder-1); + lower_slices_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter/2))); } // we need to process each island separately because we might have different // extra perimeters for each one - for (const Surface &surface : this->slices->surfaces) { - // detect how many perimeters must be generated for this island - int loop_number = this->config->perimeters + surface.extra_perimeters - 1; // 0-indexed loops - ExPolygons last = offset_ex(surface.expolygon.simplify_p(m_scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); - Polygons last_p = to_polygons(last); + // detect how many perimeters must be generated for this island + int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops + ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); + Polygons last_p = to_polygons(last); - Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, layer_height, *this->object_config, *this->print_config); - std::vector perimeters = wallToolPaths.getToolPaths(); - loop_number = int(perimeters.size()) - 1; + Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); + std::vector perimeters = wallToolPaths.getToolPaths(); + loop_number = int(perimeters.size()) - 1; #ifdef ARACHNE_DEBUG - { - static int iRun = 0; - export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); - } + { + static int iRun = 0; + export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); + } #endif - // All closed ExtrusionLine should have the same the first and the last point. - // But in rare cases, Arachne produce ExtrusionLine marked as closed but without - // equal the first and the last point. - assert([&perimeters = std::as_const(perimeters)]() -> bool { - for (const Arachne::VariableWidthLines &perimeter : perimeters) - for (const Arachne::ExtrusionLine &el : perimeter) - if (el.is_closed && el.junctions.front().p != el.junctions.back().p) - return false; - return true; - }()); + // All closed ExtrusionLine should have the same the first and the last point. + // But in rare cases, Arachne produce ExtrusionLine marked as closed but without + // equal the first and the last point. + assert([&perimeters = std::as_const(perimeters)]() -> bool { + for (const Arachne::VariableWidthLines &perimeter : perimeters) + for (const Arachne::ExtrusionLine &el : perimeter) + if (el.is_closed && el.junctions.front().p != el.junctions.back().p) + return false; + return true; + }()); - int start_perimeter = int(perimeters.size()) - 1; - int end_perimeter = -1; - int direction = -1; + int start_perimeter = int(perimeters.size()) - 1; + int end_perimeter = -1; + int direction = -1; - if (this->config->external_perimeters_first) { - start_perimeter = 0; - end_perimeter = int(perimeters.size()); - direction = 1; + if (params.config.external_perimeters_first) { + start_perimeter = 0; + end_perimeter = int(perimeters.size()); + direction = 1; + } + + std::vector all_extrusions; + for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { + if (perimeters[perimeter_idx].empty()) + continue; + for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx]) + all_extrusions.emplace_back(&wall); + } + + // Find topological order with constraints from extrusions_constrains. + std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. + std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. + std::unordered_map map_extrusion_to_idx; + for (size_t idx = 0; idx < all_extrusions.size(); idx++) + map_extrusion_to_idx.emplace(all_extrusions[idx], idx); + + auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first); + for (auto [before, after] : extrusions_constrains) { + auto after_it = map_extrusion_to_idx.find(after); + ++blocked[after_it->second]; + blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); + } + + std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. + Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. + std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. + ordered_extrusions.reserve(all_extrusions.size()); + + while (ordered_extrusions.size() < all_extrusions.size()) { + size_t best_candidate = 0; + double best_distance_sqr = std::numeric_limits::max(); + bool is_best_closed = false; + + std::vector available_candidates; + for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { + if (processed[candidate] || blocked[candidate]) + continue; // Not a valid candidate. + available_candidates.push_back(candidate); } - std::vector all_extrusions; - for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { - if (perimeters[perimeter_idx].empty()) + std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { + return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; + }); + + for (const size_t candidate_path_idx : available_candidates) { + auto& path = all_extrusions[candidate_path_idx]; + + if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. + if (best_distance_sqr == std::numeric_limits::max()) { + best_candidate = candidate_path_idx; + is_best_closed = path->is_closed; + } continue; - for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx]) - all_extrusions.emplace_back(&wall); - } - - // Find topological order with constraints from extrusions_constrains. - std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. - std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. - std::unordered_map map_extrusion_to_idx; - for (size_t idx = 0; idx < all_extrusions.size(); idx++) - map_extrusion_to_idx.emplace(all_extrusions[idx], idx); - - auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, this->config->external_perimeters_first); - for (auto [before, after] : extrusions_constrains) { - auto after_it = map_extrusion_to_idx.find(after); - ++blocked[after_it->second]; - blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); - } - - std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. - Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. - std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. - ordered_extrusions.reserve(all_extrusions.size()); - - while (ordered_extrusions.size() < all_extrusions.size()) { - size_t best_candidate = 0; - double best_distance_sqr = std::numeric_limits::max(); - bool is_best_closed = false; - - std::vector available_candidates; - for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { - if (processed[candidate] || blocked[candidate]) - continue; // Not a valid candidate. - available_candidates.push_back(candidate); } - std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { - return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; - }); - - for (const size_t candidate_path_idx : available_candidates) { - auto& path = all_extrusions[candidate_path_idx]; - - if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. - if (best_distance_sqr == std::numeric_limits::max()) { - best_candidate = candidate_path_idx; - is_best_closed = path->is_closed; - } - continue; - } - - const Point candidate_position = path->junctions.front().p; - double distance_sqr = (current_position - candidate_position).cast().norm(); - if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. - if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { - best_candidate = candidate_path_idx; - best_distance_sqr = distance_sqr; - is_best_closed = path->is_closed; - } - } - } - - auto &best_path = all_extrusions[best_candidate]; - ordered_extrusions.push_back({best_path, best_path->is_contour(), false}); - processed[best_candidate] = true; - for (size_t unlocked_idx : blocking[best_candidate]) - blocked[unlocked_idx]--; - - if(!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. - if(best_path->is_closed) - current_position = best_path->junctions[0].p; //We end where we started. - else - current_position = best_path->junctions.back().p; //Pick the other end from where we started. - } - } - - if (this->layer_id > 0 && this->config->fuzzy_skin != FuzzySkinType::None) { - std::vector closed_loop_extrusions; - for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions) - if (extrusion.extrusion->inset_idx == 0) { - if (extrusion.extrusion->is_closed && this->config->fuzzy_skin == FuzzySkinType::External) { - closed_loop_extrusions.emplace_back(&extrusion); - } else { - extrusion.fuzzify = true; - } - } - - if (this->config->fuzzy_skin == FuzzySkinType::External) { - ClipperLib_Z::Paths loops_paths; - loops_paths.reserve(closed_loop_extrusions.size()); - for (const auto &cl_extrusion : closed_loop_extrusions) { - assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back()); - size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); - ClipperLib_Z::Path loop_path; - loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1); - for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it) - loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); - loops_paths.emplace_back(loop_path); - } - - ClipperLib_Z::Clipper clipper; - clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true); - ClipperLib_Z::PolyTree loops_polytree; - clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); - - for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) { - // The whole contour must have the same index. - coord_t polygon_idx = child_node->Contour.front().z(); - bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(), - [&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); }); - if (has_same_idx) - closed_loop_extrusions[polygon_idx]->fuzzify = true; + const Point candidate_position = path->junctions.front().p; + double distance_sqr = (current_position - candidate_position).cast().norm(); + if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. + if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { + best_candidate = candidate_path_idx; + best_distance_sqr = distance_sqr; + is_best_closed = path->is_closed; } } } - if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions); !extrusion_coll.empty()) - this->loops->append(extrusion_coll); + auto &best_path = all_extrusions[best_candidate]; + ordered_extrusions.push_back({best_path, best_path->is_contour(), false}); + processed[best_candidate] = true; + for (size_t unlocked_idx : blocking[best_candidate]) + blocked[unlocked_idx]--; - ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); - const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; - if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) - infill_contour.clear(); // Infill region is too small, so let's filter it out. + if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. + if(best_path->is_closed) + current_position = best_path->junctions[0].p; //We end where we started. + else + current_position = best_path->junctions.back().p; //Pick the other end from where we started. + } + } - // create one more offset to be used as boundary for fill - // we offset by half the perimeter spacing (to get to the actual infill boundary) - // and then we offset back and forth by half the infill spacing to only consider the - // non-collapsing regions - coord_t inset = - (loop_number < 0) ? 0 : - (loop_number == 0) ? - // one loop - ext_perimeter_spacing: - // two or more loops? - perimeter_spacing; - - inset = coord_t(scale_(this->config->get_abs_value("infill_overlap", unscale(inset)))); - Polygons pp; - for (ExPolygon &ex : infill_contour) - ex.simplify_p(m_scaled_resolution, &pp); - // collapse too narrow infill areas - const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - // append infill areas to fill_surfaces - // append infill areas to fill_surfaces - ExPolygons infill_areas = offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), - float(inset + min_perimeter_infill_spacing / 2.)); - this->fill_surfaces->append(infill_areas, stInternal); - - if (this->lower_slices != nullptr && this->config->overhangs && this->config->extra_perimeters_on_overhangs && - this->config->perimeters > 0 && this->layer_id > this->object_config->raft_layers) { - // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material - auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, - this->lower_slices_polygons(), - this->overhang_flow, this->m_scaled_resolution, - *this->object_config, *this->print_config); - if (!extra_perimeters.empty()) { - ExtrusionEntityCollection *this_islands_perimeters = static_cast(this->loops->entities.back()); - ExtrusionEntityCollection new_perimeters{}; - new_perimeters.no_sort = this_islands_perimeters->no_sort; - for (const ExtrusionPaths &paths : extra_perimeters) { new_perimeters.append(paths); } - new_perimeters.append(this_islands_perimeters->entities); - this_islands_perimeters->swap(new_perimeters); - - SurfaceCollection orig_surfaces = *this->fill_surfaces; - this->fill_surfaces->clear(); - for (const auto &surface : orig_surfaces.surfaces) { - auto new_surfaces = diff_ex({surface.expolygon}, filled_area); - this->fill_surfaces->append(new_surfaces, surface); + if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) { + std::vector closed_loop_extrusions; + for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions) + if (extrusion.extrusion->inset_idx == 0) { + if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) { + closed_loop_extrusions.emplace_back(&extrusion); + } else { + extrusion.fuzzify = true; } } + + if (params.config.fuzzy_skin == FuzzySkinType::External) { + ClipperLib_Z::Paths loops_paths; + loops_paths.reserve(closed_loop_extrusions.size()); + for (const auto &cl_extrusion : closed_loop_extrusions) { + assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back()); + size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); + ClipperLib_Z::Path loop_path; + loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1); + for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it) + loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); + loops_paths.emplace_back(loop_path); + } + + ClipperLib_Z::Clipper clipper; + clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true); + ClipperLib_Z::PolyTree loops_polytree; + clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); + + for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) { + // The whole contour must have the same index. + coord_t polygon_idx = child_node->Contour.front().z(); + bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(), + [&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); }); + if (has_same_idx) + closed_loop_extrusions[polygon_idx]->fuzzify = true; + } } } + + if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty()) + out_loops.append(extrusion_coll); + + ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); + const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; + if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) + infill_contour.clear(); // Infill region is too small, so let's filter it out. + + // create one more offset to be used as boundary for fill + // we offset by half the perimeter spacing (to get to the actual infill boundary) + // and then we offset back and forth by half the infill spacing to only consider the + // non-collapsing regions + coord_t inset = + (loop_number < 0) ? 0 : + (loop_number == 0) ? + // one loop + ext_perimeter_spacing: + // two or more loops? + perimeter_spacing; + + inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset)))); + Polygons pp; + for (ExPolygon &ex : infill_contour) + ex.simplify_p(params.scaled_resolution, &pp); + // collapse too narrow infill areas + const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + // append infill areas to fill_surfaces + ExPolygons infill_areas = + offset2_ex( + union_ex(pp), + float(- min_perimeter_infill_spacing / 2.), + float(inset + min_perimeter_infill_spacing / 2.)); + + if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs && + params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) { + // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material + auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, + lower_slices_polygons_cache, + params.overhang_flow, params.scaled_resolution, + params.object_config, params.print_config); + if (!extra_perimeters.empty()) { + ExtrusionEntityCollection &this_islands_perimeters = static_cast(*out_loops.entities.back()); + ExtrusionEntitiesPtr old_entities; + old_entities.swap(this_islands_perimeters.entities); + for (ExtrusionPaths &paths : extra_perimeters) + this_islands_perimeters.append(std::move(paths)); + append(this_islands_perimeters.entities, old_entities); + infill_areas = diff_ex(infill_areas, filled_area); + } + } + + append(out_fill_expolygons, std::move(infill_areas)); } -void PerimeterGenerator::process_classic() +void PerimeterGenerator::process_classic( + // Inputs: + const Parameters ¶ms, + const Surface &surface, + const ExPolygons *lower_slices, + // Cache: + Polygons &lower_slices_polygons_cache, + // Output: + // Loops with the external thin walls + ExtrusionEntityCollection &out_loops, + // Gaps without the thin walls + ExtrusionEntityCollection &out_gap_fill, + // Infills without the gap fills + ExPolygons &out_fill_expolygons) { // other perimeters - m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); - coord_t perimeter_width = this->perimeter_flow.scaled_width(); - coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); - + coord_t perimeter_width = params.perimeter_flow.scaled_width(); + coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing(); // external perimeters - m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); - coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); - coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); - coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); - - // overhang perimeters - m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); - + coord_t ext_perimeter_width = params.ext_perimeter_flow.scaled_width(); + coord_t ext_perimeter_spacing = params.ext_perimeter_flow.scaled_spacing(); + coord_t ext_perimeter_spacing2 = scaled(0.5f * (params.ext_perimeter_flow.spacing() + params.perimeter_flow.spacing())); // solid infill - coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); + coord_t solid_infill_spacing = params.solid_infill_flow.scaled_spacing(); // Calculate the minimum required spacing between two adjacent traces. // This should be equal to the nominal flow spacing but we experiment @@ -1266,277 +1295,261 @@ void PerimeterGenerator::process_classic() // internal flow which is unrelated. coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); - bool has_gap_fill = this->config->gap_fill_enabled.value && this->config->gap_fill_speed.value > 0; + bool has_gap_fill = params.config.gap_fill_enabled.value && params.config.gap_fill_speed.value > 0; // prepare grown lower layer slices for overhang detection - if (this->lower_slices != NULL && this->config->overhangs) { + if (params.config.overhangs && lower_slices != nullptr && lower_slices_polygons_cache.empty()) { // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer - double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); - m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2))); + double nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder-1); + lower_slices_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter/2))); } // we need to process each island separately because we might have different // extra perimeters for each one - for (const Surface &surface : this->slices->surfaces) { - // detect how many perimeters must be generated for this island - int loop_number = this->config->perimeters + surface.extra_perimeters - 1; // 0-indexed loops - ExPolygons last = union_ex(surface.expolygon.simplify_p(m_scaled_resolution)); - ExPolygons gaps; - if (loop_number >= 0) { - // In case no perimeters are to be generated, loop_number will equal to -1. - std::vector contours(loop_number+1); // depth => loops - std::vector holes(loop_number+1); // depth => loops - ThickPolylines thin_walls; - // we loop one time more than needed in order to find gaps after the last perimeter was applied - for (int i = 0;; ++ i) { // outer loop is 0 - // Calculate next onion shell of perimeters. - ExPolygons offsets; - if (i == 0) { - // the minimum thickness of a single loop is: - // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 - offsets = this->config->thin_walls ? - offset2_ex( - last, - - float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1), - + float(ext_min_spacing / 2. - 1)) : - offset_ex(last, - float(ext_perimeter_width / 2.)); - // look for thin walls - if (this->config->thin_walls) { - // 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 = opening_ex( - // medial axis requires non-overlapping geometry - diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), - float(min_width / 2.)); - // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop - for (ExPolygon &ex : expp) - ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); - } - if (m_spiral_vase && offsets.size() > 1) { - // Remove all but the largest area polygon. - keep_largest_contour_only(offsets); - } - } else { - //FIXME Is this offset correct if the line width of the inner perimeters differs - // from the line width of the infill? - coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing; - offsets = this->config->thin_walls ? - // This path will ensure, that the perimeters do not overfill, as in - // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters - // excessively, creating gaps, which then need to be filled in by the not very - // reliable gap fill algorithm. - // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than - // the original. - offset2_ex(last, - - float(distance + min_spacing / 2. - 1.), - float(min_spacing / 2. - 1.)) : - // If "detect thin walls" is not enabled, this paths will be entered, which - // leads to overflows, as in prusa3d/Slic3r GH #32 - offset_ex(last, - float(distance)); - // look for gaps - if (has_gap_fill) - // not using safety offset here would "detect" very narrow gaps - // (but still long enough to escape the area threshold) that gap fill - // won't be able to fill but we'd still remove from infill area - append(gaps, diff_ex( - offset(last, - float(0.5 * distance)), - offset(offsets, float(0.5 * distance + 10)))); // safety offset + // detect how many perimeters must be generated for this island + int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops + ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution)); + ExPolygons gaps; + if (loop_number >= 0) { + // In case no perimeters are to be generated, loop_number will equal to -1. + std::vector contours(loop_number+1); // depth => loops + std::vector holes(loop_number+1); // depth => loops + ThickPolylines thin_walls; + // we loop one time more than needed in order to find gaps after the last perimeter was applied + for (int i = 0;; ++ i) { // outer loop is 0 + // Calculate next onion shell of perimeters. + ExPolygons offsets; + if (i == 0) { + // the minimum thickness of a single loop is: + // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 + offsets = params.config.thin_walls ? + offset2_ex( + last, + - float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1), + + float(ext_min_spacing / 2. - 1)) : + offset_ex(last, - float(ext_perimeter_width / 2.)); + // look for thin walls + if (params.config.thin_walls) { + // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width + // (actually, something larger than that still may exist due to mitering or other causes) + coord_t min_width = coord_t(scale_(params.ext_perimeter_flow.nozzle_diameter() / 3)); + ExPolygons expp = opening_ex( + // medial axis requires non-overlapping geometry + diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), + float(min_width / 2.)); + // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop + for (ExPolygon &ex : expp) + ex.medial_axis(min_width, ext_perimeter_width + ext_perimeter_spacing2, &thin_walls); } - if (offsets.empty()) { - // Store the number of loops actually generated. - loop_number = i - 1; - // No region left to be filled in. - last.clear(); - break; - } else if (i > loop_number) { - // If i > loop_number, we were looking just for gaps. - break; + if (params.spiral_vase && offsets.size() > 1) { + // Remove all but the largest area polygon. + keep_largest_contour_only(offsets); } - { - const bool fuzzify_contours = this->config->fuzzy_skin != FuzzySkinType::None && i == 0 && this->layer_id > 0; - const bool fuzzify_holes = fuzzify_contours && this->config->fuzzy_skin == FuzzySkinType::All; - for (const ExPolygon &expolygon : offsets) { - // Outer contour may overlap with an inner contour, - // inner contour may overlap with another inner contour, - // outer contour may overlap with itself. - //FIXME evaluate the overlaps, annotate each point with an overlap depth, - // compensate for the depth of intersection. - contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours); + } else { + //FIXME Is this offset correct if the line width of the inner perimeters differs + // from the line width of the infill? + coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing; + offsets = params.config.thin_walls ? + // This path will ensure, that the perimeters do not overfill, as in + // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters + // excessively, creating gaps, which then need to be filled in by the not very + // reliable gap fill algorithm. + // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than + // the original. + offset2_ex(last, + - float(distance + min_spacing / 2. - 1.), + float(min_spacing / 2. - 1.)) : + // If "detect thin walls" is not enabled, this paths will be entered, which + // leads to overflows, as in prusa3d/Slic3r GH #32 + offset_ex(last, - float(distance)); + // look for gaps + if (has_gap_fill) + // not using safety offset here would "detect" very narrow gaps + // (but still long enough to escape the area threshold) that gap fill + // won't be able to fill but we'd still remove from infill area + append(gaps, diff_ex( + offset(last, - float(0.5 * distance)), + offset(offsets, float(0.5 * distance + 10)))); // safety offset + } + if (offsets.empty()) { + // Store the number of loops actually generated. + loop_number = i - 1; + // No region left to be filled in. + last.clear(); + break; + } else if (i > loop_number) { + // If i > loop_number, we were looking just for gaps. + break; + } + { + const bool fuzzify_contours = params.config.fuzzy_skin != FuzzySkinType::None && i == 0 && params.layer_id > 0; + const bool fuzzify_holes = fuzzify_contours && params.config.fuzzy_skin == FuzzySkinType::All; + for (const ExPolygon &expolygon : offsets) { + // Outer contour may overlap with an inner contour, + // inner contour may overlap with another inner contour, + // outer contour may overlap with itself. + //FIXME evaluate the overlaps, annotate each point with an overlap depth, + // compensate for the depth of intersection. + contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours); - if (! expolygon.holes.empty()) { - holes[i].reserve(holes[i].size() + expolygon.holes.size()); - for (const Polygon &hole : expolygon.holes) - holes[i].emplace_back(hole, i, false, fuzzify_holes); - } + if (! expolygon.holes.empty()) { + holes[i].reserve(holes[i].size() + expolygon.holes.size()); + for (const Polygon &hole : expolygon.holes) + holes[i].emplace_back(hole, i, false, fuzzify_holes); } } - last = std::move(offsets); - if (i == loop_number && (! has_gap_fill || this->config->fill_density.value == 0)) { - // The last run of this loop is executed to collect gaps for gap fill. - // As the gap fill is either disabled or not - break; - } } - - // nest loops: holes first - for (int d = 0; d <= loop_number; ++ d) { - PerimeterGeneratorLoops &holes_d = holes[d]; - // loop through all holes having depth == d - for (int i = 0; i < (int)holes_d.size(); ++ i) { - const PerimeterGeneratorLoop &loop = holes_d[i]; - // find the hole loop that contains this one, if any - for (int t = d + 1; t <= loop_number; ++ t) { - for (int j = 0; j < (int)holes[t].size(); ++ j) { - PerimeterGeneratorLoop &candidate_parent = holes[t][j]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - holes_d.erase(holes_d.begin() + i); - -- i; - goto NEXT_LOOP; - } - } - } - // if no hole contains this hole, find the contour loop that contains it - for (int t = loop_number; t >= 0; -- t) { - for (int j = 0; j < (int)contours[t].size(); ++ j) { - PerimeterGeneratorLoop &candidate_parent = contours[t][j]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - holes_d.erase(holes_d.begin() + i); - -- i; - goto NEXT_LOOP; - } - } - } - NEXT_LOOP: ; - } - } - // nest contour loops - for (int d = loop_number; d >= 1; -- d) { - PerimeterGeneratorLoops &contours_d = contours[d]; - // loop through all contours having depth == d - for (int i = 0; i < (int)contours_d.size(); ++ i) { - const PerimeterGeneratorLoop &loop = contours_d[i]; - // find the contour loop that contains it - for (int t = d - 1; t >= 0; -- t) { - for (size_t j = 0; j < contours[t].size(); ++ j) { - PerimeterGeneratorLoop &candidate_parent = contours[t][j]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - contours_d.erase(contours_d.begin() + i); - -- i; - goto NEXT_CONTOUR; - } - } - } - NEXT_CONTOUR: ; - } - } - // at this point, all loops should be in contours[0] - ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls); - // if brim will be printed, reverse the order of perimeters so that - // we continue inwards after having finished the brim - // TODO: add test for perimeter order - if (this->config->external_perimeters_first || - (this->layer_id == 0 && this->object_config->brim_width.value > 0)) - entities.reverse(); - // append perimeters for this slice as a collection - if (! entities.empty()) - this->loops->append(entities); - } // for each loop of an island - - // fill gaps - if (! gaps.empty()) { - // collapse - double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); - double max = 2. * perimeter_spacing; - ExPolygons gaps_ex = diff_ex( - //FIXME offset2 would be enough and cheaper. - opening_ex(gaps, float(min / 2.)), - offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); - ThickPolylines polylines; - for (const ExPolygon &ex : gaps_ex) - ex.medial_axis(max, min, &polylines); - if (! polylines.empty()) { - ExtrusionEntityCollection gap_fill; - variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities); - /* Make sure we don't infill narrow parts that are already gap-filled - (we only consider this surface's gaps to reduce the diff() complexity). - Growing actual extrusions ensures that gaps not filled by medial axis - are not subtracted from fill surfaces (they might be too short gaps - that medial axis skips but infill might join with other infill regions - and use zigzag). */ - //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, - // therefore it may cover the area, but no the volume. - last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f)); - this->gap_fill->append(std::move(gap_fill.entities)); - } - } - - // create one more offset to be used as boundary for fill - // we offset by half the perimeter spacing (to get to the actual infill boundary) - // and then we offset back and forth by half the infill spacing to only consider the - // non-collapsing regions - coord_t inset = - (loop_number < 0) ? 0 : - (loop_number == 0) ? - // one loop - ext_perimeter_spacing / 2 : - // two or more loops? - perimeter_spacing / 2; - // only apply infill overlap if we actually have one perimeter - if (inset > 0) - inset -= coord_t(scale_(this->config->get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2)))); - // simplify infill contours according to resolution - Polygons pp; - for (ExPolygon &ex : last) - ex.simplify_p(m_scaled_resolution, &pp); - // collapse too narrow infill areas - coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - // append infill areas to fill_surfaces - ExPolygons infill_areas = offset2_ex(union_ex(pp), float(-inset - min_perimeter_infill_spacing / 2.), - float(min_perimeter_infill_spacing / 2.)); - this->fill_surfaces->append(infill_areas, stInternal); - - if (this->lower_slices != nullptr && this->config->overhangs && this->config->extra_perimeters_on_overhangs && - this->config->perimeters > 0 && this->layer_id > this->object_config->raft_layers) { - // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material - auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, - this->lower_slices_polygons(), - this->overhang_flow, this->m_scaled_resolution, - *this->object_config, *this->print_config); - if (!extra_perimeters.empty()) { - ExtrusionEntityCollection *this_islands_perimeters = static_cast(this->loops->entities.back()); - ExtrusionEntityCollection new_perimeters{}; - new_perimeters.no_sort = this_islands_perimeters->no_sort; - for (const ExtrusionPaths &paths : extra_perimeters) { new_perimeters.append(paths); } - new_perimeters.append(this_islands_perimeters->entities); - this_islands_perimeters->swap(new_perimeters); - - SurfaceCollection orig_surfaces = *this->fill_surfaces; - this->fill_surfaces->clear(); - for (const auto &surface : orig_surfaces.surfaces) { - auto new_surfaces = diff_ex({surface.expolygon}, filled_area); - this->fill_surfaces->append(new_surfaces, surface); - } + last = std::move(offsets); + if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) { + // The last run of this loop is executed to collect gaps for gap fill. + // As the gap fill is either disabled or not + break; } } - } // for each island -} + // nest loops: holes first + for (int d = 0; d <= loop_number; ++ d) { + PerimeterGeneratorLoops &holes_d = holes[d]; + // loop through all holes having depth == d + for (int i = 0; i < (int)holes_d.size(); ++ i) { + const PerimeterGeneratorLoop &loop = holes_d[i]; + // find the hole loop that contains this one, if any + for (int t = d + 1; t <= loop_number; ++ t) { + for (int j = 0; j < (int)holes[t].size(); ++ j) { + PerimeterGeneratorLoop &candidate_parent = holes[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + holes_d.erase(holes_d.begin() + i); + -- i; + goto NEXT_LOOP; + } + } + } + // if no hole contains this hole, find the contour loop that contains it + for (int t = loop_number; t >= 0; -- t) { + for (int j = 0; j < (int)contours[t].size(); ++ j) { + PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + holes_d.erase(holes_d.begin() + i); + -- i; + goto NEXT_LOOP; + } + } + } + NEXT_LOOP: ; + } + } + // nest contour loops + for (int d = loop_number; d >= 1; -- d) { + PerimeterGeneratorLoops &contours_d = contours[d]; + // loop through all contours having depth == d + for (int i = 0; i < (int)contours_d.size(); ++ i) { + const PerimeterGeneratorLoop &loop = contours_d[i]; + // find the contour loop that contains it + for (int t = d - 1; t >= 0; -- t) { + for (size_t j = 0; j < contours[t].size(); ++ j) { + PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + contours_d.erase(contours_d.begin() + i); + -- i; + goto NEXT_CONTOUR; + } + } + } + NEXT_CONTOUR: ; + } + } + // at this point, all loops should be in contours[0] + ExtrusionEntityCollection entities = traverse_loops_classic(params, lower_slices_polygons_cache, contours.front(), thin_walls); + // if brim will be printed, reverse the order of perimeters so that + // we continue inwards after having finished the brim + // TODO: add test for perimeter order + if (params.config.external_perimeters_first || + (params.layer_id == 0 && params.object_config.brim_width.value > 0)) + entities.reverse(); + // append perimeters for this slice as a collection + if (! entities.empty()) + out_loops.append(entities); + } // for each loop of an island -bool PerimeterGeneratorLoop::is_internal_contour() const -{ - // An internal contour is a contour containing no other contours - if (! this->is_contour) - return false; - for (const PerimeterGeneratorLoop &loop : this->children) - if (loop.is_contour) - return false; - return true; + // fill gaps + if (! gaps.empty()) { + // collapse + double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); + double max = 2. * perimeter_spacing; + ExPolygons gaps_ex = diff_ex( + //FIXME offset2 would be enough and cheaper. + opening_ex(gaps, float(min / 2.)), + offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); + ThickPolylines polylines; + for (const ExPolygon &ex : gaps_ex) + ex.medial_axis(min, max, &polylines); + if (! polylines.empty()) { + ExtrusionEntityCollection gap_fill; + variable_width_classic(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities); + /* Make sure we don't infill narrow parts that are already gap-filled + (we only consider this surface's gaps to reduce the diff() complexity). + Growing actual extrusions ensures that gaps not filled by medial axis + are not subtracted from fill surfaces (they might be too short gaps + that medial axis skips but infill might join with other infill regions + and use zigzag). */ + //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, + // therefore it may cover the area, but no the volume. + last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f)); + out_gap_fill.append(std::move(gap_fill.entities)); + } + } + + // create one more offset to be used as boundary for fill + // we offset by half the perimeter spacing (to get to the actual infill boundary) + // and then we offset back and forth by half the infill spacing to only consider the + // non-collapsing regions + coord_t inset = + (loop_number < 0) ? 0 : + (loop_number == 0) ? + // one loop + ext_perimeter_spacing / 2 : + // two or more loops? + perimeter_spacing / 2; + // only apply infill overlap if we actually have one perimeter + if (inset > 0) + inset -= coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2)))); + // simplify infill contours according to resolution + Polygons pp; + for (ExPolygon &ex : last) + ex.simplify_p(params.scaled_resolution, &pp); + // collapse too narrow infill areas + coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + // append infill areas to fill_surfaces + ExPolygons infill_areas = + offset2_ex( + union_ex(pp), + float(- inset - min_perimeter_infill_spacing / 2.), + float(min_perimeter_infill_spacing / 2.)); + + if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs && + params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) { + // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material + auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, + lower_slices_polygons_cache, + params.overhang_flow, params.scaled_resolution, + params.object_config, params.print_config); + if (!extra_perimeters.empty()) { + ExtrusionEntityCollection &this_islands_perimeters = static_cast(*out_loops.entities.back()); + ExtrusionEntitiesPtr old_entities; + old_entities.swap(this_islands_perimeters.entities); + for (ExtrusionPaths &paths : extra_perimeters) + this_islands_perimeters.append(std::move(paths)); + append(this_islands_perimeters.entities, old_entities); + infill_areas = diff_ex(infill_areas, filled_area); + } + } + + append(out_fill_expolygons, std::move(infill_areas)); } } diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index ecf09c593d..33d735a3ee 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -10,70 +10,93 @@ namespace Slic3r { -class PerimeterGenerator { -public: - // Inputs: - const SurfaceCollection *slices; - const ExPolygons *lower_slices; +namespace PerimeterGenerator +{ + +struct Parameters { + Parameters( + double layer_height, + int layer_id, + Flow perimeter_flow, + Flow ext_perimeter_flow, + Flow overhang_flow, + Flow solid_infill_flow, + const PrintRegionConfig &config, + const PrintObjectConfig &object_config, + const PrintConfig &print_config, + const bool spiral_vase) : + layer_height(layer_height), + layer_id(layer_id), + perimeter_flow(perimeter_flow), + ext_perimeter_flow(ext_perimeter_flow), + overhang_flow(overhang_flow), + solid_infill_flow(solid_infill_flow), + config(config), + object_config(object_config), + print_config(print_config), + spiral_vase(spiral_vase), + scaled_resolution(scaled(print_config.gcode_resolution.value)), + mm3_per_mm(perimeter_flow.mm3_per_mm()), + ext_mm3_per_mm(ext_perimeter_flow.mm3_per_mm()), + mm3_per_mm_overhang(overhang_flow.mm3_per_mm()) + { + } + + // Input parameters double layer_height; int layer_id; Flow perimeter_flow; Flow ext_perimeter_flow; Flow overhang_flow; Flow solid_infill_flow; - const PrintRegionConfig *config; - const PrintObjectConfig *object_config; - const PrintConfig *print_config; - // Outputs: - ExtrusionEntityCollection *loops; - ExtrusionEntityCollection *gap_fill; - SurfaceCollection *fill_surfaces; - - PerimeterGenerator( - // Input: - const SurfaceCollection* slices, - double layer_height, - Flow flow, - const PrintRegionConfig* config, - const PrintObjectConfig* object_config, - const PrintConfig* print_config, - const bool spiral_vase, - // Output: - // Loops with the external thin walls - ExtrusionEntityCollection* loops, - // Gaps without the thin walls - ExtrusionEntityCollection* gap_fill, - // Infills without the gap fills - SurfaceCollection* fill_surfaces) - : slices(slices), lower_slices(nullptr), layer_height(layer_height), - layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow), - overhang_flow(flow), solid_infill_flow(flow), - config(config), object_config(object_config), print_config(print_config), - m_spiral_vase(spiral_vase), - m_scaled_resolution(scaled(print_config->gcode_resolution.value)), - loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), - m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1) - {} + const PrintRegionConfig &config; + const PrintObjectConfig &object_config; + const PrintConfig &print_config; - void process_classic(); - void process_arachne(); - - double ext_mm3_per_mm() const { return m_ext_mm3_per_mm; } - double mm3_per_mm() const { return m_mm3_per_mm; } - double mm3_per_mm_overhang() const { return m_mm3_per_mm_overhang; } - Polygons lower_slices_polygons() const { return m_lower_slices_polygons; } + // Derived parameters + bool spiral_vase; + double scaled_resolution; + double ext_mm3_per_mm; + double mm3_per_mm; + double mm3_per_mm_overhang; private: - bool m_spiral_vase; - double m_scaled_resolution; - double m_ext_mm3_per_mm; - double m_mm3_per_mm; - double m_mm3_per_mm_overhang; - Polygons m_lower_slices_polygons; + Parameters() = delete; }; +void process_classic( + // Inputs: + const Parameters ¶ms, + const Surface &surface, + const ExPolygons *lower_slices, + // Cache: + Polygons &lower_slices_polygons_cache, + // Output: + // Loops with the external thin walls + ExtrusionEntityCollection &out_loops, + // Gaps without the thin walls + ExtrusionEntityCollection &out_gap_fill, + // Infills without the gap fills + ExPolygons &out_fill_expolygons); + +void process_arachne( + // Inputs: + const Parameters ¶ms, + const Surface &surface, + const ExPolygons *lower_slices, + // Cache: + Polygons &lower_slices_polygons_cache, + // Output: + // Loops with the external thin walls + ExtrusionEntityCollection &out_loops, + // Gaps without the thin walls + ExtrusionEntityCollection &out_gap_fill, + // Infills without the gap fills + ExPolygons &out_fill_expolygons); + ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, float tolerance, float merge_tolerance); -} +} // namespace PerimeterGenerator +} // namespace Slic3r #endif diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 8c28616749..beb496b288 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -57,98 +57,6 @@ void Point::rotate(double angle, const Point ¢er) (*this)(1) = (coord_t)round( (double)center(1) + c * dy + s * dx ); } -int Point::nearest_point_index(const Points &points) const -{ - PointConstPtrs p; - p.reserve(points.size()); - for (Points::const_iterator it = points.begin(); it != points.end(); ++it) - p.push_back(&*it); - return this->nearest_point_index(p); -} - -int Point::nearest_point_index(const PointConstPtrs &points) const -{ - int idx = -1; - double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough - - for (PointConstPtrs::const_iterator it = points.begin(); it != points.end(); ++it) { - /* If the X distance of the candidate is > than the total distance of the - best previous candidate, we know we don't want it */ - double d = sqr((*this)(0) - (*it)->x()); - if (distance != -1 && d > distance) continue; - - /* If the Y distance of the candidate is > than the total distance of the - best previous candidate, we know we don't want it */ - d += sqr((*this)(1) - (*it)->y()); - if (distance != -1 && d > distance) continue; - - idx = it - points.begin(); - distance = d; - - if (distance < EPSILON) break; - } - - return idx; -} - -int Point::nearest_point_index(const PointPtrs &points) const -{ - PointConstPtrs p; - p.reserve(points.size()); - for (PointPtrs::const_iterator it = points.begin(); it != points.end(); ++it) - p.push_back(*it); - return this->nearest_point_index(p); -} - -bool Point::nearest_point(const Points &points, Point* point) const -{ - int idx = this->nearest_point_index(points); - if (idx == -1) return false; - *point = points.at(idx); - return true; -} - -Point Point::projection_onto(const MultiPoint &poly) const -{ - Point running_projection = poly.first_point(); - double running_min = (running_projection - *this).cast().norm(); - - Lines lines = poly.lines(); - for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { - Point point_temp = this->projection_onto(*line); - if ((point_temp - *this).cast().norm() < running_min) { - running_projection = point_temp; - running_min = (running_projection - *this).cast().norm(); - } - } - return running_projection; -} - -Point Point::projection_onto(const Line &line) const -{ - if (line.a == line.b) return line.a; - - /* - (Ported from VisiLibity by Karl J. Obermeyer) - The projection of point_temp onto the line determined by - line_segment_temp can be represented as an affine combination - expressed in the form projection of - Point = theta*line_segment_temp.first + (1.0-theta)*line_segment_temp.second. - If theta is outside the interval [0,1], then one of the Line_Segment's endpoints - must be closest to calling Point. - */ - double lx = (double)(line.b(0) - line.a(0)); - double ly = (double)(line.b(1) - line.a(1)); - double theta = ( (double)(line.b(0) - (*this)(0))*lx + (double)(line.b(1)- (*this)(1))*ly ) - / ( sqr(lx) + sqr(ly) ); - - if (0.0 <= theta && theta <= 1.0) - return (theta * line.a.cast() + (1.0-theta) * line.b.cast()).cast(); - - // Else pick closest endpoint. - return ((line.a - *this).cast().squaredNorm() < (line.b - *this).cast().squaredNorm()) ? line.a : line.b; -} - bool has_duplicate_points(std::vector &&pts) { std::sort(pts.begin(), pts.end()); @@ -197,6 +105,29 @@ BoundingBoxf get_extents(const std::vector &pts) return bbox; } +int nearest_point_index(const Points &points, const Point &pt) +{ + int64_t distance = std::numeric_limits::max(); + int idx = -1; + + for (const Point &pt2 : points) { + // If the X distance of the candidate is > than the total distance of the + // best previous candidate, we know we don't want it. + int64_t d = sqr(pt2.x() - pt.x()); + if (d < distance) { + // If the Y distance of the candidate is > than the total distance of the + // best previous candidate, we know we don't want it. + d += sqr(pt2.y() - pt.y()); + if (d < distance) { + idx = &pt2 - points.data(); + distance = d; + } + } + } + + return idx; +} + std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf) { return stm << pointf(0) << "," << pointf(1); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index ae1e882768..6ab1354ee5 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -17,8 +17,6 @@ namespace Slic3r { class BoundingBox; class BoundingBoxf; -class Line; -class MultiPoint; class Point; using Vector = Point; @@ -183,13 +181,6 @@ public: Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; } Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } - Point rotate_90_degree_ccw() const { return Point(-this->y(), this->x()); } - int nearest_point_index(const Points &points) const; - int nearest_point_index(const PointConstPtrs &points) const; - int nearest_point_index(const PointPtrs &points) const; - bool nearest_point(const Points &points, Point* point) const; - Point projection_onto(const MultiPoint &poly) const; - Point projection_onto(const Line &line) const; }; inline bool operator<(const Point &l, const Point &r) @@ -242,6 +233,14 @@ BoundingBox get_extents(const Points &pts); BoundingBox get_extents(const std::vector &pts); BoundingBoxf get_extents(const std::vector &pts); +int nearest_point_index(const Points &points, const Point &pt); + +inline std::pair nearest_point(const Points &points, const Point &pt) +{ + int idx = nearest_point_index(points, pt); + return idx == -1 ? std::make_pair(Point(), false) : std::make_pair(points[idx], true); +} + // Test for duplicate points in a vector of points. // The points are copied, sorted and checked for duplicates globally. bool has_duplicate_points(std::vector &&pts); diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index bf0abd4fa2..d342f3d917 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -6,6 +6,17 @@ namespace Slic3r { +double Polygon::length() const +{ + double l = 0; + if (this->points.size() > 1) { + l = (this->points.back() - this->points.front()).cast().norm(); + for (size_t i = 1; i < this->points.size(); ++ i) + l += (this->points[i] - this->points[i - 1]).cast().norm(); + } + return l; +} + Lines Polygon::lines() const { return to_lines(*this); @@ -88,32 +99,11 @@ void Polygon::douglas_peucker(double tolerance) this->points = std::move(p); } -// Does an unoriented polygon contain a point? -// Tested by counting intersections along a horizontal line. -bool Polygon::contains(const Point &p) const -{ - // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html - bool result = false; - Points::const_iterator i = this->points.begin(); - Points::const_iterator j = this->points.end() - 1; - for (; i != this->points.end(); j = i ++) - if (i->y() > p.y() != j->y() > p.y()) -#if 1 - if (Vec2d v = (*j - *i).cast(); - // p.x() is below the line - p.x() - i->x() < double(p.y() - i->y()) * v.x() / v.y()) -#else - // Orientation predicated relative to i-th point. - if (double orient = (double)(p.x() - i->x()) * (double)(j->y() - i->y()) - (double)(p.y() - i->y()) * (double)(j->x() - i->x()); - (i->y() > j->y()) ? (orient > 0.) : (orient < 0.)) -#endif - result = !result; - return result; -} - -// this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons() Polygons Polygon::simplify(double tolerance) const { + // Works on CCW polygons only, CW contour will be reoriented to CCW by Clipper's simplify_polygons()! + assert(this->is_counter_clockwise()); + // repeat first point at the end in order to apply Douglas-Peucker // on the whole polygon Points points = this->points; @@ -126,13 +116,6 @@ Polygons Polygon::simplify(double tolerance) const return simplify_polygons(pp); } -void Polygon::simplify(double tolerance, Polygons &polygons) const -{ - Polygons pp = this->simplify(tolerance); - polygons.reserve(polygons.size() + pp.size()); - polygons.insert(polygons.end(), pp.begin(), pp.end()); -} - // Only call this on convex polygons or it will return invalid results void Polygon::triangulate_convex(Polygons* polygons) const { @@ -167,6 +150,64 @@ Point Polygon::centroid() const return Point(Vec2d(c / (3. * area_sum))); } +bool Polygon::intersection(const Line &line, Point *intersection) const +{ + if (this->points.size() < 2) + return false; + if (Line(this->points.front(), this->points.back()).intersection(line, intersection)) + return true; + for (size_t i = 1; i < this->points.size(); ++ i) + if (Line(this->points[i - 1], this->points[i]).intersection(line, intersection)) + return true; + return false; +} + +bool Polygon::first_intersection(const Line& line, Point* intersection) const +{ + if (this->points.size() < 2) + return false; + + bool found = false; + double dmin = 0.; + Line l(this->points.back(), this->points.front()); + for (size_t i = 0; i < this->points.size(); ++ i) { + l.b = this->points[i]; + Point ip; + if (l.intersection(line, &ip)) { + if (! found) { + found = true; + dmin = (line.a - ip).cast().squaredNorm(); + *intersection = ip; + } else { + double d = (line.a - ip).cast().squaredNorm(); + if (d < dmin) { + dmin = d; + *intersection = ip; + } + } + } + l.a = l.b; + } + return found; +} + +bool Polygon::intersections(const Line &line, Points *intersections) const +{ + if (this->points.size() < 2) + return false; + + size_t intersections_size = intersections->size(); + Line l(this->points.back(), this->points.front()); + for (size_t i = 0; i < this->points.size(); ++ i) { + l.b = this->points[i]; + Point intersection; + if (l.intersection(line, &intersection)) + intersections->emplace_back(std::move(intersection)); + l.a = l.b; + } + return intersections->size() > intersections_size; +} + // Filter points from poly to the output with the help of FilterFn. // filter function receives two vectors: // v1: this_point - previous_point @@ -516,6 +557,56 @@ void remove_collinear(Polygons &polys) remove_collinear(poly); } +Polygons polygons_simplify(const Polygons &source_polygons, double tolerance) +{ + Polygons out; + out.reserve(source_polygons.size()); + for (const Polygon &source_polygon : source_polygons) { + // Run Douglas / Peucker simplification algorithm on an open polyline (by repeating the first point at the end of the polyline), + Points simplified = MultiPoint::_douglas_peucker(to_polyline(source_polygon).points, tolerance); + // then remove the last (repeated) point. + simplified.pop_back(); + // Simplify the decimated contour by ClipperLib. + bool ccw = ClipperLib::Area(simplified) > 0.; + for (Points &path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero)) { + if (! ccw) + // ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW. + std::reverse(path.begin(), path.end()); + out.emplace_back(std::move(path)); + } + } + return out; +} + +// Do polygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool polygons_match(const Polygon &l, const Polygon &r) +{ + if (l.size() != r.size()) + return false; + auto it_l = std::find(l.points.begin(), l.points.end(), r.points.front()); + if (it_l == l.points.end()) + return false; + auto it_r = r.points.begin(); + for (; it_l != l.points.end(); ++ it_l, ++ it_r) + if (*it_l != *it_r) + return false; + it_l = l.points.begin(); + for (; it_r != r.points.end(); ++ it_l, ++ it_r) + if (*it_l != *it_r) + return false; + return true; +} + +bool contains(const Polygon &polygon, const Point &p, bool border_result) +{ + if (const int poly_count_inside = ClipperLib::PointInPolygon(p, polygon.points); + poly_count_inside == -1) + return border_result; + else + return (poly_count_inside % 2) == 1; +} + bool contains(const Polygons &polygons, const Point &p, bool border_result) { int poly_count_inside = 0; diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index bd1c3c4cc8..3c4bb0e2ad 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -15,11 +15,14 @@ using Polygons = std::vector; using PolygonPtrs = std::vector; using ConstPolygonPtrs = std::vector; +// Returns true if inside. Returns border_result if on boundary. +bool contains(const Polygon& polygon, const Point& p, bool border_result = true); +bool contains(const Polygons& polygons, const Point& p, bool border_result = true); + class Polygon : public MultiPoint { public: Polygon() = default; - ~Polygon() override = default; explicit Polygon(const Points &points) : MultiPoint(points) {} Polygon(std::initializer_list points) : MultiPoint(points) {} Polygon(const Polygon &other) : MultiPoint(other.points) {} @@ -38,9 +41,10 @@ public: const Point& operator[](Points::size_type idx) const { return this->points[idx]; } // last point == first point for polygons - const Point& last_point() const override { return this->points.front(); } + const Point& last_point() const { return this->points.front(); } - Lines lines() const override; + double length() const; + Lines lines() const; Polyline split_at_vertex(const Point &point) const; // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_index(int index) const; @@ -58,13 +62,21 @@ public: void douglas_peucker(double tolerance); // Does an unoriented polygon contain a point? - // Tested by counting intersections along a horizontal line. - bool contains(const Point &point) const; + bool contains(const Point &point) const { return Slic3r::contains(*this, point, true); } + // Approximate on boundary test. + bool on_boundary(const Point &point, double eps) const + { return (this->point_projection(point) - point).cast().squaredNorm() < eps * eps; } + + // Works on CCW polygons only, CW contour will be reoriented to CCW by Clipper's simplify_polygons()! Polygons simplify(double tolerance) const; - void simplify(double tolerance, Polygons &polygons) const; void densify(float min_length, std::vector* lengths = nullptr); void triangulate_convex(Polygons* polygons) const; Point centroid() const; + + bool intersection(const Line& line, Point* intersection) const; + bool first_intersection(const Line& line, Point* intersection) const; + bool intersections(const Line &line, Points *intersections) const; + // Considering CCW orientation of this polygon, find all convex resp. concave points // with the angle at the vertex larger than a threshold. // Zero angle_threshold means to accept all convex resp. concave points. @@ -136,14 +148,7 @@ inline void polygons_append(Polygons &dst, Polygons &&src) } } -inline Polygons polygons_simplify(const Polygons &polys, double tolerance) -{ - Polygons out; - out.reserve(polys.size()); - for (const Polygon &p : polys) - polygons_append(out, p.simplify(tolerance)); - return out; -} +Polygons polygons_simplify(const Polygons &polys, double tolerance); inline void polygons_rotate(Polygons &polys, double angle) { @@ -204,18 +209,22 @@ inline Lines to_lines(const Polygons &polys) return lines; } -inline Polylines to_polylines(const Polygons &polys) +inline Polyline to_polyline(const Polygon &polygon) { - Polylines polylines; - polylines.assign(polys.size(), Polyline()); - size_t idx = 0; - for (Polygons::const_iterator it = polys.begin(); it != polys.end(); ++ it) { - Polyline &pl = polylines[idx ++]; - pl.points = it->points; - pl.points.push_back(it->points.front()); - } - assert(idx == polylines.size()); - return polylines; + Polyline out; + out.points.reserve(polygon.size() + 1); + out.points.assign(polygon.points.begin(), polygon.points.end()); + out.points.push_back(polygon.points.front()); + return out; +} + +inline Polylines to_polylines(const Polygons &polygons) +{ + Polylines out; + out.reserve(polygons.size()); + for (const Polygon &polygon : polygons) + out.emplace_back(to_polyline(polygon)); + return out; } inline Polylines to_polylines(Polygons &&polys) @@ -250,8 +259,9 @@ inline Polygons to_polygons(std::vector &&paths) return out; } -// Returns true if inside. Returns border_result if on boundary. -bool contains(const Polygons& polygons, const Point& p, bool border_result = true); +// Do polygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool polygons_match(const Polygon &l, const Polygon &r); Polygon make_circle(double radius, double error); Polygon make_circle_num_segments(double radius, size_t num_segments); diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 42d89c8827..e2dcabfe11 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -19,6 +19,14 @@ const Point& Polyline::leftmost_point() const return *p; } +double Polyline::length() const +{ + double l = 0; + for (size_t i = 1; i < this->points.size(); ++ i) + l += (this->points[i] - this->points[i - 1]).cast().norm(); + return l; +} + Lines Polyline::lines() const { Lines lines; @@ -146,9 +154,8 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const auto min_point_it = this->points.cbegin(); Point prev = this->points.front(); for (auto it = this->points.cbegin() + 1; it != this->points.cend(); ++ it) { - Point proj = point.projection_onto(Line(prev, *it)); - auto d2 = (proj - point).cast().squaredNorm(); - if (d2 < min_dist2) { + Point proj; + if (double d2 = line_alg::distance_to_squared(Line(prev, *it), point, &proj); d2 < min_dist2) { min_dist2 = d2; min_point_it = it; } @@ -235,9 +242,8 @@ std::pair foot_pt(const Points &polyline, const Point &pt) auto it = polyline.begin(); auto it_proj = polyline.begin(); for (++ it; it != polyline.end(); ++ it) { - Point foot_pt = pt.projection_onto(Line(prev, *it)); - double d2 = (foot_pt - pt).cast().squaredNorm(); - if (d2 < d2_min) { + Point foot_pt; + if (double d2 = line_alg::distance_to_squared(Line(prev, *it), pt, &foot_pt); d2 < d2_min) { d2_min = d2; foot_pt_min = foot_pt; it_proj = it; @@ -286,6 +292,14 @@ void ThickPolyline::clip_end(double distance) assert(this->width.size() == (this->points.size() - 1) * 2); } +double Polyline3::length() const +{ + double l = 0; + for (size_t i = 1; i < this->points.size(); ++ i) + l += (this->points[i] - this->points[i - 1]).cast().norm(); + return l; +} + Lines3 Polyline3::lines() const { Lines3 lines; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 547664d4ff..bee5e51ba5 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -17,7 +17,6 @@ typedef std::vector ThickPolylines; class Polyline : public MultiPoint { public: Polyline() = default; - ~Polyline() override = default; Polyline(const Polyline &other) : MultiPoint(other.points) {} Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {} Polyline(std::initializer_list list) : MultiPoint(list) {} @@ -64,11 +63,12 @@ public: Point& operator[](Points::size_type idx) { return this->points[idx]; } const Point& operator[](Points::size_type idx) const { return this->points[idx]; } - const Point& last_point() const override { return this->points.back(); } + double length() const; + const Point& last_point() const { return this->points.back(); } const Point& leftmost_point() const; - Lines lines() const override; + Lines lines() const; - virtual void clip_end(double distance); + void clip_end(double distance); void clip_start(double distance); void extend_end(double distance); void extend_start(double distance); @@ -170,7 +170,7 @@ public: std::swap(this->endpoints.first, this->endpoints.second); } - void clip_end(double distance) override; + void clip_end(double distance); std::vector width; std::pair endpoints; @@ -191,7 +191,8 @@ inline ThickPolylines to_thick_polylines(Polylines &&polylines, const coordf_t w class Polyline3 : public MultiPoint3 { public: - virtual Lines3 lines() const; + double length() const; + Lines3 lines() const; }; typedef std::vector Polylines3; diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index f0a81a71ad..74ba2e0f5b 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -418,7 +418,7 @@ public: size_t first_compatible_idx(PreferedCondition prefered_condition) const { size_t i = m_default_suppressed ? m_num_default_presets : 0; - size_t n = this->m_presets.size(); + size_t n = m_presets.size(); size_t i_compatible = n; int match_quality = -1; for (; i < n; ++ i) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 8afae13ab7..4e230dc6dc 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1223,7 +1223,7 @@ void Print::_make_wipe_tower() volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // try to assign some infills/objects for the wiping: - volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe); + volume_to_wipe = layer_tools.wiping_extrusions_nonconst().mark_wiping_extrusions(*this, layer_tools, current_extruder_id, extruder_id, volume_to_wipe); // add back the minimal amount toforce on the wipe tower: volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); @@ -1234,7 +1234,7 @@ void Print::_make_wipe_tower() current_extruder_id = extruder_id; } } - layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); + layer_tools.wiping_extrusions_nonconst().ensure_perimeters_infills_order(*this, layer_tools); if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0) break; } diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index b60401e96f..b0aa7bd1ec 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -103,7 +103,7 @@ std::string PrintBase::output_filepath(const std::string &path, const std::strin void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel /* warning_level */, const std::string &message, const PrintObjectBase* print_object) { - if (this->m_status_callback) { + if (m_status_callback) { auto status = print_object ? SlicingStatus(*print_object, step) : SlicingStatus(*this, step); m_status_callback(status); } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 48f9bec093..ab50c105da 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -131,6 +131,7 @@ void PrintObject::make_perimeters() // Revert the typed slices into untyped slices. if (m_typed_slices) { for (Layer *layer : m_layers) { + layer->clear_fills(); layer->restore_untyped_slices(); m_print->throw_if_canceled(); } @@ -157,7 +158,7 @@ void PrintObject::make_perimeters() m_print->throw_if_canceled(); LayerRegion &layerm = *m_layers[layer_idx]->get_region(region_id); const LayerRegion &upper_layerm = *m_layers[layer_idx+1]->get_region(region_id); - const Polygons upper_layerm_polygons = to_polygons(upper_layerm.slices.surfaces); + const Polygons upper_layerm_polygons = to_polygons(upper_layerm.slices().surfaces); // Filter upper layer polygons in intersection_ppl by their bounding boxes? // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; const double total_loop_length = total_length(upper_layerm_polygons); @@ -166,7 +167,8 @@ void PrintObject::make_perimeters() const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width(); const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing(); - for (Surface &slice : layerm.slices.surfaces) { + // slice is not const because slice.extra_perimeters is being incremented. + for (Surface &slice : layerm.m_slices.surfaces) { for (;;) { // compute the total thickness of perimeters const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2 @@ -418,12 +420,12 @@ void PrintObject::generate_support_spots() BOOST_LOG_TRIVIAL(debug) << "Searching support spots - start"; m_print->set_status(75, L("Searching support spots")); - if (this->m_config.support_material && !this->m_config.support_material_auto && + if (m_config.support_material && !m_config.support_material_auto && std::all_of(this->model_object()->volumes.begin(), this->model_object()->volumes.end(), [](const ModelVolume* mv){return mv->supported_facets.empty();}) ) { SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values}; - auto [issues, malformations] = SupportSpotsGenerator::full_search(this, params); + SupportSpotsGenerator::Issues issues = SupportSpotsGenerator::full_search(this, params); auto obj_transform = this->trafo_centered(); for (ModelVolume *model_volume : this->model_object()->volumes) { @@ -521,7 +523,7 @@ std::pair PrintObject::prepare m_print->throw_if_canceled(); const Layer *layer = this->layers()[idx_layer]; for (const LayerRegion *layerm : layer->regions()) - for (const Surface &surface : layerm->fill_surfaces.surfaces) + for (const Surface &surface : layerm->fill_surfaces()) if (surface.surface_type == stInternalBridge) append(out, triangulate_expolygon_3d(surface.expolygon, layer->bottom_z())); } @@ -906,13 +908,13 @@ void PrintObject::detect_surfaces_type() Surfaces top; if (upper_layer) { ExPolygons upper_slices = interface_shells ? - diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) : - diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); + diff_ex(layerm->slices().surfaces, upper_layer->m_regions[region_id]->slices().surfaces, ApplySafetyOffset::Yes) : + diff_ex(layerm->slices().surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); surfaces_append(top, opening_ex(upper_slices, offset), stTop); } else { // if no upper layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection - top = layerm->slices.surfaces; + top = layerm->slices().surfaces; for (Surface &surface : top) surface.surface_type = stTop; } @@ -933,7 +935,7 @@ void PrintObject::detect_surfaces_type() surfaces_append( bottom, opening_ex( - diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), + diff_ex(layerm->slices().surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces @@ -945,8 +947,8 @@ void PrintObject::detect_surfaces_type() bottom, opening_ex( diff_ex( - intersection(layerm->slices.surfaces, lower_layer->lslices), // supported - lower_layer->m_regions[region_id]->slices.surfaces, + intersection(layerm->slices().surfaces, lower_layer->lslices), // supported + lower_layer->m_regions[region_id]->slices().surfaces, ApplySafetyOffset::Yes), offset), stBottom); @@ -955,7 +957,7 @@ void PrintObject::detect_surfaces_type() } else { // if no lower layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection - bottom = layerm->slices.surfaces; + bottom = layerm->slices().surfaces; for (Surface &surface : bottom) surface.surface_type = stBottom; } @@ -984,13 +986,13 @@ void PrintObject::detect_surfaces_type() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // save surfaces to layer - Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->slices.surfaces; + Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->m_slices.surfaces; Surfaces surfaces_backup; if (! interface_shells) { surfaces_backup = std::move(surfaces_out); surfaces_out.clear(); } - const Surfaces &surfaces_prev = interface_shells ? layerm->slices.surfaces : surfaces_backup; + const Surfaces &surfaces_prev = interface_shells ? layerm->slices().surfaces : surfaces_backup; // find internal surfaces (difference between top/bottom surfaces and others) { @@ -1016,15 +1018,15 @@ void PrintObject::detect_surfaces_type() if (interface_shells) { // Move surfaces_new to layerm->slices.surfaces for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer) - m_layers[idx_layer]->m_regions[region_id]->slices.surfaces = std::move(surfaces_new[idx_layer]); + m_layers[idx_layer]->m_regions[region_id]->m_slices.set(std::move(surfaces_new[idx_layer])); } if (spiral_vase) { if (num_layers > 1) // Turn the last bottom layer infill to a top infill, so it will be extruded with a proper pattern. - m_layers[num_layers - 1]->m_regions[region_id]->slices.set_type(stTop); + m_layers[num_layers - 1]->m_regions[region_id]->m_slices.set_type(stTop); for (size_t i = num_layers; i < m_layers.size(); ++ i) - m_layers[i]->m_regions[region_id]->slices.set_type(stInternal); + m_layers[i]->m_regions[region_id]->m_slices.set_type(stInternal); } BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start"; @@ -1071,7 +1073,7 @@ void PrintObject::process_external_surfaces() bool expansions = false; bool voids = false; for (const LayerRegion *layerm : layer->regions()) { - for (const Surface &surface : layerm->fill_surfaces.surfaces) { + for (const Surface &surface : layerm->fill_surfaces()) { if (surface.surface_type == stInternal) voids = true; else @@ -1096,7 +1098,7 @@ void PrintObject::process_external_surfaces() Polygons voids; for (const LayerRegion *layerm : m_layers[layer_idx]->regions()) { if (layerm->region().config().fill_density.value == 0.) - for (const Surface &surface : layerm->fill_surfaces.surfaces) + for (const Surface &surface : layerm->fill_surfaces()) // Shrink the holes, let the layer above expand slightly inside the unsupported areas. polygons_append(voids, offset(surface.expolygon, unsupported_width)); } @@ -1125,7 +1127,7 @@ void PrintObject::process_external_surfaces() m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end"; } -} +} // void PrintObject::process_external_surfaces() void PrintObject::discover_vertical_shells() { @@ -1193,15 +1195,15 @@ void PrintObject::discover_vertical_shells() LayerRegion &layerm = *layer.m_regions[region_id]; float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; // Top surfaces. - append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing)); - append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), min_perimeter_infill_spacing)); + append(cache.top_surfaces, offset(layerm.slices().filter_by_type(stTop), min_perimeter_infill_spacing)); + append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), min_perimeter_infill_spacing)); // Bottom surfaces. - append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); - append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); + append(cache.bottom_surfaces, offset(layerm.slices().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); + append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. // First find the maxium number of perimeters per region slice. unsigned int perimeters = 0; - for (Surface &s : layerm.slices.surfaces) + for (const Surface &s : layerm.slices()) perimeters = std::max(perimeters, s.extra_perimeters); perimeters += layerm.region().config().perimeters.value; // Then calculate the infill offset. @@ -1212,7 +1214,7 @@ void PrintObject::discover_vertical_shells() 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing()); perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing()))); } - polygons_append(cache.holes, to_polygons(layerm.fill_expolygons)); + polygons_append(cache.holes, to_polygons(layerm.fill_expolygons())); } // Save some computing time by reducing the number of polygons. cache.top_surfaces = union_(cache.top_surfaces); @@ -1265,15 +1267,15 @@ void PrintObject::discover_vertical_shells() float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; // Top surfaces. auto &cache = cache_top_botom_regions[idx_layer]; - cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing); - append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), min_perimeter_infill_spacing)); + cache.top_surfaces = offset(layerm.slices().filter_by_type(stTop), min_perimeter_infill_spacing); + append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), min_perimeter_infill_spacing)); // Bottom surfaces. - cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing); - append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); + cache.bottom_surfaces = offset(layerm.slices().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing); + append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); // Holes over all regions. Only collect them once, they are valid for all region_id iterations. if (cache.holes.empty()) { for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) - polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons)); + polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons())); } } }); @@ -1430,14 +1432,14 @@ void PrintObject::discover_vertical_shells() // Trim the shells region by the internal & internal void surfaces. const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid }; - const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 3)); + const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces().filter_by_types(surfaceTypesInternal, 3)); shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes); polygons_append(shell, diff(polygonsInternal, holes)); if (shell.empty()) continue; // Append the internal solids, so they will be merged with the new ones. - polygons_append(shell, to_polygons(layerm->fill_surfaces.filter_by_type(stInternalSolid))); + polygons_append(shell, to_polygons(layerm->fill_surfaces().filter_by_type(stInternalSolid))); // These regions will be filled by a rectilinear full infill. Currently this type of infill // only fills regions, which fit at least a single line. To avoid gaps in the sparse infill, @@ -1484,8 +1486,8 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the internal & internalvoid by the shell. - Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), shell); - Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), shell); + Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces().filter_by_type(stInternal), shell); + Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces().filter_by_type(stInternalVoid), shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -1497,10 +1499,10 @@ void PrintObject::discover_vertical_shells() // Assign resulting internal surfaces to layer. const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge }; - layerm->fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType)); - layerm->fill_surfaces.append(new_internal, stInternal); - layerm->fill_surfaces.append(new_internal_void, stInternalVoid); - layerm->fill_surfaces.append(new_internal_solid, stInternalSolid); + layerm->m_fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType)); + layerm->m_fill_surfaces.append(new_internal, stInternal); + layerm->m_fill_surfaces.append(new_internal_void, stInternalVoid); + layerm->m_fill_surfaces.append(new_internal_solid, stInternalSolid); } // for each layer }); m_print->throw_if_canceled(); @@ -1514,7 +1516,7 @@ void PrintObject::discover_vertical_shells() } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // for each region -} +} // void PrintObject::discover_vertical_shells() // This method applies bridge flow to the first internal solid layer above sparse infill. void PrintObject::bridge_over_infill() @@ -1537,7 +1539,7 @@ void PrintObject::bridge_over_infill() for (Layer *layer : m_layers) { Polygons sum; for (const LayerRegion *layerm : layer->m_regions) - layerm->fill_surfaces.filter_by_type(stInternal, &sum); + layerm->fill_surfaces().filter_by_type(stInternal, &sum); internals.emplace_back(std::move(sum)); } @@ -1554,7 +1556,7 @@ void PrintObject::bridge_over_infill() // Extract the stInternalSolid surfaces that might be transformed into bridges. ExPolygons internal_solid; - layerm->fill_surfaces.remove_type(stInternalSolid, &internal_solid); + layerm->m_fill_surfaces.remove_type(stInternalSolid, &internal_solid); if (internal_solid.empty()) // No internal solid -> no new bridges for this layer region. continue; @@ -1586,7 +1588,7 @@ void PrintObject::bridge_over_infill() if (to_bridge_pp.empty()) { // Restore internal_solid surfaces. for (ExPolygon &ex : internal_solid) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); + layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); continue; } // convert into ExPolygons @@ -1602,9 +1604,9 @@ void PrintObject::bridge_over_infill() to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes); // build the new collection of fill_surfaces for (ExPolygon &ex : to_bridge) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex))); + layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex))); for (ExPolygon &ex : not_to_bridge) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); + layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); /* # exclude infill from the layers below if needed # see discussion at https://github.com/alexrj/Slic3r/issues/240 @@ -1645,7 +1647,7 @@ void PrintObject::bridge_over_infill() m_print->throw_if_canceled(); } }); -} +} // void PrintObject::bridge_over_infill() static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) { @@ -1852,7 +1854,7 @@ void PrintObject::clip_fill_surfaces() // Solid surfaces to be supported. Polygons overhangs; for (const LayerRegion *layerm : layer->m_regions) - for (const Surface &surface : layerm->fill_surfaces.surfaces) { + for (const Surface &surface : layerm->fill_surfaces()) { Polygons polygons = to_polygons(surface.expolygon); if (surface.is_solid()) polygons_append(overhangs, polygons); @@ -1861,7 +1863,7 @@ void PrintObject::clip_fill_surfaces() Polygons lower_layer_fill_surfaces; Polygons lower_layer_internal_surfaces; for (const LayerRegion *layerm : lower_layer->m_regions) - for (const Surface &surface : layerm->fill_surfaces.surfaces) { + for (const Surface &surface : layerm->fill_surfaces()) { Polygons polygons = to_polygons(surface.expolygon); if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) polygons_append(lower_layer_internal_surfaces, polygons); @@ -1896,12 +1898,12 @@ void PrintObject::clip_fill_surfaces() continue; SurfaceType internal_surface_types[] = { stInternal, stInternalVoid }; Polygons internal; - for (Surface &surface : layerm->fill_surfaces.surfaces) + for (Surface &surface : layerm->m_fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) polygons_append(internal, std::move(surface.expolygon)); - layerm->fill_surfaces.remove_types(internal_surface_types, 2); - layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, ApplySafetyOffset::Yes), stInternal); - layerm->fill_surfaces.append(diff_ex (internal, upper_internal, ApplySafetyOffset::Yes), stInternalVoid); + layerm->m_fill_surfaces.remove_types(internal_surface_types, 2); + layerm->m_fill_surfaces.append(intersection_ex(internal, upper_internal, ApplySafetyOffset::Yes), stInternal); + layerm->m_fill_surfaces.append(diff_ex (internal, upper_internal, ApplySafetyOffset::Yes), stInternalVoid); // If there are voids it means that our internal infill is not adjacent to // perimeters. In this case it would be nice to add a loop around infill to // make it more robust and nicer. TODO. @@ -1911,7 +1913,7 @@ void PrintObject::clip_fill_surfaces() } m_print->throw_if_canceled(); } -} +} // void PrintObject::clip_fill_surfaces() void PrintObject::discover_horizontal_shells() { @@ -1927,7 +1929,7 @@ void PrintObject::discover_horizontal_shells() (i % region_config.solid_infill_every_layers) == 0) { // Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge. SurfaceType type = (region_config.fill_density == 100 || region_config.solid_infill_every_layers == 1) ? stInternalSolid : stInternalBridge; - for (Surface &surface : layerm->fill_surfaces.surfaces) + for (Surface &surface : layerm->m_fill_surfaces.surfaces) if (surface.surface_type == stInternal) surface.surface_type = type; } @@ -1958,11 +1960,11 @@ void PrintObject::discover_horizontal_shells() // (not covered by a layer above / below). // This does not contain the areas covered by perimeters! Polygons solid; - for (const Surface &surface : layerm->slices.surfaces) + for (const Surface &surface : layerm->slices()) if (surface.surface_type == type) polygons_append(solid, to_polygons(surface.expolygon)); // Infill areas (slices without the perimeters). - for (const Surface &surface : layerm->fill_surfaces.surfaces) + for (const Surface &surface : layerm->fill_surfaces()) if (surface.surface_type == type) polygons_append(solid, to_polygons(surface.expolygon)); if (solid.empty()) @@ -1995,7 +1997,7 @@ void PrintObject::discover_horizontal_shells() Polygons new_internal_solid; { Polygons internal; - for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces) + for (const Surface &surface : neighbor_layerm->fill_surfaces()) if (surface.surface_type == stInternal || surface.surface_type == stInternalSolid) polygons_append(internal, to_polygons(surface.expolygon)); new_internal_solid = intersection(solid, internal, ApplySafetyOffset::Yes); @@ -2051,7 +2053,7 @@ void PrintObject::discover_horizontal_shells() // additional area in the next shell too // make sure our grown surfaces don't exceed the fill area Polygons internal; - for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces) + for (const Surface &surface : neighbor_layerm->fill_surfaces()) if (surface.is_internal() && !surface.is_bridge()) polygons_append(internal, to_polygons(surface.expolygon)); polygons_append(new_internal_solid, @@ -2070,16 +2072,16 @@ void PrintObject::discover_horizontal_shells() // internal-solid are the union of the existing internal-solid surfaces // and new ones - SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces); + SurfaceCollection backup = std::move(neighbor_layerm->m_fill_surfaces); polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stInternalSolid))); ExPolygons internal_solid = union_ex(new_internal_solid); // assign new internal-solid surfaces to layer - neighbor_layerm->fill_surfaces.set(internal_solid, stInternalSolid); + neighbor_layerm->m_fill_surfaces.set(internal_solid, stInternalSolid); // subtract intersections from layer surfaces to get resulting internal surfaces Polygons polygons_internal = to_polygons(std::move(internal_solid)); ExPolygons internal = diff_ex(backup.filter_by_type(stInternal), polygons_internal, ApplySafetyOffset::Yes); // assign resulting internal surfaces to layer - neighbor_layerm->fill_surfaces.append(internal, stInternal); + neighbor_layerm->m_fill_surfaces.append(internal, stInternal); polygons_append(polygons_internal, to_polygons(std::move(internal))); // assign top and bottom surfaces to layer SurfaceType surface_types_solid[] = { stTop, stBottom, stBottomBridge }; @@ -2087,7 +2089,7 @@ void PrintObject::discover_horizontal_shells() std::vector top_bottom_groups; backup.group(&top_bottom_groups); for (SurfacesPtr &group : top_bottom_groups) - neighbor_layerm->fill_surfaces.append( + neighbor_layerm->m_fill_surfaces.append( diff_ex(group, polygons_internal), // Use an existing surface as a template, it carries the bridge angle etc. *group.front()); @@ -2106,7 +2108,7 @@ void PrintObject::discover_horizontal_shells() } // for each layer } // for each region #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -} +} // void PrintObject::discover_horizontal_shells() // combine fill surfaces across layers to honor the "infill every N layers" option // Idempotence of this method is guaranteed by the fact that we don't remove things from @@ -2164,10 +2166,10 @@ void PrintObject::combine_infill() layerms.emplace_back(m_layers[i]->regions()[region_id]); // We need to perform a multi-layer intersection, so let's split it in pairs. // Initialize the intersection with the candidates of the lowest layer. - ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces.filter_by_type(stInternal)); + ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces().filter_by_type(stInternal)); // Start looping from the second layer and intersect the current intersection with it. for (size_t i = 1; i < layerms.size(); ++ i) - intersection = intersection_ex(layerms[i]->fill_surfaces.filter_by_type(stInternal), intersection); + intersection = intersection_ex(layerms[i]->fill_surfaces().filter_by_type(stInternal), intersection); double area_threshold = layerms.front()->infill_area_threshold(); if (! intersection.empty() && area_threshold > 0.) intersection.erase(std::remove_if(intersection.begin(), intersection.end(), @@ -2196,9 +2198,9 @@ void PrintObject::combine_infill() for (ExPolygon &expoly : intersection) polygons_append(intersection_with_clearance, offset(expoly, clearance_offset)); for (LayerRegion *layerm : layerms) { - Polygons internal = to_polygons(std::move(layerm->fill_surfaces.filter_by_type(stInternal))); - layerm->fill_surfaces.remove_type(stInternal); - layerm->fill_surfaces.append(diff_ex(internal, intersection_with_clearance), stInternal); + Polygons internal = to_polygons(std::move(layerm->fill_surfaces().filter_by_type(stInternal))); + layerm->m_fill_surfaces.remove_type(stInternal); + layerm->m_fill_surfaces.append(diff_ex(internal, intersection_with_clearance), stInternal); if (layerm == layerms.back()) { // Apply surfaces back with adjusted depth to the uppermost layer. Surface templ(stInternal, ExPolygon()); @@ -2206,17 +2208,17 @@ void PrintObject::combine_infill() for (LayerRegion *layerm2 : layerms) templ.thickness += layerm2->layer()->height; templ.thickness_layers = (unsigned short)layerms.size(); - layerm->fill_surfaces.append(intersection, templ); + layerm->m_fill_surfaces.append(intersection, templ); } else { // Save void surfaces. - layerm->fill_surfaces.append( + layerm->m_fill_surfaces.append( intersection_ex(internal, intersection_with_clearance), stInternalVoid); } } } } -} +} // void PrintObject::combine_infill() void PrintObject::_generate_support_material() { diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 4b91714e58..81e21f305d 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -432,12 +432,12 @@ std::string fix_slicing_errors(LayerPtrs &layers, const std::function &t const Surfaces *lower_surfaces = nullptr; for (size_t j = idx_layer + 1; j < layers.size(); ++ j) if (! layers[j]->slicing_errors) { - upper_surfaces = &layers[j]->regions()[region_id]->slices.surfaces; + upper_surfaces = &layers[j]->regions()[region_id]->slices().surfaces; break; } for (int j = int(idx_layer) - 1; j >= 0; -- j) if (! layers[j]->slicing_errors) { - lower_surfaces = &layers[j]->regions()[region_id]->slices.surfaces; + lower_surfaces = &layers[j]->regions()[region_id]->slices().surfaces; break; } // Collect outer contours and holes from the valid layers above & below. @@ -464,7 +464,7 @@ std::string fix_slicing_errors(LayerPtrs &layers, const std::function &t if (lower_surfaces) for (const auto &surface : *lower_surfaces) polygons_append(holes, surface.expolygon.holes); - layerm->slices.set(diff_ex(union_(outer), holes), stInternal); + layerm->m_slices.set(diff_ex(union_(outer), holes), stInternal); } // Update layer slices after repairing the single regions. layer->make_slices(); @@ -517,24 +517,33 @@ void PrintObject::slice() // Update bounding boxes, back up raw slices of complex models. tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this](const tbb::blocked_range& range) { + [this](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); Layer &layer = *m_layers[layer_idx]; - layer.lslices_bboxes.clear(); - layer.lslices_bboxes.reserve(layer.lslices.size()); + layer.lslices_ex.clear(); + layer.lslices_ex.reserve(layer.lslices.size()); for (const ExPolygon &expoly : layer.lslices) - layer.lslices_bboxes.emplace_back(get_extents(expoly)); + layer.lslices_ex.push_back({ get_extents(expoly) }); layer.backup_untyped_slices(); } }); + // Interlink the lslices into a Z graph. + tbb::parallel_for( + tbb::blocked_range(1, m_layers.size()), + [this](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + m_print->throw_if_canceled(); + Layer::build_up_down_graph(*m_layers[layer_idx - 1], *m_layers[layer_idx]); + } + }); if (m_layers.empty()) throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); this->set_done(posSlice); } template -static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) +void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) { // Returns MMU segmentation based on painting in MMU segmentation gizmo std::vector> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel); @@ -579,9 +588,9 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance // layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID. auto it_painted_region = layer_range.painted_regions.begin(); for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id) - if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices.surfaces.empty()) { + if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices().empty()) { assert(layerm.region().print_object_region_id() == region_id); - const BoundingBox bbox = get_extents(layerm.slices.surfaces); + const BoundingBox bbox = get_extents(layerm.slices().surfaces); assert(it_painted_region < layer_range.painted_regions.end()); // Find the first it_painted_region which overrides this region. for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region) @@ -604,7 +613,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance } // Steal from this region. int target_region_id = it_painted_region->region->print_object_region_id(); - ExPolygons stolen = intersection_ex(layerm.slices.surfaces, segmented.expolygons); + ExPolygons stolen = intersection_ex(layerm.slices().surfaces, segmented.expolygons); if (! stolen.empty()) { ByRegion &dst = by_region[target_region_id]; if (dst.expolygons.empty()) { @@ -622,7 +631,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance } if (! self_trimmed) { // Trim slices of this LayerRegion with all the MMU regions. - Polygons mine = to_polygons(std::move(layerm.slices.surfaces)); + Polygons mine = to_polygons(std::move(layerm.slices().surfaces)); for (auto &segmented : by_extruder) if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && bbox.overlap(segmented.bbox)) { mine = diff(mine, segmented.expolygons); @@ -653,7 +662,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance if (src.needs_merge) // Multiple regions were merged into one. src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON))); - layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal); + layer->get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal); } } }); @@ -693,7 +702,7 @@ void PrintObject::slice_volumes() for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) { std::vector &by_layer = region_slices[region_id]; for (size_t layer_id = 0; layer_id < by_layer.size(); ++ layer_id) - m_layers[layer_id]->regions()[region_id]->slices.append(std::move(by_layer[layer_id]), stInternal); + m_layers[layer_id]->regions()[region_id]->m_slices.append(std::move(by_layer[layer_id]), stInternal); } region_slices.clear(); @@ -754,14 +763,14 @@ void PrintObject::slice_volumes() LayerRegion *layerm = layer->m_regions.front(); if (elfoot > 0) { // Apply the elephant foot compensation and store the 1st layer slices without the Elephant foot compensation applied. - lslices_1st_layer = to_expolygons(std::move(layerm->slices.surfaces)); + lslices_1st_layer = to_expolygons(std::move(layerm->m_slices.surfaces)); float delta = xy_compensation_scaled; if (delta > elfoot) { delta -= elfoot; elfoot = 0.f; } else if (delta > 0) elfoot -= delta; - layerm->slices.set( + layerm->m_slices.set( union_ex( Slic3r::elephant_foot_compensation( (delta == 0.f) ? lslices_1st_layer : offset_ex(lslices_1st_layer, delta), @@ -771,8 +780,8 @@ void PrintObject::slice_volumes() lslices_1st_layer = offset_ex(std::move(lslices_1st_layer), xy_compensation_scaled); } else if (xy_compensation_scaled < 0.f) { // Apply the XY compensation. - layerm->slices.set( - offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), xy_compensation_scaled), + layerm->m_slices.set( + offset_ex(to_expolygons(std::move(layerm->m_slices.surfaces)), xy_compensation_scaled), stInternal); } } else { diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 8634fc3bb9..9e21fca454 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -180,7 +180,7 @@ bool DefaultSupportTree::interconnect(const Pillar &pillar, Vec3d eupper = pillar.endpoint(); Vec3d elower = nextpillar.endpoint(); - double zmin = ground_level(this->m_sm) + m_sm.cfg.base_height_mm; + double zmin = ground_level(m_sm) + m_sm.cfg.base_height_mm; eupper.z() = std::max(eupper.z(), zmin); elower.z() = std::max(elower.z(), zmin); diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 5cb7b70690..a9e094386f 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -84,8 +84,7 @@ public: float overhangs_area = 0.f; bool overlaps(const Structure &rhs) const { - //FIXME ExPolygon::overlaps() shall be commutative, it is not! - return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon)); + return this->bbox.overlap(rhs.bbox) && this->polygon->overlaps(*rhs.polygon); } float overlap_area(const Structure &rhs) const { double out = 0.; diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 89d3b85b33..d46094f7fb 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -650,14 +650,14 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t // 1) Count the new polygons first. size_t n_polygons_new = 0; for (const LayerRegion *region : layer.regions()) - for (const Surface &surface : region->slices.surfaces) + for (const Surface &surface : region->slices()) if (surface.surface_type == surface_type) n_polygons_new += surface.expolygon.holes.size() + 1; // 2) Collect the new polygons. Polygons out; out.reserve(n_polygons_new); for (const LayerRegion *region : layer.regions()) - for (const Surface &surface : region->slices.surfaces) + for (const Surface &surface : region->slices()) if (surface.surface_type == surface_type) polygons_append(out, surface.expolygon); return out; @@ -1213,9 +1213,9 @@ namespace SupportMaterialInternal { static bool has_bridging_extrusions(const Layer &layer) { for (const LayerRegion *region : layer.regions()) { - if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters)) + if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters())) return true; - if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills)) + if (region->fill_surfaces().has(stBottomBridge) && has_bridging_fills(region->fills())) return true; } return false; @@ -1287,7 +1287,7 @@ namespace SupportMaterialInternal { // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); #else - Polylines overhang_perimeters = diff_pl(layerm.perimeters.as_polylines(), lower_grown_slices); + Polylines overhang_perimeters = diff_pl(layerm.perimeters().as_polylines(), lower_grown_slices); #endif // only consider straight overhangs @@ -1311,7 +1311,7 @@ namespace SupportMaterialInternal { bool supported[2] = { false, false }; for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i) for (int j = 0; j < 2; ++ j) - if (! supported[j] && lower_layer.lslices_bboxes[i].contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) + if (! supported[j] && lower_layer.lslices_ex[i].bbox.contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) supported[j] = true; if (supported[0] && supported[1]) // Offset a polyline into a thick line. @@ -1321,7 +1321,7 @@ namespace SupportMaterialInternal { } // remove the entire bridges and only support the unsupported edges //FIXME the brided regions are already collected as layerm.bridged. Use it? - for (const Surface &surface : layerm.fill_surfaces.surfaces) + for (const Surface &surface : layerm.fill_surfaces()) if (surface.surface_type == stBottomBridge && surface.bridge_angle < 0.0) polygons_append(bridges, surface.expolygon); //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? @@ -1329,14 +1329,14 @@ namespace SupportMaterialInternal { //FIXME add supports at regular intervals to support long bridges! bridges = diff(bridges, // Offset unsupported edges into polygons. - offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Remove bridged areas from the supported areas. contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); #ifdef SLIC3R_DEBUG static int iRun = 0; SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), - { { { union_ex(offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, + { { { union_ex(offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ @@ -1487,7 +1487,7 @@ static inline std::tuple detect_overhangs( 0.5f * fw); // Overhang polygons for this layer and region. Polygons diff_polygons; - Polygons layerm_polygons = to_polygons(layerm->slices.surfaces); + Polygons layerm_polygons = to_polygons(layerm->slices().surfaces); if (lower_layer_offset == 0.f) { // Support everything. diff_polygons = diff(layerm_polygons, lower_layer_polygons); @@ -2858,10 +2858,10 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( break; some_region_overlaps = true; polygons_append(polygons_trimming, - offset(region->fill_surfaces.filter_by_type(stBottomBridge), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(region->fill_surfaces().filter_by_type(stBottomBridge), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (region->region().config().overhangs.value) // Add bridging perimeters. - SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); + SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters(), gap_xy_scaled, polygons_trimming); } if (! some_region_overlaps) break; diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 5beb770f29..66c6de7a9d 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -6,6 +6,8 @@ #include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" +#include "Print.hpp" +#include "Tesselate.hpp" #include "libslic3r.h" #include "tbb/parallel_for.h" #include "tbb/blocked_range.h" @@ -18,6 +20,7 @@ #include #include #include +#include #include "AABBTreeLines.hpp" #include "KDTreeIndirect.hpp" @@ -26,7 +29,7 @@ #include "Geometry/ConvexHull.hpp" // #define DETAILED_DEBUG_LOGS -//#define DEBUG_FILES +// #define DEBUG_FILES #ifdef DEBUG_FILES #include @@ -38,164 +41,74 @@ namespace Slic3r { class ExtrusionLine { public: - ExtrusionLine() : - a(Vec2f::Zero()), b(Vec2f::Zero()), len(0.0f), origin_entity(nullptr) { - } - ExtrusionLine(const Vec2f &a, const Vec2f &b, const ExtrusionEntity *origin_entity) : - a(a), b(b), len((a - b).norm()), origin_entity(origin_entity) { - } + ExtrusionLine() : a(Vec2f::Zero()), b(Vec2f::Zero()), len(0.0f), origin_entity(nullptr) {} + ExtrusionLine(const Vec2f &a, const Vec2f &b, const ExtrusionEntity *origin_entity) + : a(a), b(b), len((a - b).norm()), origin_entity(origin_entity) + {} - float length() { - return (a - b).norm(); - } + float length() { return (a - b).norm(); } - bool is_external_perimeter() const { + bool is_external_perimeter() const + { assert(origin_entity != nullptr); return origin_entity->role() == erExternalPerimeter || origin_entity->role() == erOverhangPerimeter; } - Vec2f a; - Vec2f b; - float len; + Vec2f a; + Vec2f b; + float len; const ExtrusionEntity *origin_entity; - bool support_point_generated = false; - float malformation = 0.0f; + bool support_point_generated = false; + float malformation = 0.0f; static const constexpr int Dim = 2; - using Scalar = Vec2f::Scalar; + using Scalar = Vec2f::Scalar; }; -auto get_a(ExtrusionLine &&l) { - return l.a; -} -auto get_b(ExtrusionLine &&l) { - return l.b; -} +auto get_a(ExtrusionLine &&l) { return l.a; } +auto get_b(ExtrusionLine &&l) { return l.b; } namespace SupportSpotsGenerator { -SupportPoint::SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec3f &direction) : - position(position), force(force), spot_radius(spot_radius), direction(direction) { -} - -static const size_t NULL_ISLAND = std::numeric_limits::max(); +SupportPoint::SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction) + : position(position), force(force), spot_radius(spot_radius), direction(direction) +{} using LD = AABBTreeLines::LinesDistancer; -class PixelGrid { - Vec2f pixel_size; - Vec2f origin; - Vec2f size; - Vec2i pixel_count; - - std::vector pixels { }; - -public: - PixelGrid(const PrintObject *po, float resolution) { - pixel_size = Vec2f(resolution, resolution); - - Vec2crd size_half = po->size().head<2>().cwiseQuotient(Vec2crd(2, 2)) + Vec2crd::Ones(); - Vec2f min = unscale(Vec2crd(-size_half.x(), -size_half.y())).cast(); - Vec2f max = unscale(Vec2crd(size_half.x(), size_half.y())).cast(); - - origin = min; - size = max - min; - pixel_count = size.cwiseQuotient(pixel_size).cast() + Vec2i::Ones(); - - pixels.resize(pixel_count.y() * pixel_count.x()); - clear(); - } - - void distribute_edge(const Vec2f &p1, const Vec2f &p2, size_t value) { - Vec2f dir = (p2 - p1); - float length = dir.norm(); - if (length < 0.1) { - return; - } - float step_size = this->pixel_size.x() / 2.0; - - float distributed_length = 0; - while (distributed_length < length) { - float next_len = std::min(length, distributed_length + step_size); - Vec2f location = p1 + ((next_len / length) * dir); - this->access_pixel(location) = value; - - distributed_length = next_len; - } - } - - void clear() { - for (size_t &val : pixels) { - val = NULL_ISLAND; - } - } - - float pixel_area() const { - return this->pixel_size.x() * this->pixel_size.y(); - } - - size_t get_pixel(const Vec2i &coords) const { - return pixels[this->to_pixel_index(coords)]; - } - - Vec2i get_pixel_count() { - return pixel_count; - } - - Vec2f get_pixel_center(const Vec2i &coords) const { - return origin + coords.cast().cwiseProduct(this->pixel_size) - + this->pixel_size.cwiseQuotient(Vec2f(2.0f, 2.0f)); - } - -private: - Vec2i to_pixel_coords(const Vec2f &position) const { - Vec2i pixel_coords = (position - this->origin).cwiseQuotient(this->pixel_size).cast(); - return pixel_coords; - } - - size_t to_pixel_index(const Vec2i &pixel_coords) const { - assert(pixel_coords.x() >= 0); - assert(pixel_coords.x() < pixel_count.x()); - assert(pixel_coords.y() >= 0); - assert(pixel_coords.y() < pixel_count.y()); - - return pixel_coords.y() * pixel_count.x() + pixel_coords.x(); - } - - size_t& access_pixel(const Vec2f &position) { - return pixels[this->to_pixel_index(this->to_pixel_coords(position))]; - } -}; - -struct SupportGridFilter { +struct SupportGridFilter +{ private: Vec3f cell_size; Vec3f origin; Vec3f size; Vec3i cell_count; - std::unordered_set taken_cells { }; + std::unordered_set taken_cells{}; public: - SupportGridFilter(const PrintObject *po, float voxel_size) { + SupportGridFilter(const PrintObject *po, float voxel_size) + { cell_size = Vec3f(voxel_size, voxel_size, voxel_size); Vec2crd size_half = po->size().head<2>().cwiseQuotient(Vec2crd(2, 2)) + Vec2crd::Ones(); - Vec3f min = unscale(Vec3crd(-size_half.x(), -size_half.y(), 0)).cast() - cell_size; - Vec3f max = unscale(Vec3crd(size_half.x(), size_half.y(), po->height())).cast() + cell_size; + Vec3f min = unscale(Vec3crd(-size_half.x(), -size_half.y(), 0)).cast() - cell_size; + Vec3f max = unscale(Vec3crd(size_half.x(), size_half.y(), po->height())).cast() + cell_size; - origin = min; - size = max - min; + origin = min; + size = max - min; cell_count = size.cwiseQuotient(cell_size).cast() + Vec3i::Ones(); } - Vec3i to_cell_coords(const Vec3f &position) const { + Vec3i to_cell_coords(const Vec3f &position) const + { Vec3i cell_coords = (position - this->origin).cwiseQuotient(this->cell_size).cast(); return cell_coords; } - size_t to_cell_index(const Vec3i &cell_coords) const { + size_t to_cell_index(const Vec3i &cell_coords) const + { assert(cell_coords.x() >= 0); assert(cell_coords.x() < cell_count.x()); assert(cell_coords.y() >= 0); @@ -203,43 +116,41 @@ public: assert(cell_coords.z() >= 0); assert(cell_coords.z() < cell_count.z()); - return cell_coords.z() * cell_count.x() * cell_count.y() - + cell_coords.y() * cell_count.x() - + cell_coords.x(); + return cell_coords.z() * cell_count.x() * cell_count.y() + cell_coords.y() * cell_count.x() + cell_coords.x(); } - Vec3f get_cell_center(const Vec3i &cell_coords) const { - return origin + cell_coords.cast().cwiseProduct(this->cell_size) - + this->cell_size.cwiseQuotient(Vec3f(2.0f, 2.0f, 2.0)); + Vec3f get_cell_center(const Vec3i &cell_coords) const + { + return origin + cell_coords.cast().cwiseProduct(this->cell_size) + this->cell_size.cwiseQuotient(Vec3f(2.0f, 2.0f, 2.0)); } - void take_position(const Vec3f &position) { - taken_cells.insert(to_cell_index(to_cell_coords(position))); - } + void take_position(const Vec3f &position) { taken_cells.insert(to_cell_index(to_cell_coords(position))); } - bool position_taken(const Vec3f &position) const { + bool position_taken(const Vec3f &position) const + { return taken_cells.find(to_cell_index(to_cell_coords(position))) != taken_cells.end(); } - }; -struct IslandConnection { - float area { }; - Vec3f centroid_accumulator = Vec3f::Zero(); +struct SliceConnection +{ + float area{}; + Vec3f centroid_accumulator = Vec3f::Zero(); Vec2f second_moment_of_area_accumulator = Vec2f::Zero(); - float second_moment_of_area_covariance_accumulator { }; + float second_moment_of_area_covariance_accumulator{}; - void add(const IslandConnection &other) { + void add(const SliceConnection &other) + { this->area += other.area; this->centroid_accumulator += other.centroid_accumulator; this->second_moment_of_area_accumulator += other.second_moment_of_area_accumulator; this->second_moment_of_area_covariance_accumulator += other.second_moment_of_area_covariance_accumulator; } - void print_info(const std::string &tag) { - Vec3f centroid = centroid_accumulator / area; - Vec2f variance = - (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); + void print_info(const std::string &tag) + { + Vec3f centroid = centroid_accumulator / area; + Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); std::cout << tag << std::endl; std::cout << "area: " << area << std::endl; @@ -249,93 +160,59 @@ struct IslandConnection { } }; -struct Island { - std::unordered_map connected_islands { }; - float volume { }; - Vec3f volume_centroid_accumulator = Vec3f::Zero(); - float sticking_area { }; // for support points present on this layer (or bed extrusions) - Vec3f sticking_centroid_accumulator = Vec3f::Zero(); - Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero(); - float sticking_second_moment_of_area_covariance_accumulator { }; - - std::vector external_lines; -}; - -struct LayerIslands { - std::vector islands; - float layer_z; -}; - -float get_flow_width(const LayerRegion *region, ExtrusionRole role) { +float get_flow_width(const LayerRegion *region, ExtrusionRole role) +{ switch (role) { - case ExtrusionRole::erBridgeInfill: - return region->flow(FlowRole::frExternalPerimeter).width(); - case ExtrusionRole::erExternalPerimeter: - return region->flow(FlowRole::frExternalPerimeter).width(); - case ExtrusionRole::erGapFill: - return region->flow(FlowRole::frInfill).width(); - case ExtrusionRole::erPerimeter: - return region->flow(FlowRole::frPerimeter).width(); - case ExtrusionRole::erSolidInfill: - return region->flow(FlowRole::frSolidInfill).width(); - case ExtrusionRole::erInternalInfill: - return region->flow(FlowRole::frInfill).width(); - case ExtrusionRole::erTopSolidInfill: - return region->flow(FlowRole::frTopSolidInfill).width(); - default: - return region->flow(FlowRole::frPerimeter).width(); + case ExtrusionRole::erBridgeInfill: return region->flow(FlowRole::frExternalPerimeter).width(); + case ExtrusionRole::erExternalPerimeter: return region->flow(FlowRole::frExternalPerimeter).width(); + case ExtrusionRole::erGapFill: return region->flow(FlowRole::frInfill).width(); + case ExtrusionRole::erPerimeter: return region->flow(FlowRole::frPerimeter).width(); + case ExtrusionRole::erSolidInfill: return region->flow(FlowRole::frSolidInfill).width(); + case ExtrusionRole::erInternalInfill: return region->flow(FlowRole::frInfill).width(); + case ExtrusionRole::erTopSolidInfill: return region->flow(FlowRole::frTopSolidInfill).width(); + default: return region->flow(FlowRole::frPerimeter).width(); } } // Accumulator of current extrusion path properties // It remembers unsuported distance and maximum accumulated curvature over that distance. // Used to determine local stability issues (too long bridges, extrusion curves into air) -struct ExtrusionPropertiesAccumulator { - float distance = 0; //accumulated distance - float curvature = 0; //accumulated signed ccw angles - float max_curvature = 0; //max absolute accumulated value +struct ExtrusionPropertiesAccumulator +{ + float distance = 0; // accumulated distance + float curvature = 0; // accumulated signed ccw angles + float max_curvature = 0; // max absolute accumulated value - void add_distance(float dist) { - distance += dist; - } + void add_distance(float dist) { distance += dist; } - void add_angle(float ccw_angle) { + void add_angle(float ccw_angle) + { curvature += ccw_angle; max_curvature = std::max(max_curvature, std::abs(curvature)); } - void reset() { - distance = 0; - curvature = 0; + void reset() + { + distance = 0; + curvature = 0; max_curvature = 0; } }; // base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) // checkout e.g. here: https://www.geogebra.org/calculator -float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { - float shifted = value - mean_x_coord; - float denominator = falloff_speed * shifted * shifted + 1.0f; - float exponent = 1.0f / denominator; - return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); -} - -void push_lines(const ExtrusionEntity *e, std::vector& destination) +float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { - assert(!e->is_collection()); - Polyline pl = e->as_polyline(); - for (int point_idx = 0; point_idx < int(pl.points.size() - 1); ++point_idx) { - Vec2f start = unscaled(pl.points[point_idx]).cast(); - Vec2f next = unscaled(pl.points[point_idx + 1]).cast(); - ExtrusionLine line{start, next, e}; - destination.push_back(line); - } + float shifted = value - mean_x_coord; + float denominator = falloff_speed * shifted * shifted + 1.0f; + float exponent = 1.0f / denominator; + return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); } std::vector to_short_lines(const ExtrusionEntity *e, float length_limit) { assert(!e->is_collection()); - Polyline pl = e->as_polyline(); + Polyline pl = e->as_polyline(); std::vector lines; lines.reserve(pl.points.size() * 1.5f); lines.emplace_back(unscaled(pl.points[0]).cast(), unscaled(pl.points[0]).cast(), e); @@ -356,33 +233,30 @@ std::vector to_short_lines(const ExtrusionEntity *e, float length return lines; } -void check_extrusion_entity_stability(const ExtrusionEntity *entity, - std::vector &checked_lines_out, - float layer_z, - const LayerRegion *layer_region, - const LD &prev_layer_lines, - Issues &issues, - const Params ¶ms) { - +std::vector check_extrusion_entity_stability(const ExtrusionEntity *entity, + const LayerRegion *layer_region, + const LD &prev_layer_lines, + const Params ¶ms) +{ if (entity->is_collection()) { - for (const auto *e : static_cast(entity)->entities) { - check_extrusion_entity_stability(e, checked_lines_out, layer_z, layer_region, prev_layer_lines, - issues, params); + std::vector checked_lines_out; + checked_lines_out.reserve(prev_layer_lines.get_lines().size() / 3); + for (const auto *e : static_cast(entity)->entities) { + auto tmp = check_extrusion_entity_stability(e, layer_region, prev_layer_lines, params); + checked_lines_out.insert(checked_lines_out.end(), tmp.begin(), tmp.end()); } - } else { //single extrusion path, with possible varying parameters - const auto to_vec3f = [layer_z](const Vec2f &point) { - return Vec3f(point.x(), point.y(), layer_z); - }; + return checked_lines_out; + } else { // single extrusion path, with possible varying parameters + if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) { return {}; } + std::vector lines = to_short_lines(entity, params.bridge_distance); - if (lines.empty()) return; - ExtrusionPropertiesAccumulator bridging_acc { }; - ExtrusionPropertiesAccumulator malformation_acc { }; + ExtrusionPropertiesAccumulator bridging_acc{}; + ExtrusionPropertiesAccumulator malformation_acc{}; bridging_acc.add_distance(params.bridge_distance + 1.0f); - const float flow_width = get_flow_width(layer_region, entity->role()); - float min_malformation_dist = flow_width - params.malformation_overlap_factor.first * flow_width; - float max_malformation_dist = flow_width - params.malformation_overlap_factor.second * flow_width; - + const float flow_width = get_flow_width(layer_region, entity->role()); + float min_malformation_dist = flow_width - params.malformation_overlap_factor.first * flow_width; + float max_malformation_dist = flow_width - params.malformation_overlap_factor.second * flow_width; for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { ExtrusionLine ¤t_line = lines[line_idx]; @@ -393,443 +267,304 @@ void check_extrusion_entity_stability(const ExtrusionEntity *entity, if (line_idx + 1 < lines.size()) { const Vec2f v1 = current_line.b - current_line.a; const Vec2f v2 = lines[line_idx + 1].b - lines[line_idx + 1].a; - curr_angle = angle(v1, v2); + curr_angle = angle(v1, v2); } bridging_acc.add_angle(curr_angle); // malformation in concave angles does not happen malformation_acc.add_angle(std::max(0.0f, curr_angle)); - if (curr_angle < -20.0 * PI / 180.0) { - malformation_acc.reset(); - } + if (curr_angle < -20.0 * PI / 180.0) { malformation_acc.reset(); } auto [dist_from_prev_layer, nearest_line_idx, nearest_point] = prev_layer_lines.signed_distance_from_lines_extra(current_line.b); - if (fabs(dist_from_prev_layer) < flow_width) { + if (dist_from_prev_layer < flow_width) { bridging_acc.reset(); } else { bridging_acc.add_distance(current_line.len); // if unsupported distance is larger than bridge distance linearly decreased by curvature, enforce supports. - bool in_layer_dist_condition = bridging_acc.distance - > params.bridge_distance / (1.0f + (bridging_acc.max_curvature - * params.bridge_distance_decrease_by_curvature_factor / PI)); - bool between_layers_condition = fabs(dist_from_prev_layer) > flow_width || - prev_layer_lines.get_line(nearest_line_idx).malformation > 3.0f * layer_region->layer()->height; - + bool in_layer_dist_condition = bridging_acc.distance > + params.bridge_distance / (1.0f + (bridging_acc.max_curvature * + params.bridge_distance_decrease_by_curvature_factor / PI)); + bool between_layers_condition = dist_from_prev_layer > max_malformation_dist; if (in_layer_dist_condition && between_layers_condition) { - issues.support_points.emplace_back(to_vec3f(current_line.b), 0.0f, params.support_points_interface_radius, Vec3f(0.f, 0.0f, -1.0f)); current_line.support_point_generated = true; bridging_acc.reset(); } } - //malformation + // malformation propagation from below if (fabs(dist_from_prev_layer) < 2.0f * flow_width) { const ExtrusionLine &nearest_line = prev_layer_lines.get_line(nearest_line_idx); current_line.malformation += 0.85 * nearest_line.malformation; } + // current line maformation if (dist_from_prev_layer > min_malformation_dist && dist_from_prev_layer < max_malformation_dist) { float factor = std::abs(dist_from_prev_layer - (max_malformation_dist + min_malformation_dist) * 0.5) / (max_malformation_dist - min_malformation_dist); malformation_acc.add_distance(current_line.len); current_line.malformation += layer_region->layer()->height * factor * (2.0f + 3.0f * (malformation_acc.max_curvature / PI)); - current_line.malformation = std::min(current_line.malformation, float(layer_region->layer()->height * params.max_malformation_factor)); + current_line.malformation = std::min(current_line.malformation, + float(layer_region->layer()->height * params.max_malformation_factor)); } else { malformation_acc.reset(); } } - checked_lines_out.insert(checked_lines_out.end(), lines.begin(), lines.end()); + return lines; } } -std::tuple reckon_islands( - const Layer *layer, bool first_layer, - size_t prev_layer_islands_count, - const PixelGrid &prev_layer_grid, - const std::vector &layer_lines, - const Params ¶ms) { +// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance +// none of the values is divided/normalized by area. +// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous) +// f(x,y) = x^2 for second moment of area +// and f(x,y) = x*y for second moment of area covariance +std::tuple compute_triangle_moments_of_area(const Vec2f &a, const Vec2f &b, const Vec2f &c) +{ + // based on the following guide: + // Denote the vertices of S by a, b, c. Then the map + // g:(u,v)↦a+u(b−a)+v(c−a) , + // which in coordinates appears as + // g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1−a1)+v(c1−a1)=a2+u(b2−a2)+v(c2−a2) ,(1) + // obviously maps S′ bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain + // ∫Sf(x,y)d(x,y)=∫S′f(x(u,v),y(u,v))∣∣Jg(u,v)∣∣ d(u,v) . + // In the case at hand the Jacobian determinant is a constant: From (1) we obtain + // Jg(u,v)=det[xuyuxvyv]=(b1−a1)(c2−a2)−(c1−a1)(b2−a2) . + // Therefore we can write + // ∫Sf(x,y)d(x,y)=∣∣Jg∣∣∫10∫1−u0f~(u,v) dv du , + // where f~ denotes the pullback of f to S′: + // f~(u,v):=f(x(u,v),y(u,v)) . + // Don't forget taking the absolute value of Jg! - //extract extrusions (connected paths from multiple lines) from the layer_lines. Grouping by the same polyline is determined by common origin_entity ptr. - // result is a vector of [start, end) index pairs into the layer_lines vector - std::vector> extrusions; //start and end idx (one beyond last extrusion) [start,end) - const ExtrusionEntity *current_ex = nullptr; - for (size_t lidx = 0; lidx < layer_lines.size(); ++lidx) { - const ExtrusionLine &line = layer_lines[lidx]; - if (line.origin_entity == current_ex) { - extrusions.back().second = lidx + 1; - } else { - extrusions.emplace_back(lidx, lidx + 1); - current_ex = line.origin_entity; - } - } + float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y())); - std::vector> islands; // these search trees will be used to determine to which island does the extrusion belong. - for (const ExPolygon& island : layer->lslices) { - islands.emplace_back(to_lines(island)); - } + // coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x) + // coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y) + // second moment of area for x: f(x, y) = x^2; + // f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form) - std::vector> island_extrusions(islands.size(), - std::vector{}); // final assigment of each extrusion to an island. - for (size_t extrusion_idx = 0; extrusion_idx < extrusions.size(); extrusion_idx++) { - Point second_point = Point::new_scale(layer_lines[extrusions[extrusion_idx].first].b); - for (size_t island_idx = 0; island_idx < islands.size(); island_idx++) { - if (islands[island_idx].signed_distance_from_lines(second_point) <= 0.0) { - island_extrusions[island_idx].push_back(extrusion_idx); - } - } - } + // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du + // integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2) - float flow_width = get_flow_width(layer->regions()[0], erExternalPerimeter); - // after filtering the layer lines into islands, build the result LayerIslands structure. - LayerIslands result { }; - result.layer_z = layer->slice_z; - std::vector line_to_island_mapping(layer_lines.size(), NULL_ISLAND); - for (const std::vector &island_ex : island_extrusions) { - if (island_ex.empty()) { - continue; - } + Vec2f second_moment_of_area_xy = jacobian_determinant_abs * + (a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) + + a.cwiseProduct(b + c)) / + 12.0f; + // second moment of area covariance : f(x, y) = x*y; + // f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form) + //(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2)) + // == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) - Island island { }; - for (size_t extrusion_idx : island_ex) { - - if (layer_lines[extrusions[extrusion_idx].first].is_external_perimeter()) { - island.external_lines.insert(island.external_lines.end(), - layer_lines.begin() + extrusions[extrusion_idx].first, - layer_lines.begin() + extrusions[extrusion_idx].second); - } + // intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv = + // 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 + // b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - + // 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du = + // 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2) + // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du + float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) * + (a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() + + b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y()); - for (size_t lidx = extrusions[extrusion_idx].first; lidx < extrusions[extrusion_idx].second; ++lidx) { - line_to_island_mapping[lidx] = result.islands.size(); - const ExtrusionLine &line = layer_lines[lidx]; - float volume = line.len * layer->height * flow_width * PI / 4.0f; - island.volume += volume; - island.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), float(layer->slice_z)) - * volume; + float area = jacobian_determinant_abs * 0.5f; - if (first_layer) { - float sticking_area = line.len * flow_width; - island.sticking_area += sticking_area; - Vec2f middle = Vec2f((line.a + line.b) / 2.0f); - island.sticking_centroid_accumulator += sticking_area * to_3d(middle, float(layer->slice_z)); - // Bottom infill lines can be quite long, and algined, so the middle approximaton used above does not work - Vec2f dir = (line.b - line.a).normalized(); - float segment_length = flow_width; // segments of size flow_width - for (float segment_middle_dist = std::min(line.len, segment_length * 0.5f); - segment_middle_dist < line.len; - segment_middle_dist += segment_length) { - Vec2f segment_middle = line.a + segment_middle_dist * dir; - island.sticking_second_moment_of_area_accumulator += segment_length * flow_width - * segment_middle.cwiseProduct(segment_middle); - island.sticking_second_moment_of_area_covariance_accumulator += segment_length * flow_width - * segment_middle.x() - * segment_middle.y(); - } - } else if (layer_lines[lidx].support_point_generated) { - float sticking_area = line.len * flow_width; - island.sticking_area += sticking_area; - island.sticking_centroid_accumulator += sticking_area * to_3d(line.b, float(layer->slice_z)); - island.sticking_second_moment_of_area_accumulator += sticking_area * line.b.cwiseProduct(line.b); - island.sticking_second_moment_of_area_covariance_accumulator += sticking_area * line.b.x() - * line.b.y(); - } - } - } - result.islands.push_back(island); - } + Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f; - //LayerIslands structure built. Now determine connections and their areas to the previous layer using rasterization. - PixelGrid current_layer_grid = prev_layer_grid; - current_layer_grid.clear(); - // build index image of current layer - tbb::parallel_for(tbb::blocked_range(0, layer_lines.size()), - [&layer_lines, ¤t_layer_grid, &line_to_island_mapping]( - tbb::blocked_range r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - size_t island = line_to_island_mapping[i]; - const ExtrusionLine &line = layer_lines[i]; - current_layer_grid.distribute_edge(line.a, line.b, island); - } - }); - - //compare the image of previous layer with the current layer. For each pair of overlapping valid pixels, add pixel area to the respective island connection - for (size_t x = 0; x < size_t(current_layer_grid.get_pixel_count().x()); ++x) { - for (size_t y = 0; y < size_t(current_layer_grid.get_pixel_count().y()); ++y) { - Vec2i coords = Vec2i(x, y); - if (current_layer_grid.get_pixel(coords) != NULL_ISLAND - && prev_layer_grid.get_pixel(coords) != NULL_ISLAND) { - IslandConnection &connection = result.islands[current_layer_grid.get_pixel(coords)] - .connected_islands[prev_layer_grid.get_pixel(coords)]; - Vec2f current_coords = current_layer_grid.get_pixel_center(coords); - connection.area += current_layer_grid.pixel_area(); - connection.centroid_accumulator += to_3d(current_coords, result.layer_z) - * current_layer_grid.pixel_area(); - connection.second_moment_of_area_accumulator += current_coords.cwiseProduct(current_coords) - * current_layer_grid.pixel_area(); - connection.second_moment_of_area_covariance_accumulator += current_coords.x() * current_coords.y() - * current_layer_grid.pixel_area(); - } - } - } - - // filter out very small connection areas, they brake the graph building - for (Island &island : result.islands) { - std::vector conns_to_remove; - for (const auto &conn : island.connected_islands) { - if (conn.second.area < params.connections_min_considerable_area) { conns_to_remove.push_back(conn.first); } - } - for (size_t conn : conns_to_remove) { island.connected_islands.erase(conn); } - } - - return {result, current_layer_grid}; -} - -struct CoordinateFunctor { - const std::vector *coordinates; - CoordinateFunctor(const std::vector *coords) : - coordinates(coords) { - } - CoordinateFunctor() : - coordinates(nullptr) { - } - - const float& operator()(size_t idx, size_t dim) const { - return coordinates->operator [](idx)[dim]; - } + return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance}; }; -class ObjectPart { - float volume { }; - Vec3f volume_centroid_accumulator = Vec3f::Zero(); - float sticking_area { }; - Vec3f sticking_centroid_accumulator = Vec3f::Zero(); - Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero(); - float sticking_second_moment_of_area_covariance_accumulator { }; +SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer) +{ + SliceConnection connection; + const LayerSlice &slice = layer->lslices_ex[slice_idx]; + ExPolygon slice_poly = layer->lslices[slice_idx]; + const Layer *lower_layer = layer->lower_layer; + + ExPolygons below_polys{}; + for (const auto &link : slice.overlaps_below) { below_polys.push_back(lower_layer->lslices[link.slice_idx]); } + ExPolygons overlap = intersection_ex({slice_poly}, below_polys); + + std::vector triangles = triangulate_expolygons_2f(overlap); + for (size_t idx = 0; idx < triangles.size(); idx += 3) { + auto [area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_triangle_moments_of_area(triangles[idx], triangles[idx + 1], triangles[idx + 2]); + connection.area += area; + connection.centroid_accumulator += Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area); + connection.second_moment_of_area_accumulator += second_moment_area; + connection.second_moment_of_area_covariance_accumulator += second_moment_of_area_covariance; + } + + return connection; +}; + +class ObjectPart +{ public: + float volume{}; + Vec3f volume_centroid_accumulator = Vec3f::Zero(); + float sticking_area{}; + Vec3f sticking_centroid_accumulator = Vec3f::Zero(); + Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero(); + float sticking_second_moment_of_area_covariance_accumulator{}; + ObjectPart() = default; - ObjectPart(const Island &island) { - this->volume = island.volume; - this->volume_centroid_accumulator = island.volume_centroid_accumulator; - this->sticking_area = island.sticking_area; - this->sticking_centroid_accumulator = island.sticking_centroid_accumulator; - this->sticking_second_moment_of_area_accumulator = island.sticking_second_moment_of_area_accumulator; - this->sticking_second_moment_of_area_covariance_accumulator = - island.sticking_second_moment_of_area_covariance_accumulator; - } - - float get_volume() const { - return volume; - } - - void add(const ObjectPart &other) { + void add(const ObjectPart &other) + { this->volume_centroid_accumulator += other.volume_centroid_accumulator; this->volume += other.volume; this->sticking_area += other.sticking_area; this->sticking_centroid_accumulator += other.sticking_centroid_accumulator; this->sticking_second_moment_of_area_accumulator += other.sticking_second_moment_of_area_accumulator; - this->sticking_second_moment_of_area_covariance_accumulator += - other.sticking_second_moment_of_area_covariance_accumulator; + this->sticking_second_moment_of_area_covariance_accumulator += other.sticking_second_moment_of_area_covariance_accumulator; } - void add_support_point(const Vec3f &position, float sticking_area) { + void add_support_point(const Vec3f &position, float sticking_area) + { this->sticking_area += sticking_area; this->sticking_centroid_accumulator += sticking_area * position; - this->sticking_second_moment_of_area_accumulator += sticking_area - * position.head<2>().cwiseProduct(position.head<2>()); - this->sticking_second_moment_of_area_covariance_accumulator += sticking_area - * position.x() * position.y(); + this->sticking_second_moment_of_area_accumulator += sticking_area * position.head<2>().cwiseProduct(position.head<2>()); + this->sticking_second_moment_of_area_covariance_accumulator += sticking_area * position.x() * position.y(); } - float compute_directional_xy_variance( - const Vec2f &line_dir, - const Vec3f ¢roid_accumulator, - const Vec2f &second_moment_of_area_accumulator, - const float &second_moment_of_area_covariance_accumulator, - const float &area) const { + float compute_directional_xy_variance(const Vec2f &line_dir, + const Vec3f ¢roid_accumulator, + const Vec2f &second_moment_of_area_accumulator, + const float &second_moment_of_area_covariance_accumulator, + const float &area) const + { assert(area > 0); - Vec3f centroid = centroid_accumulator / area; - Vec2f variance = (second_moment_of_area_accumulator / area - - centroid.head<2>().cwiseProduct(centroid.head<2>())); + Vec3f centroid = centroid_accumulator / area; + Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); // Var(aX+bY)=a^2*Var(X)+b^2*Var(Y)+2*a*b*Cov(X,Y) - float directional_xy_variance = line_dir.x() * line_dir.x() * variance.x() - + line_dir.y() * line_dir.y() * variance.y() + - 2.0f * line_dir.x() * line_dir.y() * covariance; + float directional_xy_variance = line_dir.x() * line_dir.x() * variance.x() + line_dir.y() * line_dir.y() * variance.y() + + 2.0f * line_dir.x() * line_dir.y() * covariance; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) - << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z(); - BOOST_LOG_TRIVIAL(debug) - << "variance: " << variance.x() << " " << variance.y(); - BOOST_LOG_TRIVIAL(debug) - << "covariance: " << covariance; - BOOST_LOG_TRIVIAL(debug) - << "directional_xy_variance: " << directional_xy_variance; + BOOST_LOG_TRIVIAL(debug) << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "variance: " << variance.x() << " " << variance.y(); + BOOST_LOG_TRIVIAL(debug) << "covariance: " << covariance; + BOOST_LOG_TRIVIAL(debug) << "directional_xy_variance: " << directional_xy_variance; #endif return directional_xy_variance; } - float compute_elastic_section_modulus( - const Vec2f &line_dir, - const Vec3f &extreme_point, - const Vec3f ¢roid_accumulator, - const Vec2f &second_moment_of_area_accumulator, - const float &second_moment_of_area_covariance_accumulator, - const float &area) const { - - float directional_xy_variance = compute_directional_xy_variance( - line_dir, - centroid_accumulator, - second_moment_of_area_accumulator, - second_moment_of_area_covariance_accumulator, - area); - if (directional_xy_variance < EPSILON) { - return 0.0f; - } - Vec3f centroid = centroid_accumulator / area; - float extreme_fiber_dist = line_alg::distance_to( - Linef(centroid.head<2>().cast(), - (centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast()), - extreme_point.head<2>().cast()); + float compute_elastic_section_modulus(const Vec2f &line_dir, + const Vec3f &extreme_point, + const Vec3f ¢roid_accumulator, + const Vec2f &second_moment_of_area_accumulator, + const float &second_moment_of_area_covariance_accumulator, + const float &area) const + { + float directional_xy_variance = compute_directional_xy_variance(line_dir, centroid_accumulator, second_moment_of_area_accumulator, + second_moment_of_area_covariance_accumulator, area); + if (directional_xy_variance < EPSILON) { return 0.0f; } + Vec3f centroid = centroid_accumulator / area; + float extreme_fiber_dist = line_alg::distance_to(Linef(centroid.head<2>().cast(), + (centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast()), + extreme_point.head<2>().cast()); float elastic_section_modulus = area * directional_xy_variance / extreme_fiber_dist; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) - << "extreme_fiber_dist: " << extreme_fiber_dist; - BOOST_LOG_TRIVIAL(debug) - << "elastic_section_modulus: " << elastic_section_modulus; + BOOST_LOG_TRIVIAL(debug) << "extreme_fiber_dist: " << extreme_fiber_dist; + BOOST_LOG_TRIVIAL(debug) << "elastic_section_modulus: " << elastic_section_modulus; #endif return elastic_section_modulus; } - float is_stable_while_extruding( - const IslandConnection &connection, - const ExtrusionLine &extruded_line, - const Vec3f &extreme_point, - float layer_z, - const Params ¶ms) const { - - Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized(); + float is_stable_while_extruding(const SliceConnection &connection, + const ExtrusionLine &extruded_line, + const Vec3f &extreme_point, + float layer_z, + const Params ¶ms) const + { + Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized(); const Vec3f &mass_centroid = this->volume_centroid_accumulator / this->volume; - float mass = this->volume * params.filament_density; - float weight = mass * params.gravity_constant; + float mass = this->volume * params.filament_density; + float weight = mass * params.gravity_constant; float movement_force = params.max_acceleration * mass; float extruder_conflict_force = params.standard_extruder_conflict_force + - std::min(extruded_line.malformation, 1.0f) * params.malformations_additive_conflict_extruder_force; + std::min(extruded_line.malformation, 1.0f) * params.malformations_additive_conflict_extruder_force; // section for bed calculations { - if (this->sticking_area < EPSILON) - return 1.0f; + if (this->sticking_area < EPSILON) return 1.0f; - Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; - float bed_yield_torque = -compute_elastic_section_modulus( - line_dir, - extreme_point, - this->sticking_centroid_accumulator, - this->sticking_second_moment_of_area_accumulator, - this->sticking_second_moment_of_area_covariance_accumulator, - this->sticking_area) - * params.get_bed_adhesion_yield_strength(); + Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; + float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, this->sticking_centroid_accumulator, + this->sticking_second_moment_of_area_accumulator, + this->sticking_second_moment_of_area_covariance_accumulator, + this->sticking_area) * + params.get_bed_adhesion_yield_strength(); - Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>()); - float bed_weight_arm_len = bed_weight_arm.norm(); - float bed_weight_dir_xy_variance = compute_directional_xy_variance(bed_weight_arm, - this->sticking_centroid_accumulator, - this->sticking_second_moment_of_area_accumulator, - this->sticking_second_moment_of_area_covariance_accumulator, - this->sticking_area); - float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f; - float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight; + Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>()); + float bed_weight_arm_len = bed_weight_arm.norm(); + float bed_weight_dir_xy_variance = compute_directional_xy_variance(bed_weight_arm, this->sticking_centroid_accumulator, + this->sticking_second_moment_of_area_accumulator, + this->sticking_second_moment_of_area_covariance_accumulator, + this->sticking_area); + float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f; + float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight; - float bed_movement_arm = std::max(0.0f, mass_centroid.z() - bed_centroid.z()); + float bed_movement_arm = std::max(0.0f, mass_centroid.z() - bed_centroid.z()); float bed_movement_torque = movement_force * bed_movement_arm; - float bed_conflict_torque_arm = layer_z - bed_centroid.z(); + float bed_conflict_torque_arm = layer_z - bed_centroid.z(); float bed_extruder_conflict_torque = extruder_conflict_force * bed_conflict_torque_arm; - float bed_total_torque = bed_movement_torque + bed_extruder_conflict_torque + bed_weight_torque - + bed_yield_torque; + float bed_total_torque = bed_movement_torque + bed_extruder_conflict_torque + bed_weight_torque + bed_yield_torque; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) - << "bed_centroid: " << bed_centroid.x() << " " << bed_centroid.y() << " " << bed_centroid.z(); - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_yield_torque: " << bed_yield_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_weight_arm: " << bed_weight_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_weight_torque: " << bed_weight_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_movement_arm: " << bed_movement_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_movement_torque: " << bed_movement_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_conflict_torque_arm: " << bed_conflict_torque_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: extruded_line.malformation: " << extruded_line.malformation; - BOOST_LOG_TRIVIAL(debug) - << "SSG: extruder_conflict_force: " << extruder_conflict_force; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_extruder_conflict_torque: " << bed_extruder_conflict_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z; + BOOST_LOG_TRIVIAL(debug) << "bed_centroid: " << bed_centroid.x() << " " << bed_centroid.y() << " " << bed_centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_yield_torque: " << bed_yield_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_arm: " << bed_weight_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_torque: " << bed_weight_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_arm: " << bed_movement_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_torque: " << bed_movement_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_conflict_torque_arm: " << bed_conflict_torque_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.malformation: " << extruded_line.malformation; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruder_conflict_force: " << extruder_conflict_force; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_extruder_conflict_torque: " << bed_extruder_conflict_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z; #endif - if (bed_total_torque > 0) - return bed_total_torque / bed_conflict_torque_arm; + if (bed_total_torque > 0) return bed_total_torque / bed_conflict_torque_arm; } - //section for weak connection calculations + // section for weak connection calculations { - if (connection.area < EPSILON) - return 1.0f; + if (connection.area < EPSILON) return 1.0f; Vec3f conn_centroid = connection.centroid_accumulator / connection.area; - if (layer_z - conn_centroid.z() < 3.0f) { - return -1.0f; - } - float conn_yield_torque = compute_elastic_section_modulus( - line_dir, - extreme_point, - connection.centroid_accumulator, - connection.second_moment_of_area_accumulator, - connection.second_moment_of_area_covariance_accumulator, - connection.area) * params.material_yield_strength; + if (layer_z - conn_centroid.z() < 3.0f) { return -1.0f; } + float conn_yield_torque = compute_elastic_section_modulus(line_dir, extreme_point, connection.centroid_accumulator, + connection.second_moment_of_area_accumulator, + connection.second_moment_of_area_covariance_accumulator, + connection.area) * + params.material_yield_strength; - float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); + float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); float conn_weight_torque = conn_weight_arm * weight * (conn_centroid.z() / layer_z); - float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); + float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); float conn_movement_torque = movement_force * conn_movement_arm; - float conn_conflict_torque_arm = layer_z - conn_centroid.z(); + float conn_conflict_torque_arm = layer_z - conn_centroid.z(); float conn_extruder_conflict_torque = extruder_conflict_force * conn_conflict_torque_arm; - float conn_total_torque = conn_movement_torque + conn_extruder_conflict_torque + conn_weight_torque - - conn_yield_torque; + float conn_total_torque = conn_movement_torque + conn_extruder_conflict_torque + conn_weight_torque - conn_yield_torque; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) - << "bed_centroid: " << conn_centroid.x() << " " << conn_centroid.y() << " " << conn_centroid.z(); - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_yield_torque: " << conn_yield_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_weight_arm: " << conn_weight_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_weight_torque: " << conn_weight_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_movement_arm: " << conn_movement_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_movement_torque: " << conn_movement_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_conflict_torque_arm: " << conn_conflict_torque_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_extruder_conflict_torque: " << conn_extruder_conflict_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z; + BOOST_LOG_TRIVIAL(debug) << "bed_centroid: " << conn_centroid.x() << " " << conn_centroid.y() << " " << conn_centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_yield_torque: " << conn_yield_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_arm: " << conn_weight_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_torque: " << conn_weight_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_movement_arm: " << conn_movement_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_movement_torque: " << conn_movement_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_conflict_torque_arm: " << conn_conflict_torque_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_extruder_conflict_torque: " << conn_extruder_conflict_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z; #endif return conn_total_torque / conn_conflict_torque_arm; @@ -837,57 +572,102 @@ public: } }; -#ifdef DETAILED_DEBUG_LOGS -void debug_print_graph(const std::vector &islands_graph) { - std::cout << "BUILT ISLANDS GRAPH:" << std::endl; - for (size_t layer_idx = 0; layer_idx < islands_graph.size(); ++layer_idx) { - std::cout << "ISLANDS AT LAYER: " << layer_idx << " AT HEIGHT: " << islands_graph[layer_idx].layer_z - << std::endl; - for (size_t island_idx = 0; island_idx < islands_graph[layer_idx].islands.size(); ++island_idx) { - const Island &island = islands_graph[layer_idx].islands[island_idx]; - std::cout << " ISLAND " << island_idx << std::endl; - std::cout << " volume: " << island.volume << std::endl; - std::cout << " sticking_area: " << island.sticking_area << std::endl; - std::cout << " connected_islands count: " << island.connected_islands.size() << std::endl; - std::cout << " lines count: " << island.external_lines.size() << std::endl; +// return new object part and actual area covered by extrusions +std::tuple build_object_part_from_slice(const LayerSlice &slice, const Layer *layer) +{ + ObjectPart new_object_part; + float area_covered_by_extrusions = 0; + + auto add_extrusions_to_object = [&new_object_part, &area_covered_by_extrusions](const ExtrusionEntity *e, const LayerRegion *region) { + float flow_width = get_flow_width(region, e->role()); + const Layer *l = region->layer(); + float slice_z = l->slice_z; + float height = l->height; + std::vector lines = to_short_lines(e, 5.0); + for (const ExtrusionLine &line : lines) { + float volume = line.len * height * flow_width * PI / 4.0f; + area_covered_by_extrusions += line.len * flow_width; + new_object_part.volume += volume; + new_object_part.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), slice_z) * volume; + + if (l->id() == 0) { // first layer + float sticking_area = line.len * flow_width; + new_object_part.sticking_area += sticking_area; + Vec2f middle = Vec2f((line.a + line.b) / 2.0f); + new_object_part.sticking_centroid_accumulator += sticking_area * to_3d(middle, slice_z); + // Bottom infill lines can be quite long, and algined, so the middle approximaton used above does not work + Vec2f dir = (line.b - line.a).normalized(); + float segment_length = flow_width; // segments of size flow_width + for (float segment_middle_dist = std::min(line.len, segment_length * 0.5f); segment_middle_dist < line.len; + segment_middle_dist += segment_length) { + Vec2f segment_middle = line.a + segment_middle_dist * dir; + new_object_part.sticking_second_moment_of_area_accumulator += segment_length * flow_width * + segment_middle.cwiseProduct(segment_middle); + new_object_part.sticking_second_moment_of_area_covariance_accumulator += segment_length * flow_width * + segment_middle.x() * segment_middle.y(); + } + } + } + }; + + for (const auto &island : slice.islands) { + const LayerRegion *perimeter_region = layer->get_region(island.perimeters.region()); + for (const auto &perimeter_idx : island.perimeters) { + for (const ExtrusionEntity *perimeter : + static_cast(perimeter_region->perimeters().entities[perimeter_idx])->entities) { + add_extrusions_to_object(perimeter, perimeter_region); + } + } + for (const LayerExtrusionRange &fill_range : island.fills) { + const LayerRegion *fill_region = layer->get_region(fill_range.region()); + for (const auto &fill_idx : fill_range) { + for (const ExtrusionEntity *fill : + static_cast(fill_region->fills().entities[fill_idx])->entities) { + add_extrusions_to_object(fill, fill_region); + } + } + } + const LayerRegion *thin_fill_region = layer->get_region(island.fill_region_id); + for (const auto &thin_fill_idx : island.thin_fills) { + add_extrusions_to_object(thin_fill_region->thin_fills().entities[thin_fill_idx], perimeter_region); } } - std::cout << "END OF GRAPH" << std::endl; -} -#endif -class ActiveObjectParts { - size_t next_part_idx = 0; + return {new_object_part, area_covered_by_extrusions}; +} + +class ActiveObjectParts +{ + size_t next_part_idx = 0; std::unordered_map active_object_parts; - std::unordered_map active_object_parts_id_mapping; + std::unordered_map active_object_parts_id_mapping; public: - size_t get_flat_id(size_t id) { + size_t get_flat_id(size_t id) + { size_t index = active_object_parts_id_mapping.at(id); - while (index != active_object_parts_id_mapping.at(index)) { - index = active_object_parts_id_mapping.at(index); - } + while (index != active_object_parts_id_mapping.at(index)) { index = active_object_parts_id_mapping.at(index); } size_t i = id; while (index != active_object_parts_id_mapping.at(i)) { - size_t next = active_object_parts_id_mapping[i]; + size_t next = active_object_parts_id_mapping[i]; active_object_parts_id_mapping[i] = index; - i = next; + i = next; } return index; } - ObjectPart& access(size_t id) { - return this->active_object_parts.at(this->get_flat_id(id)); - } + ObjectPart &access(size_t id) { return this->active_object_parts.at(this->get_flat_id(id)); } - size_t insert(const Island &island) { - this->active_object_parts.emplace(next_part_idx, ObjectPart(island)); + size_t insert(const ObjectPart &new_part) + { + this->active_object_parts.emplace(next_part_idx, new_part); this->active_object_parts_id_mapping.emplace(next_part_idx, next_part_idx); return next_part_idx++; } - void merge(size_t from, size_t to) { - size_t to_flat = this->get_flat_id(to); + void merge(size_t from, size_t to) + { + size_t to_flat = this->get_flat_id(to); size_t from_flat = this->get_flat_id(from); active_object_parts.at(to_flat).add(active_object_parts.at(from_flat)); active_object_parts.erase(from_flat); @@ -895,282 +675,199 @@ public: } }; -Issues check_global_stability(SupportGridFilter supports_presence_grid, - const std::vector &islands_graph, const Params ¶ms) { -#ifdef DETAILED_DEBUG_LOGS - debug_print_graph(islands_graph); -#endif +Issues check_stability(const PrintObject *po, const Params ¶ms) +{ + Issues issues{}; + SupportGridFilter supports_presence_grid(po, params.min_distance_between_support_points); + ActiveObjectParts active_object_parts{}; + LD prev_layer_ext_perim_lines({}); - Issues issues { }; - ActiveObjectParts active_object_parts { }; - std::unordered_map prev_island_to_object_part_mapping; - std::unordered_map next_island_to_object_part_mapping; + std::unordered_map prev_slice_idx_to_object_part_mapping; + std::unordered_map next_slice_idx_to_object_part_mapping; + std::unordered_map prev_slice_idx_to_weakest_connection; + std::unordered_map next_slice_idx_to_weakest_connection; - std::unordered_map prev_island_weakest_connection; - std::unordered_map next_island_weakest_connection; + for (size_t layer_idx = 0; layer_idx < po->layer_count(); ++layer_idx) { + const Layer *layer = po->get_layer(layer_idx); + float bottom_z = layer->bottom_z(); + auto create_support_point_position = [bottom_z](const Vec2f &layer_pos) { return Vec3f{layer_pos.x(), layer_pos.y(), bottom_z}; }; - for (size_t layer_idx = 0; layer_idx < islands_graph.size(); ++layer_idx) { - float layer_z = islands_graph[layer_idx].layer_z; + for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { + const LayerSlice &slice = layer->lslices_ex.at(slice_idx); + auto [new_part, covered_area] = build_object_part_from_slice(slice, layer); + SliceConnection connection_to_below = estimate_slice_connection(slice_idx, layer); #ifdef DETAILED_DEBUG_LOGS - for (const auto &m : prev_island_to_object_part_mapping) { - std::cout << "island " << m.first << " maps to part " << m.second << std::endl; - prev_island_weakest_connection[m.first].print_info("connection info:"); - } + std::cout << "SLICE IDX: " << slice_idx << std::endl; + for (const auto &link : slice.overlaps_below) { + std::cout << "connected to slice below: " << link.slice_idx << " by area : " << link.area << std::endl; + } + connection_to_below.print_info("CONNECTION TO BELOW"); #endif - for (size_t island_idx = 0; island_idx < islands_graph[layer_idx].islands.size(); ++island_idx) { - const Island &island = islands_graph[layer_idx].islands[island_idx]; - if (island.connected_islands.empty()) { //new object part emerging - size_t part_id = active_object_parts.insert(island); - next_island_to_object_part_mapping.emplace(island_idx, part_id); - next_island_weakest_connection.emplace(island_idx, - IslandConnection { 1.0f, Vec3f::Zero(), Vec2f { INFINITY, INFINITY } }); + if (connection_to_below.area < EPSILON) { // new object part emerging + size_t part_id = active_object_parts.insert(new_part); + next_slice_idx_to_object_part_mapping.emplace(slice_idx, part_id); + next_slice_idx_to_weakest_connection.emplace(slice_idx, connection_to_below); } else { - size_t final_part_id { }; - IslandConnection transfered_weakest_connection { }; - IslandConnection new_weakest_connection { }; + size_t final_part_id{}; + SliceConnection transfered_weakest_connection{}; // MERGE parts { std::unordered_set parts_ids; - for (const auto &connection : island.connected_islands) { - size_t part_id = active_object_parts.get_flat_id( - prev_island_to_object_part_mapping.at(connection.first)); + for (const auto &link : slice.overlaps_below) { + size_t part_id = active_object_parts.get_flat_id(prev_slice_idx_to_object_part_mapping.at(link.slice_idx)); parts_ids.insert(part_id); - transfered_weakest_connection.add(prev_island_weakest_connection.at(connection.first)); - new_weakest_connection.add(connection.second); + transfered_weakest_connection.add(prev_slice_idx_to_weakest_connection.at(link.slice_idx)); } + final_part_id = *parts_ids.begin(); for (size_t part_id : parts_ids) { - if (final_part_id != part_id) { - active_object_parts.merge(part_id, final_part_id); - } + if (final_part_id != part_id) { active_object_parts.merge(part_id, final_part_id); } } } - auto estimate_conn_strength = [layer_z](const IslandConnection &conn) { - Vec3f centroid = conn.centroid_accumulator / conn.area; - Vec2f variance = (conn.second_moment_of_area_accumulator / conn.area - - centroid.head<2>().cwiseProduct(centroid.head<2>())); - float xy_variance = variance.x() + variance.y(); - float arm_len_estimate = std::max(1.0f, layer_z - (conn.centroid_accumulator.z() / conn.area)); + auto estimate_conn_strength = [bottom_z](const SliceConnection &conn) { + if (conn.area < EPSILON) { // connection is empty, does not exists. Return max strength so that it is not picked as the + // weakest connection. + return INFINITY; + } + Vec3f centroid = conn.centroid_accumulator / conn.area; + Vec2f variance = (conn.second_moment_of_area_accumulator / conn.area - + centroid.head<2>().cwiseProduct(centroid.head<2>())); + float xy_variance = variance.x() + variance.y(); + float arm_len_estimate = std::max(1.0f, bottom_z - (conn.centroid_accumulator.z() / conn.area)); return conn.area * sqrt(xy_variance) / arm_len_estimate; }; #ifdef DETAILED_DEBUG_LOGS - new_weakest_connection.print_info("new_weakest_connection"); + connection_to_below.print_info("new_weakest_connection"); transfered_weakest_connection.print_info("transfered_weakest_connection"); #endif - if (estimate_conn_strength(transfered_weakest_connection) - > estimate_conn_strength(new_weakest_connection)) { - transfered_weakest_connection = new_weakest_connection; + if (estimate_conn_strength(transfered_weakest_connection) > estimate_conn_strength(connection_to_below)) { + transfered_weakest_connection = connection_to_below; } - next_island_weakest_connection.emplace(island_idx, transfered_weakest_connection); - next_island_to_object_part_mapping.emplace(island_idx, final_part_id); + next_slice_idx_to_weakest_connection.emplace(slice_idx, transfered_weakest_connection); + next_slice_idx_to_object_part_mapping.emplace(slice_idx, final_part_id); ObjectPart &part = active_object_parts.access(final_part_id); - part.add(ObjectPart(island)); + part.add(new_part); } } - prev_island_to_object_part_mapping = next_island_to_object_part_mapping; - next_island_to_object_part_mapping.clear(); - prev_island_weakest_connection = next_island_weakest_connection; - next_island_weakest_connection.clear(); + prev_slice_idx_to_object_part_mapping = next_slice_idx_to_object_part_mapping; + next_slice_idx_to_object_part_mapping.clear(); + prev_slice_idx_to_weakest_connection = next_slice_idx_to_weakest_connection; + next_slice_idx_to_weakest_connection.clear(); - // All object parts updated, inactive parts removed and weakest point of each island updated as well. - // Now compute the stability of each active object part, adding supports where necessary, and also - // check each island whether the weakest point is strong enough. If not, add supports as well. - - for (size_t island_idx = 0; island_idx < islands_graph[layer_idx].islands.size(); ++island_idx) { - const Island &island = islands_graph[layer_idx].islands[island_idx]; - ObjectPart &part = active_object_parts.access(prev_island_to_object_part_mapping[island_idx]); - - IslandConnection &weakest_conn = prev_island_weakest_connection[island_idx]; + std::vector current_layer_ext_perims_lines{}; + current_layer_ext_perims_lines.reserve(prev_layer_ext_perim_lines.get_lines().size()); + // All object parts updated, and for each slice we have coresponding weakest connection. + // We can now check each slice and its corresponding weakest connection and object part for stability. + for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { + const LayerSlice &slice = layer->lslices_ex.at(slice_idx); + ObjectPart &part = active_object_parts.access(prev_slice_idx_to_object_part_mapping[slice_idx]); + SliceConnection &weakest_conn = prev_slice_idx_to_weakest_connection[slice_idx]; + std::vector current_slice_ext_perims_lines{}; + current_slice_ext_perims_lines.reserve(prev_layer_ext_perim_lines.get_lines().size() / layer->lslices_ex.size()); #ifdef DETAILED_DEBUG_LOGS weakest_conn.print_info("weakest connection info: "); #endif - LD island_lines_dist(island.external_lines); - float unchecked_dist = params.min_distance_between_support_points + 1.0f; + // Function that is used when new support point is generated. It will update the ObjectPart stability, weakest conneciton info, + // and the support presence grid and add the point to the issues. + auto reckon_new_support_point = [&part, &weakest_conn, &issues, &supports_presence_grid, ¶ms, + &layer_idx](const Vec3f &support_point, float force, const Vec2f &dir) { + if (supports_presence_grid.position_taken(support_point) || layer_idx <= 1) { return; } + float area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI); + part.add_support_point(support_point, area); - for (const ExtrusionLine &line : island.external_lines) { - if ((unchecked_dist + line.len < params.min_distance_between_support_points - && line.malformation < 0.3f) || line.len == 0) { - unchecked_dist += line.len; - } else { - unchecked_dist = line.len; - Vec3f pivot_site_search_point = to_3d(Vec2f(line.b + (line.b - line.a).normalized() * 300.0f), - layer_z); - auto [dist, nidx, target_point] = island_lines_dist.signed_distance_from_lines_extra(pivot_site_search_point.head<2>()); - Vec3f support_point = to_3d(target_point, layer_z); - auto force = part.is_stable_while_extruding(weakest_conn, line, support_point, layer_z, params); - if (force > 0) { - if (!supports_presence_grid.position_taken(support_point)) { - float orig_area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI); - // artifically lower the area for materials that have strong bed adhesion, as this adhesion does not apply for support points - float altered_area = orig_area * params.get_support_spots_adhesion_strength() / params.get_bed_adhesion_yield_strength(); - part.add_support_point(support_point, altered_area); + float radius = params.support_points_interface_radius; + issues.support_points.emplace_back(support_point, force, radius, dir); + supports_presence_grid.take_position(support_point); - float radius = part.get_volume() < params.small_parts_threshold ? params.small_parts_support_points_interface_radius : params.support_points_interface_radius; - issues.support_points.emplace_back(support_point, force, radius, to_3d(Vec2f(line.b - line.a).normalized(), 0.0f)); - supports_presence_grid.take_position(support_point); + if (weakest_conn.area > EPSILON) { // Do not add it to the weakest connection if it is not valid - does not exist + weakest_conn.area += area; + weakest_conn.centroid_accumulator += support_point * area; + weakest_conn.second_moment_of_area_accumulator += area * support_point.head<2>().cwiseProduct(support_point.head<2>()); + weakest_conn.second_moment_of_area_covariance_accumulator += area * support_point.x() * support_point.y(); + } + }; - weakest_conn.area += altered_area; - weakest_conn.centroid_accumulator += support_point * altered_area; - weakest_conn.second_moment_of_area_accumulator += altered_area * - support_point.head<2>().cwiseProduct(support_point.head<2>()); - weakest_conn.second_moment_of_area_covariance_accumulator += altered_area * support_point.x() * - support_point.y(); + // first we will check local extrusion stability of bridges, then of perimeters. Perimeters are more important, they + // account for most of the curling and possible crashes, so on them we will run also global stability check + for (const auto &island : slice.islands) { + // Support bridges where needed. + for (const LayerExtrusionRange &fill_range : island.fills) { + const LayerRegion *fill_region = layer->get_region(fill_range.region()); + for (const auto &fill_idx : fill_range) { + const ExtrusionEntity *entity = fill_region->fills().entities[fill_idx]; + if (entity->role() == erBridgeInfill) { + for (const ExtrusionLine &bridge : + check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines, params)) { + if (bridge.support_point_generated) { + reckon_new_support_point(create_support_point_position(bridge.b), -EPSILON, Vec2f::Zero()); + } + } } } } + + const LayerRegion *perimeter_region = layer->get_region(island.perimeters.region()); + for (const auto &perimeter_idx : island.perimeters) { + const ExtrusionEntity *entity = perimeter_region->perimeters().entities[perimeter_idx]; + std::vector perims = check_extrusion_entity_stability(entity, perimeter_region, + prev_layer_ext_perim_lines, params); + for (const ExtrusionLine &perim : perims) { + if (perim.support_point_generated) { + reckon_new_support_point(create_support_point_position(perim.b), -EPSILON, Vec2f::Zero()); + } + if (perim.is_external_perimeter()) { current_slice_ext_perims_lines.push_back(perim); } + } + } } - } - //end of iteration over layer - } + + LD current_slice_lines_distancer(current_slice_ext_perims_lines); + float unchecked_dist = params.min_distance_between_support_points + 1.0f; + + for (const ExtrusionLine &line : current_slice_ext_perims_lines) { + if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.malformation < 0.3f) || line.len == 0) { + unchecked_dist += line.len; + } else { + unchecked_dist = line.len; + Vec2f pivot_site_search_point = Vec2f(line.b + (line.b - line.a).normalized() * 300.0f); + auto [dist, nidx, + nearest_point] = current_slice_lines_distancer.signed_distance_from_lines_extra(pivot_site_search_point); + Vec3f support_point = create_support_point_position(nearest_point); + auto force = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params); + if (force > 0) { reckon_new_support_point(support_point, force, (line.b - line.a).normalized()); } + } + } + current_layer_ext_perims_lines.insert(current_layer_ext_perims_lines.end(), current_slice_ext_perims_lines.begin(), + current_slice_ext_perims_lines.end()); + } // slice iterations + prev_layer_ext_perim_lines = LD(current_layer_ext_perims_lines); + } // layer iterations return issues; } -std::tuple> check_extrusions_and_build_graph(const PrintObject *po, - const Params ¶ms) { #ifdef DEBUG_FILES - FILE *segmentation_f = boost::nowide::fopen(debug_out_path("segmentation.obj").c_str(), "w"); - FILE *malform_f = boost::nowide::fopen(debug_out_path("malformations.obj").c_str(), "w"); -#endif - - Issues issues { }; - Malformations malformations{}; - std::vector islands_graph; - std::vector layer_lines; - float flow_width = get_flow_width(po->layers()[po->layer_count() - 1]->regions()[0], erExternalPerimeter); - PixelGrid prev_layer_grid(po, flow_width*2.0f); - -// PREPARE BASE LAYER - const Layer *layer = po->layers()[0]; - malformations.layers.push_back({}); // no malformations to be expected at first layer - for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { - for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { - push_lines(perimeter, layer_lines); - } // perimeter - } // ex_entity - for (const ExtrusionEntity *ex_entity : layer_region->fills.entities) { - for (const ExtrusionEntity *fill : static_cast(ex_entity)->entities) { - push_lines(fill, layer_lines); - } // fill - } // ex_entity - } // region - - auto [layer_islands, layer_grid] = reckon_islands(layer, true, 0, prev_layer_grid, - layer_lines, params); - islands_graph.push_back(std::move(layer_islands)); -#ifdef DEBUG_FILES - for (size_t x = 0; x < size_t(layer_grid.get_pixel_count().x()); ++x) { - for (size_t y = 0; y < size_t(layer_grid.get_pixel_count().y()); ++y) { - Vec2i coords = Vec2i(x, y); - size_t island_idx = layer_grid.get_pixel(coords); - if (layer_grid.get_pixel(coords) != NULL_ISLAND) { - Vec2f pos = layer_grid.get_pixel_center(coords); - size_t pseudornd = ((island_idx + 127) * 33331 + 6907) % 23; - Vec3f color = value_to_rgbf(0.0f, float(23), float(pseudornd)); - fprintf(segmentation_f, "v %f %f %f %f %f %f\n", pos[0], - pos[1], layer->slice_z, color[0], color[1], color[2]); - } - } - } - for (const auto &line : layer_lines) { - if (line.malformation > 0.0f) { - Vec3f color = value_to_rgbf(-EPSILON, 1.0f, line.malformation); - fprintf(malform_f, "v %f %f %f %f %f %f\n", line.b[0], - line.b[1], layer->slice_z, color[0], color[1], color[2]); - } - } -#endif - LD external_lines(layer_lines); - layer_lines.clear(); - prev_layer_grid = layer_grid; - - for (size_t layer_idx = 1; layer_idx < po->layer_count(); ++layer_idx) { - const Layer *layer = po->layers()[layer_idx]; - for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { - for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { - check_extrusion_entity_stability(perimeter, layer_lines, layer->slice_z, layer_region, - external_lines, issues, params); - } // perimeter - } // ex_entity - for (const ExtrusionEntity *ex_entity : layer_region->fills.entities) { - for (const ExtrusionEntity *fill : static_cast(ex_entity)->entities) { - if (fill->role() == ExtrusionRole::erGapFill - || fill->role() == ExtrusionRole::erBridgeInfill) { - check_extrusion_entity_stability(fill, layer_lines, layer->slice_z, layer_region, - external_lines, issues, params); - } else { - push_lines(fill, layer_lines); - } - } // fill - } // ex_entity - } // region - - auto [layer_islands, layer_grid] = reckon_islands(layer, false, 0, prev_layer_grid, - layer_lines, params); - islands_graph.push_back(std::move(layer_islands)); - - Lines malformed_lines{}; - for (const auto &line : layer_lines) { - if (line.malformation > 0.3f) { malformed_lines.push_back(Line{Point::new_scale(line.a), Point::new_scale(line.b)}); } - } - malformations.layers.push_back(malformed_lines); - -#ifdef DEBUG_FILES - for (size_t x = 0; x < size_t(layer_grid.get_pixel_count().x()); ++x) { - for (size_t y = 0; y < size_t(layer_grid.get_pixel_count().y()); ++y) { - Vec2i coords = Vec2i(x, y); - size_t island_idx = layer_grid.get_pixel(coords); - if (layer_grid.get_pixel(coords) != NULL_ISLAND) { - Vec2f pos = layer_grid.get_pixel_center(coords); - size_t pseudornd = ((island_idx + 127) * 33331 + 6907) % 23; - Vec3f color = value_to_rgbf(0.0f, float(23), float(pseudornd)); - fprintf(segmentation_f, "v %f %f %f %f %f %f\n", pos[0], - pos[1], layer->slice_z, color[0], color[1], color[2]); - } - } - } - for (const auto &line : layer_lines) { - if (line.malformation > 0.0f) { - Vec3f color = value_to_rgbf(-EPSILON, layer->height*params.max_malformation_factor, line.malformation); - fprintf(malform_f, "v %f %f %f %f %f %f\n", line.b[0], - line.b[1], layer->slice_z, color[0], color[1], color[2]); - } - } -#endif - external_lines = LD(layer_lines); - layer_lines.clear(); - prev_layer_grid = layer_grid; - } - -#ifdef DEBUG_FILES - fclose(segmentation_f); - fclose(malform_f); -#endif - - return {issues, malformations, islands_graph}; -} - -#ifdef DEBUG_FILES -void debug_export(Issues issues, std::string file_name) { +void debug_export(Issues issues, std::string file_name) +{ Slic3r::CNumericLocalesSetter locales_setter; { FILE *fp = boost::nowide::fopen(debug_out_path((file_name + "_supports.obj").c_str()).c_str(), "w"); if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "Debug files: Couldn't open " << file_name << " for writing"; + BOOST_LOG_TRIVIAL(error) << "Debug files: Couldn't open " << file_name << " for writing"; return; } for (size_t i = 0; i < issues.support_points.size(); ++i) { - fprintf(fp, "v %f %f %f %f %f %f\n", issues.support_points[i].position(0), - issues.support_points[i].position(1), - issues.support_points[i].position(2), 1.0, 0.0, 1.0); + if (issues.support_points[i].force <= 0) { + fprintf(fp, "v %f %f %f %f %f %f\n", issues.support_points[i].position(0), issues.support_points[i].position(1), + issues.support_points[i].position(2), 0.0, 1.0, 0.0); + } else { + fprintf(fp, "v %f %f %f %f %f %f\n", issues.support_points[i].position(0), issues.support_points[i].position(1), + issues.support_points[i].position(2), 1.0, 0.0, 0.0); + } } fclose(fp); @@ -1181,21 +878,17 @@ void debug_export(Issues issues, std::string file_name) { // std::vector quick_search(const PrintObject *po, const Params ¶ms) { // return {}; // } - -std::tuple full_search(const PrintObject *po, const Params ¶ms) { - auto [local_issues, malformations, graph] = check_extrusions_and_build_graph(po, params); - Issues global_issues = check_global_stability( { po, params.min_distance_between_support_points }, graph, params); +Issues full_search(const PrintObject *po, const Params ¶ms) +{ + Issues issues = check_stability(po, params); #ifdef DEBUG_FILES - debug_export(local_issues, "local_issues"); - debug_export(global_issues, "global_issues"); + debug_export(issues, "issues"); #endif - global_issues.support_points.insert(global_issues.support_points.end(), - local_issues.support_points.begin(), local_issues.support_points.end()); - - return {global_issues, malformations}; + return issues; } + struct LayerCurlingEstimator { LD prev_layer_lines = LD({}); @@ -1307,18 +1000,28 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms) if (l->regions().empty()) { continue; } - std::unordered_map extrusions_widths; - std::vector extrusion_lines; - for (const LayerRegion *region : l->regions()) { - for (const ExtrusionEntity *extrusion : region->perimeters.flatten().entities) { - auto lines = to_short_lines(extrusion, params.bridge_distance); - extrusion_lines.insert(extrusion_lines.end(), lines.begin(), lines.end()); - extrusions_widths.emplace(extrusion, get_flow_width(region, extrusion->role())); + struct Visitor { + Visitor(const Params ¶ms) : params(params) {} + void recursive_do(const ExtrusionEntityCollection &collection, const LayerRegion *region) { + for (const ExtrusionEntity* entity : collection.entities) + if (entity->is_collection()) + this->recursive_do(*static_cast(entity), region); + else { + append(extrusion_lines, to_short_lines(entity, params.bridge_distance)); + extrusions_widths.emplace(entity, get_flow_width(region, entity->role())); + } } - } - lce.flow_width_getter = [&](const ExtrusionLine &l) { return extrusions_widths[l.origin_entity]; }; + const Params ¶ms; + std::unordered_map extrusions_widths; + std::vector extrusion_lines; + } visitor(params); - lce.estimate_curling(extrusion_lines, l); + for (const LayerRegion *region : l->regions()) + visitor.recursive_do(region->perimeters(), region); + + lce.flow_width_getter = [&](const ExtrusionLine &l) { return visitor.extrusions_widths[l.origin_entity]; }; + + lce.estimate_curling(visitor.extrusion_lines, l); #ifdef DEBUG_FILES for (const ExtrusionLine &line : extrusion_lines) { diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 6a549bd36f..b0cbab57dc 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -32,8 +32,7 @@ struct Params { const float min_distance_between_support_points = 3.0f; //mm const float support_points_interface_radius = 1.5f; // mm const float connections_min_considerable_area = 1.5f; //mm^2 - const float small_parts_threshold = 5.0f; //mm^3 - const float small_parts_support_points_interface_radius = 3.0f; // mm + const float min_distance_to_allow_local_supports = 2.0f; //mm std::string filament_type; const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position. @@ -61,11 +60,11 @@ struct Params { }; struct SupportPoint { - SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec3f &direction); + SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction); Vec3f position; float force; float spot_radius; - Vec3f direction; + Vec2f direction; }; struct Issues { @@ -77,7 +76,7 @@ struct Malformations { }; // std::vector quick_search(const PrintObject *po, const Params ¶ms); -std::tuple full_search(const PrintObject *po, const Params ¶ms); +Issues full_search(const PrintObject *po, const Params ¶ms); void estimate_supports_malformations(SupportLayerPtrs &layers, float supports_flow_width, const Params ¶ms); void estimate_malformations(LayerPtrs &layers, const Params ¶ms); diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index ef1de30e95..4afcdf6f5c 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -104,7 +104,7 @@ public: }; typedef std::vector Surfaces; -typedef std::vector SurfacesPtr; +typedef std::vector SurfacesPtr; inline Polygons to_polygons(const Surface &surface) { @@ -221,6 +221,7 @@ inline void polygons_append(Polygons &dst, const SurfacesPtr &src) } } +/* inline void polygons_append(Polygons &dst, SurfacesPtr &&src) { dst.reserve(dst.size() + number_polygons(src)); @@ -230,6 +231,7 @@ inline void polygons_append(Polygons &dst, SurfacesPtr &&src) (*it)->expolygon.holes.clear(); } } +*/ // Append a vector of Surfaces at the end of another vector of polygons. inline void surfaces_append(Surfaces &dst, const ExPolygons &src, SurfaceType surfaceType) diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp index 2ec071f7db..10a12b6836 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -22,46 +22,45 @@ void SurfaceCollection::simplify(double tolerance) } /* group surfaces by common properties */ -void SurfaceCollection::group(std::vector *retval) +void SurfaceCollection::group(std::vector *retval) const { - for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) { + for (const Surface &surface : this->surfaces) { // find a group with the same properties - SurfacesPtr* group = NULL; + SurfacesPtr *group = nullptr; for (std::vector::iterator git = retval->begin(); git != retval->end(); ++git) - if (! git->empty() && surfaces_could_merge(*git->front(), *it)) { + if (! git->empty() && surfaces_could_merge(*git->front(), surface)) { group = &*git; break; } // if no group with these properties exists, add one - if (group == NULL) { + if (group == nullptr) { retval->resize(retval->size() + 1); group = &retval->back(); } // append surface to group - group->push_back(&*it); + group->push_back(&surface); } } -SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) +SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) const { SurfacesPtr ss; - for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { - if (surface->surface_type == type) ss.push_back(&*surface); - } + for (const Surface &surface : this->surfaces) + if (surface.surface_type == type) + ss.push_back(&surface); return ss; } -SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) +SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) const { SurfacesPtr ss; - for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { + for (const Surface &surface : this->surfaces) for (int i = 0; i < ntypes; ++ i) { - if (surface->surface_type == types[i]) { - ss.push_back(&*surface); + if (surface.surface_type == types[i]) { + ss.push_back(&surface); break; } } - } return ss; } diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index 8c27a507bf..b81808b329 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -17,7 +17,7 @@ public: SurfaceCollection(Surfaces &&surfaces) : surfaces(std::move(surfaces)) {}; void simplify(double tolerance); - void group(std::vector *retval); + void group(std::vector *retval) const; template bool any_internal_contains(const T &item) const { for (const Surface &surface : this->surfaces) if (surface.is_internal() && surface.expolygon.contains(item)) return true; return false; @@ -26,8 +26,8 @@ public: for (const Surface &surface : this->surfaces) if (surface.is_bottom() && surface.expolygon.contains(item)) return true; return false; } - SurfacesPtr filter_by_type(const SurfaceType type); - SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes); + SurfacesPtr filter_by_type(const SurfaceType type) const; + SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes) const; void keep_type(const SurfaceType type); void keep_types(const SurfaceType *types, int ntypes); void remove_type(const SurfaceType type); @@ -48,6 +48,13 @@ public: return false; } + Surfaces::const_iterator cbegin() const { return this->surfaces.cbegin(); } + Surfaces::const_iterator cend() const { return this->surfaces.cend(); } + Surfaces::const_iterator begin() const { return this->surfaces.cbegin(); } + Surfaces::const_iterator end() const { return this->surfaces.cend(); } + Surfaces::iterator begin() { return this->surfaces.begin(); } + Surfaces::iterator end() { return this->surfaces.end(); } + void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; } void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); } void set(const ExPolygons &src, SurfaceType surfaceType) { clear(); this->append(src, surfaceType); } diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index e098636fd4..b416db634f 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -291,6 +291,42 @@ void TreeModelVolumes::precalculate(const coord_t max_layer) // m_precalculated = true; BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms."; + +#if 0 + // Paint caches into SVGs: + auto paint_cache_into_SVGs = [this](const RadiusLayerPolygonCache &cache, std::string_view name) { + const std::vector>> sorted = cache.sorted(); + static constexpr const std::string_view colors[] = { + "red", "green", "blue", "magenta", "orange" + }; + static constexpr const size_t num_colors = sizeof(colors) / sizeof(colors[0]); + for (size_t i = 0; i < sorted.size();) { + // Find range of cache items with the same layer index. + size_t j = i; + for (++ j; j < sorted.size() && sorted[i].first.second == sorted[j].first.second; ++ j) ; + // Collect expolygons in reverse order (largest to smallest). + std::vector> expolygons_with_attributes; + for (int k = int(j - 1); k >= int(i); -- k) { + std::string legend = format("radius-%1%", unscaled(sorted[k].first.first)); + expolygons_with_attributes.push_back({ union_ex(sorted[k].second), SVG::ExPolygonAttributes(legend, std::string(colors[(k - int(i)) % num_colors]), 1.) }); + } + // Render the range of per radius collision polygons into a common SVG. + SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d.svg", name.data(), sorted[i].first.second), expolygons_with_attributes); + i = j; + } + }; + paint_cache_into_SVGs(m_collision_cache, "collision_cache"); + paint_cache_into_SVGs(m_collision_cache_holefree, "collision_cache_holefree"); + paint_cache_into_SVGs(m_avoidance_cache, "avoidance_cache"); + paint_cache_into_SVGs(m_avoidance_cache_slow, "avoidance_cache_slow"); + paint_cache_into_SVGs(m_avoidance_cache_to_model, "avoidance_cache_to_model"); + paint_cache_into_SVGs(m_avoidance_cache_to_model_slow, "avoidance_cache_to_model_slow"); + paint_cache_into_SVGs(m_placeable_areas_cache, "placable_areas_cache"); + paint_cache_into_SVGs(m_avoidance_cache_holefree, "avoidance_cache_holefree"); + paint_cache_into_SVGs(m_avoidance_cache_holefree_to_model, "avoidance_cache_holefree_to_model"); + paint_cache_into_SVGs(m_wall_restrictions_cache, "wall_restrictions_cache"); + paint_cache_into_SVGs(m_wall_restrictions_cache_min, "wall_restrictions_cache_min"); +#endif } const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const @@ -783,4 +819,14 @@ coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const return out; } +// For debugging purposes, sorted by layer index, then by radius. +std::vector>> TreeModelVolumes::RadiusLayerPolygonCache::sorted() const +{ + std::vector>> out; + for (auto it = this->data.begin(); it != this->data.end(); ++ it) + out.emplace_back(it->first, it->second); + std::sort(out.begin(), out.end(), [](auto &l, auto &r){ return l.first.second < r.first.second || (l.first.second == r.first.second) && l.first.first < r.first.first; }); + return out; +} + } // namespace Slic3r::FFFTreeSupport diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index eea271bd4b..0ced2f422d 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -366,6 +366,9 @@ private: return max_layer; } + // For debugging purposes, sorted by layer index, then by radius. + [[nodiscard]] std::vector>> sorted() const; + private: RadiusLayerPolygonCacheData data; mutable std::mutex mutex; diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index b60744e327..1f0aa00b23 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -115,76 +115,6 @@ static inline void check_self_intersections(const ExPolygon &expoly, const std:: #endif // _WIN32 } -static inline void clip_for_diff(const Polygon &src, const BoundingBox &bbox, Polygon &out) -{ - out.clear(); - const size_t cnt = src.points.size(); - if (cnt < 3) - return; - - enum class Side { - Left = 1, - Right = 2, - Top = 4, - Bottom = 8 - }; - - auto sides = [bbox](const Point &p) { - return int(p.x() < bbox.min.x()) * int(Side::Left) + - int(p.x() > bbox.max.x()) * int(Side::Right) + - int(p.y() < bbox.min.y()) * int(Side::Bottom) + - int(p.y() > bbox.max.y()) * int(Side::Top); - }; - - int sides_prev = sides(src.points.back()); - int sides_this = sides(src.points.front()); - const size_t last = cnt - 1; - for (size_t i = 0; i < last; ++ i) { - int sides_next = sides(src.points[i + 1]); - if (// This point is inside. Take it. - sides_this == 0 || - // Either this point is outside and previous or next is inside, or - // the edge possibly cuts corner of the bounding box. - (sides_prev & sides_this & sides_next) == 0) { - out.points.emplace_back(src.points[i]); - sides_prev = sides_this; - } else { - // All the three points (this, prev, next) are outside at the same side. - // Ignore this point. - } - sides_this = sides_next; - } - // For the last point, if src is completely outside bbox, then out.points will be empty. Just use the first point instead. - int sides_next = sides(out.points.empty() ? src.points.front() : out.points.front()); - if (// The last point is inside. Take it. - sides_this == 0 || - // Either this point is outside and previous or next is inside, or - // the edge possibly cuts corner of the bounding box. - (sides_prev & sides_this & sides_next) == 0) - out.points.emplace_back(src.points.back()); -} - -[[nodiscard]] static inline Polygon clip_for_diff(const Polygon &src, const BoundingBox &bbox) -{ - Polygon out; - clip_for_diff(src, bbox, out); - return out; -} - -[[nodiscard]] static inline Polygons clip_for_diff(const Polygons &src, const BoundingBox &bbox) -{ - Polygons out; - out.reserve(src.size()); - for (const Polygon &p : src) - out.emplace_back(clip_for_diff(p, bbox)); - return out; -} - -[[nodiscard]] static inline Polygons diff_clipped(const Polygons &src, const Polygons &clipping) -{ - return diff(src, clip_for_diff(clipping, get_extents(src).inflated(SCALED_EPSILON))); -} - static constexpr const auto tiny_area_threshold = sqr(scaled(0.001)); static std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) @@ -821,7 +751,7 @@ static std::optional> polyline_sample_next_point_at_dis Polygons collision_trimmed_buffer; auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons& { if (collision_trimmed_buffer.empty() && ! collision.empty()) - collision_trimmed_buffer = clip_for_diff(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); + collision_trimmed_buffer = ClipperUtils::clip_clipper_polygons_with_subject_bbox(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); return collision_trimmed_buffer; }; @@ -3798,10 +3728,10 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) { Polygons polys; for (auto& area : move_bounds[layer_idx]) - append(polys, area->influence_area); + append(polys, area.influence_area); if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end()) SVG::export_expolygons(debug_out_path("treesupport-initial_areas-%d.svg", layer_idx), - { { { union_ex(volumes.getWallRestriction(config.getCollisionRadius((*begin)->state), layer_idx, (*begin)->state.use_min_xy_dist)) }, + { { { union_ex(volumes.getWallRestriction(config.getCollisionRadius(begin->state), layer_idx, begin->state.use_min_xy_dist)) }, { "wall_restricrictions", "gray", 0.5f } }, { { union_ex(polys) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 41af2c8579..5610c9f780 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -284,7 +284,7 @@ void TriangleMesh::rotate(float angle, const Axis &axis) case Z: its_rotate_z(this->its, angle); break; default: assert(false); return; } - update_bounding_box(this->its, this->m_stats); + update_bounding_box(this->its, m_stats); } } @@ -295,7 +295,7 @@ void TriangleMesh::rotate(float angle, const Vec3d& axis) Transform3d m = Transform3d::Identity(); m.rotate(Eigen::AngleAxisd(angle, axis_norm)); its_transform(its, m); - update_bounding_box(this->its, this->m_stats); + update_bounding_box(this->its, m_stats); } } @@ -334,7 +334,7 @@ void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) det = -det; } m_stats.volume *= det; - update_bounding_box(this->its, this->m_stats); + update_bounding_box(this->its, m_stats); } void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) @@ -346,7 +346,7 @@ void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) det = -det; } m_stats.volume *= det; - update_bounding_box(this->its, this->m_stats); + update_bounding_box(this->its, m_stats); } void TriangleMesh::flip_triangles() @@ -512,7 +512,7 @@ std::vector TriangleMesh::slice(const std::vector &z) const size_t TriangleMesh::memsize() const { - size_t memsize = 8 + this->its.memsize() + sizeof(this->m_stats); + size_t memsize = 8 + this->its.memsize() + sizeof(m_stats); return memsize; } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 29dff91685..c288b6d3a6 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -92,7 +92,7 @@ public: TriangleMesh(std::vector &&vertices, const std::vector &&faces); explicit TriangleMesh(const indexed_triangle_set &M); explicit TriangleMesh(indexed_triangle_set &&M, const RepairedMeshErrors& repaired_errors = RepairedMeshErrors()); - void clear() { this->its.clear(); this->m_stats.clear(); } + void clear() { this->its.clear(); m_stats.clear(); } bool ReadSTLFile(const char* input_file, bool repair = true); bool write_ascii(const char* output_file); bool write_binary(const char* output_file); diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 820c26acc6..90f77d738f 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -371,7 +371,7 @@ std::pair, std::vector> TriangleSelector::precompute_a { std::vector neighbors(m_triangles.size(), Vec3i(-1, -1, -1)); std::vector neighbors_propagated(m_triangles.size(), Vec3i(-1, -1, -1)); - for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) { + for (int facet_idx = 0; facet_idx < m_orig_size_indices; ++facet_idx) { neighbors[facet_idx] = m_neighbors[facet_idx]; neighbors_propagated[facet_idx] = neighbors[facet_idx]; assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors[facet_idx])); @@ -1480,7 +1480,7 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const std::vector TriangleSelector::get_seed_fill_contour() const { std::vector edges_out; - for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) { + for (int facet_idx = 0; facet_idx < m_orig_size_indices; ++facet_idx) { const Vec3i neighbors = m_neighbors[facet_idx]; assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); this->get_seed_fill_contour_recursive(facet_idx, neighbors, neighbors, edges_out); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 0de638ac88..d5a21cf219 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -33,12 +33,13 @@ #include "Technologies.hpp" #include "Semver.hpp" +using coord_t = #if 1 // Saves around 32% RAM after slicing step, 6.7% after G-code export (tested on PrusaSlicer 2.2.0 final). -using coord_t = int32_t; + int32_t; #else -//FIXME At least FillRectilinear2 and std::boost Voronoi require coord_t to be 32bit. -typedef int64_t coord_t; + //FIXME At least FillRectilinear2 and std::boost Voronoi require coord_t to be 32bit. + int64_t; #endif using coordf_t = double; @@ -381,4 +382,4 @@ inline IntegerOnly fast_round_up(double a) } // namespace Slic3r -#endif +#endif // _libslic3r_h_ diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 31b2c5c90e..99b8d73d55 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1984,7 +1984,7 @@ std::set TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru std::set used_extruders; - auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(print_z)); + auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), print_z, [](const LayerTools &lhs, double rhs){ return lhs.print_z < rhs; }); for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools) { const std::vector& extruders = it_layer_tools->extruders; for (const auto& extruder : extruders) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 12fed0be70..11d1bb80c3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1,8046 +1,8046 @@ -#include "libslic3r/libslic3r.h" -#include "GLCanvas3D.hpp" - -#include - -#include "libslic3r/BuildVolume.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/GCode/ThumbnailData.hpp" -#include "libslic3r/Geometry/ConvexHull.hpp" -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/Layer.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/LocalesUtils.hpp" -#include "libslic3r/Technologies.hpp" -#include "libslic3r/Tesselate.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "3DBed.hpp" -#include "3DScene.hpp" -#include "BackgroundSlicingProcess.hpp" -#include "GLShader.hpp" -#include "GUI.hpp" -#include "Tab.hpp" -#include "GUI_Preview.hpp" -#include "OpenGLManager.hpp" -#include "Plater.hpp" -#include "MainFrame.hpp" -#include "GUI_App.hpp" -#include "GUI_ObjectList.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "Mouse3DController.hpp" -#include "I18N.hpp" -#include "NotificationManager.hpp" -#include "format.hpp" - -#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" -#include "slic3r/Utils/UndoRedo.hpp" - -#if ENABLE_RETINA_GL -#include "slic3r/Utils/RetinaHelper.hpp" -#endif - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx. -#include "libslic3r/Print.hpp" -#include "libslic3r/SLAPrint.hpp" - -#include "wxExtensions.hpp" - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include "DoubleSlider.hpp" - -#include - -static constexpr const float TRACKBALLSIZE = 0.8f; - -#if ENABLE_LEGACY_OPENGL_REMOVAL -static const Slic3r::ColorRGBA DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f, 1.0f }; -static const Slic3r::ColorRGBA DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f, 1.0f }; -static const Slic3r::ColorRGBA ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f, 1.0f }; -static const Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f, 1.0f }; -#else -static const Slic3r::ColorRGB DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f }; -static const Slic3r::ColorRGB DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f }; -static const Slic3r::ColorRGB ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f }; -static const Slic3r::ColorRGB ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -// Number of floats -static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB -// Reserve size in number of floats. -#if !ENABLE_LEGACY_OPENGL_REMOVAL -static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE = 131072 * 2; // 1.05MB -// Reserve size in number of floats, maximum sum of all preallocated buffers. -//static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4; // 128MB -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -namespace Slic3r { -namespace GUI { - -#ifdef __WXGTK3__ -// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support. -RetinaHelper::RetinaHelper(wxWindow* window) : m_window(window), m_self(nullptr) {} -RetinaHelper::~RetinaHelper() {} -float RetinaHelper::get_scale_factor() { return float(m_window->GetContentScaleFactor()); } -#endif // __WXGTK3__ - -// Fixed the collision between BuildVolume::Type::Convex and macro Convex defined inside /usr/include/X11/X.h that is included by WxWidgets 3.0. -#if defined(__linux__) && defined(Convex) -#undef Convex -#endif - -GLCanvas3D::LayersEditing::~LayersEditing() -{ - if (m_z_texture_id != 0) { - glsafe(::glDeleteTextures(1, &m_z_texture_id)); - m_z_texture_id = 0; - } - delete m_slicing_parameters; -} - -const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f; - -void GLCanvas3D::LayersEditing::init() -{ - glsafe(::glGenTextures(1, (GLuint*)&m_z_texture_id)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); - if (!OpenGLManager::get_gl_info().is_core_profile() || !OpenGLManager::get_gl_info().is_mesa()) { - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); - } - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); -} - -void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) -{ - m_config = config; - delete m_slicing_parameters; - m_slicing_parameters = nullptr; - m_layers_texture.valid = false; -} - -void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) -{ - const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr; - // Maximum height of an object changes when the object gets rotated or scaled. - // Changing maximum height of an object will invalidate the layer heigth editing profile. - // m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently. - const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast(model_object_new->bounding_box().max.z()); - if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || - (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { - m_layer_height_profile.clear(); - m_layer_height_profile_modified = false; - delete m_slicing_parameters; - m_slicing_parameters = nullptr; - m_layers_texture.valid = false; - this->last_object_id = object_id; - m_model_object = model_object_new; - m_object_max_z = new_max_z; - } -} - -bool GLCanvas3D::LayersEditing::is_allowed() const -{ - return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0; -} - -bool GLCanvas3D::LayersEditing::is_enabled() const -{ - return m_enabled; -} - -void GLCanvas3D::LayersEditing::set_enabled(bool enabled) -{ - m_enabled = is_allowed() && enabled; -} - -float GLCanvas3D::LayersEditing::s_overlay_window_width; - -void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) -{ - if (!m_enabled) - return; - - const Size& cnv_size = canvas.get_canvas_size(); - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.set_next_window_pos(static_cast(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, - static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); - - imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Left mouse button:")); - ImGui::SameLine(); - imgui.text(_L("Add detail")); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Right mouse button:")); - ImGui::SameLine(); - imgui.text(_L("Remove detail")); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Left mouse button:")); - ImGui::SameLine(); - imgui.text(_L("Reset to base")); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Right mouse button:")); - ImGui::SameLine(); - imgui.text(_L("Smoothing")); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mouse wheel:")); - ImGui::SameLine(); - imgui.text(_L("Increase/decrease edit area")); - - ImGui::Separator(); - if (imgui.button(_L("Adaptive"))) - wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality)); - - ImGui::SameLine(); - float text_align = ImGui::GetCursorPosX(); - ImGui::AlignTextToFramePadding(); - imgui.text(_L("Quality / Speed")); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8()); - ImGui::EndTooltip(); - } - - ImGui::SameLine(); - float widget_align = ImGui::GetCursorPosX(); - ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); - m_adaptive_quality = std::clamp(m_adaptive_quality, 0.0f, 1.f); - imgui.slider_float("", &m_adaptive_quality, 0.0f, 1.f, "%.2f"); - - ImGui::Separator(); - if (imgui.button(_L("Smooth"))) - wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params)); - - ImGui::SameLine(); - ImGui::SetCursorPosX(text_align); - ImGui::AlignTextToFramePadding(); - imgui.text(_L("Radius")); - ImGui::SameLine(); - ImGui::SetCursorPosX(widget_align); - ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); - int radius = (int)m_smooth_params.radius; - if (ImGui::SliderInt("##1", &radius, 1, 10)) { - radius = std::clamp(radius, 1, 10); - m_smooth_params.radius = (unsigned int)radius; - } - - ImGui::SetCursorPosX(text_align); - ImGui::AlignTextToFramePadding(); - imgui.text(_L("Keep min")); - ImGui::SameLine(); - if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization - ImGui::SetCursorPosX(widget_align); - - ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); - imgui.checkbox("##2", m_smooth_params.keep_min); - - ImGui::Separator(); - if (imgui.button(_L("Reset"))) - wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); - - GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/; - imgui.end(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - render_active_object_annotations(canvas); - render_profile(canvas); -#else - const Rect& bar_rect = get_bar_rect_viewport(canvas); - render_active_object_annotations(canvas, bar_rect); - render_profile(bar_rect); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) -{ - const Vec2d mouse_pos = canvas.get_local_mouse_position(); - const Rect& rect = get_bar_rect_screen(canvas); - float x = (float)mouse_pos.x(); - float y = (float)mouse_pos.y(); - float t = rect.get_top(); - float b = rect.get_bottom(); - - return (rect.get_left() <= x && x <= rect.get_right() && t <= y && y <= b) ? - // Inside the bar. - (b - y - 1.0f) / (b - t - 1.0f) : - // Outside the bar. - -1000.0f; -} - -bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y) -{ - const Rect& rect = get_bar_rect_screen(canvas); - return rect.get_left() <= x && x <= rect.get_right() && rect.get_top() <= y && y <= rect.get_bottom(); -} - -Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) -{ - const Size& cnv_size = canvas.get_canvas_size(); - float w = (float)cnv_size.get_width(); - float h = (float)cnv_size.get_height(); - - return { w - thickness_bar_width(canvas), 0.0f, w, h }; -} - -#if !ENABLE_LEGACY_OPENGL_REMOVAL -Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) -{ - const Size& cnv_size = canvas.get_canvas_size(); - float half_w = 0.5f * (float)cnv_size.get_width(); - float half_h = 0.5f * (float)cnv_size.get_height(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - return { (half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom }; -} -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -bool GLCanvas3D::LayersEditing::is_initialized() const -{ - return wxGetApp().get_shader("variable_layer_height") != nullptr; -} - -std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const -{ - std::string ret; - if (m_enabled && m_layer_height_profile.size() >= 4) { - float z = get_cursor_z_relative(canvas); - if (z != -1000.0f) { - z *= m_object_max_z; - - float h = 0.0f; - for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2) { - const float zi = static_cast(m_layer_height_profile[i]); - const float zi_1 = static_cast(m_layer_height_profile[i - 2]); - if (zi_1 <= z && z <= zi) { - float dz = zi - zi_1; - h = (dz != 0.0f) ? static_cast(lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz)) : - static_cast(m_layer_height_profile[i + 1]); - break; - } - } - if (h > 0.0f) - ret = std::to_string(h); - } - } - return ret; -} - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas) -{ - const Size cnv_size = canvas.get_canvas_size(); - const float cnv_width = (float)cnv_size.get_width(); - const float cnv_height = (float)cnv_size.get_height(); - if (cnv_width == 0.0f || cnv_height == 0.0f) - return; - - const float cnv_inv_width = 1.0f / cnv_width; -#else -void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) -{ -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); - if (shader == nullptr) - return; - - shader->start_using(); - - shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z)); - shader->set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height); - shader->set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); - shader->set_uniform("z_cursor_band_width", band_width); - shader->set_uniform("object_max_z", m_object_max_z); -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->set_uniform("view_model_matrix", Transform3d::Identity()); - shader->set_uniform("projection_matrix", Transform3d::Identity()); - shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); - - // Render the color bar -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!m_profile.background.is_initialized() || m_profile.old_canvas_width != cnv_width) { - m_profile.old_canvas_width = cnv_width; - m_profile.background.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3T2 }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - const float l = 1.0f - 2.0f * THICKNESS_BAR_WIDTH * cnv_inv_width; - const float r = 1.0f; - const float t = 1.0f; - const float b = -1.0f; - init_data.add_vertex(Vec3f(l, b, 0.0f), Vec3f::UnitZ(), Vec2f(0.0f, 0.0f)); - init_data.add_vertex(Vec3f(r, b, 0.0f), Vec3f::UnitZ(), Vec2f(1.0f, 0.0f)); - init_data.add_vertex(Vec3f(r, t, 0.0f), Vec3f::UnitZ(), Vec2f(1.0f, 1.0f)); - init_data.add_vertex(Vec3f(l, t, 0.0f), Vec3f::UnitZ(), Vec2f(0.0f, 1.0f)); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_profile.background.init_from(std::move(init_data)); - } - - m_profile.background.render(); -#else - const float l = bar_rect.get_left(); - const float r = bar_rect.get_right(); - const float t = bar_rect.get_top(); - const float b = bar_rect.get_bottom(); - - ::glBegin(GL_QUADS); - ::glNormal3f(0.0f, 0.0f, 1.0f); - ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(l, b); - ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(r, b); - ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t); - ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - - shader->stop_using(); -} - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void GLCanvas3D::LayersEditing::render_profile(const GLCanvas3D& canvas) -#else -void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -{ - //FIXME show some kind of legend. - - if (!m_slicing_parameters) - return; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Size cnv_size = canvas.get_canvas_size(); - const float cnv_width = (float)cnv_size.get_width(); - const float cnv_height = (float)cnv_size.get_height(); - if (cnv_width == 0.0f || cnv_height == 0.0f) - return; - - // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - const float scale_x = THICKNESS_BAR_WIDTH / float(1.12 * m_slicing_parameters->max_layer_height); - const float scale_y = cnv_height / m_object_max_z; - - const float cnv_inv_width = 1.0f / cnv_width; - const float cnv_inv_height = 1.0f / cnv_height; -#else - // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - const float scale_x = bar_rect.get_width() / float(1.12 * m_slicing_parameters->max_layer_height); - const float scale_y = bar_rect.get_height() / m_object_max_z; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - // Baseline - if (!m_profile.baseline.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) { - m_profile.baseline.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2 }; - init_data.color = ColorRGBA::BLACK(); - init_data.reserve_vertices(2); - init_data.reserve_indices(2); - - // vertices - const float axis_x = 2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_slicing_parameters->layer_height) * scale_x) * cnv_inv_width - 0.5f); - init_data.add_vertex(Vec2f(axis_x, -1.0f)); - init_data.add_vertex(Vec2f(axis_x, 1.0f)); - - // indices - init_data.add_line(0, 1); - - m_profile.baseline.init_from(std::move(init_data)); - } - - if (!m_profile.profile.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) { - m_profile.old_layer_height_profile = m_layer_height_profile; - m_profile.profile.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2 }; - init_data.color = ColorRGBA::BLUE(); - init_data.reserve_vertices(m_layer_height_profile.size() / 2); - init_data.reserve_indices(m_layer_height_profile.size() / 2); - - // vertices + indices - for (unsigned int i = 0; i < (unsigned int)m_layer_height_profile.size(); i += 2) { - init_data.add_vertex(Vec2f(2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_layer_height_profile[i + 1]) * scale_x) * cnv_inv_width - 0.5f), - 2.0f * (float(m_layer_height_profile[i]) * scale_y * cnv_inv_height - 0.5))); - init_data.add_index(i / 2); - } - - m_profile.profile.init_from(std::move(init_data)); - } - -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); -#else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE - if (shader != nullptr) { - shader->start_using(); - shader->set_uniform("view_model_matrix", Transform3d::Identity()); - shader->set_uniform("projection_matrix", Transform3d::Identity()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = wxGetApp().plater()->get_camera().get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE - m_profile.baseline.render(); - m_profile.profile.render(); - shader->stop_using(); - } -#else - const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x; - - // Baseline - glsafe(::glColor3f(0.0f, 0.0f, 0.0f)); - ::glBegin(GL_LINE_STRIP); - ::glVertex2f(x, bar_rect.get_bottom()); - ::glVertex2f(x, bar_rect.get_top()); - glsafe(::glEnd()); - - // Curve - glsafe(::glColor3f(0.0f, 0.0f, 1.0f)); - ::glBegin(GL_LINE_STRIP); - for (unsigned int i = 0; i < m_layer_height_profile.size(); i += 2) - ::glVertex2f(bar_rect.get_left() + (float)m_layer_height_profile[i + 1] * scale_x, bar_rect.get_bottom() + (float)m_layer_height_profile[i] * scale_y); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes) -{ - assert(this->is_allowed()); - assert(this->last_object_id != -1); - - GLShaderProgram* current_shader = wxGetApp().get_current_shader(); - ScopeGuard guard([current_shader]() { if (current_shader != nullptr) current_shader->start_using(); }); - if (current_shader != nullptr) - current_shader->stop_using(); - - GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); - if (shader == nullptr) - return; - - shader->start_using(); - - generate_layer_height_texture(); - - // Uniforms were resolved, go ahead using the layer editing shader. - shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * float(m_object_max_z))); - shader->set_uniform("z_texture_row_to_normalized", 1.0f / float(m_layers_texture.height)); - shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas))); - shader->set_uniform("z_cursor_band_width", float(this->band_width)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // Initialize the layer height texture mapping. - const GLsizei w = (GLsizei)m_layers_texture.width; - const GLsizei h = (GLsizei)m_layers_texture.height; - const GLsizei half_w = w / 2; - const GLsizei half_h = h / 2; - glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); - glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); - for (GLVolume* glvolume : volumes.volumes) { - // Render the object using the layer editing shader and texture. - if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) - continue; - - shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); - shader->set_uniform("object_max_z", 0.0f); -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Transform3d& view_matrix = camera.get_view_matrix(); - const Transform3d model_matrix = glvolume->world_matrix(); - shader->set_uniform("view_model_matrix", view_matrix * model_matrix); - const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); - shader->set_uniform("view_normal_matrix", view_normal_matrix); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glvolume->render(); - } - // Revert back to the previous shader. - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); -} - -void GLCanvas3D::LayersEditing::adjust_layer_height_profile() -{ - this->update_slicing_parameters(); - PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile); - Slic3r::adjust_layer_height_profile(*m_slicing_parameters, m_layer_height_profile, this->last_z, this->strength, this->band_width, this->last_action); - m_layer_height_profile_modified = true; - m_layers_texture.valid = false; -} - -void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas) -{ - const_cast(m_model_object)->layer_height_profile.clear(); - m_layer_height_profile.clear(); - m_layers_texture.valid = false; - canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().obj_list()->update_info_items(last_object_id); -} - -void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor) -{ - this->update_slicing_parameters(); - m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, quality_factor); - const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); - m_layers_texture.valid = false; - canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().obj_list()->update_info_items(last_object_id); -} - -void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params) -{ - this->update_slicing_parameters(); - m_layer_height_profile = smooth_height_profile(m_layer_height_profile, *m_slicing_parameters, smoothing_params); - const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); - m_layers_texture.valid = false; - canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().obj_list()->update_info_items(last_object_id); -} - -void GLCanvas3D::LayersEditing::generate_layer_height_texture() -{ - this->update_slicing_parameters(); - // Always try to update the layer height profile. - bool update = ! m_layers_texture.valid; - if (PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile)) { - // Initialized to the default value. - m_layer_height_profile_modified = false; - update = true; - } - // Update if the layer height profile was changed, or when the texture is not valid. - if (! update && ! m_layers_texture.data.empty() && m_layers_texture.cells > 0) - // Texture is valid, don't update. - return; - - if (m_layers_texture.data.empty()) { - m_layers_texture.width = 1024; - m_layers_texture.height = 1024; - m_layers_texture.levels = 2; - m_layers_texture.data.assign(m_layers_texture.width * m_layers_texture.height * 5, 0); - } - - bool level_of_detail_2nd_level = true; - m_layers_texture.cells = Slic3r::generate_layer_height_texture( - *m_slicing_parameters, - Slic3r::generate_object_layers(*m_slicing_parameters, m_layer_height_profile), - m_layers_texture.data.data(), m_layers_texture.height, m_layers_texture.width, level_of_detail_2nd_level); - m_layers_texture.valid = true; -} - -void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) -{ - if (last_object_id >= 0) { - if (m_layer_height_profile_modified) { - wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit")); - const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); - canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().obj_list()->update_info_items(last_object_id); - } - } - m_layer_height_profile_modified = false; -} - -void GLCanvas3D::LayersEditing::update_slicing_parameters() -{ - if (m_slicing_parameters == nullptr) { - m_slicing_parameters = new SlicingParameters(); - *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); - } -} - -float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas) -{ - return -#if ENABLE_RETINA_GL - canvas.get_canvas_size().get_scale_factor() -#else - canvas.get_wxglcanvas()->GetContentScaleFactor() -#endif - * THICKNESS_BAR_WIDTH; -} - - -const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); -const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); -const int GLCanvas3D::Mouse::Drag::MoveThresholdPx = 5; - -GLCanvas3D::Mouse::Drag::Drag() - : start_position_2D(Invalid_2D_Point) - , start_position_3D(Invalid_3D_Point) - , move_volume_idx(-1) - , move_requires_threshold(false) - , move_start_threshold_position_2D(Invalid_2D_Point) -{ -} - -GLCanvas3D::Mouse::Mouse() - : dragging(false) - , position(DBL_MAX, DBL_MAX) - , scene_position(DBL_MAX, DBL_MAX, DBL_MAX) - , ignore_left_up(false) -{ -} - -void GLCanvas3D::Labels::render(const std::vector& sorted_instances) const -{ - if (!m_enabled || !is_shown()) - return; - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Model* model = m_canvas.get_model(); - if (model == nullptr) - return; - - Transform3d world_to_eye = camera.get_view_matrix(); - Transform3d world_to_screen = camera.get_projection_matrix() * world_to_eye; - const std::array& viewport = camera.get_viewport(); - - struct Owner - { - int obj_idx; - int inst_idx; - size_t model_instance_id; - BoundingBoxf3 world_box; - double eye_center_z; - std::string title; - std::string label; - std::string print_order; - bool selected; - }; - - // collect owners world bounding boxes and data from volumes - std::vector owners; - const GLVolumeCollection& volumes = m_canvas.get_volumes(); - for (const GLVolume* volume : volumes.volumes) { - int obj_idx = volume->object_idx(); - if (0 <= obj_idx && obj_idx < (int)model->objects.size()) { - int inst_idx = volume->instance_idx(); - std::vector::iterator it = std::find_if(owners.begin(), owners.end(), [obj_idx, inst_idx](const Owner& owner) { - return (owner.obj_idx == obj_idx) && (owner.inst_idx == inst_idx); - }); - if (it != owners.end()) { - it->world_box.merge(volume->transformed_bounding_box()); - it->selected &= volume->selected; - } else { - const ModelObject* model_object = model->objects[obj_idx]; - Owner owner; - owner.obj_idx = obj_idx; - owner.inst_idx = inst_idx; - owner.model_instance_id = model_object->instances[inst_idx]->id().id; - owner.world_box = volume->transformed_bounding_box(); - owner.title = "object" + std::to_string(obj_idx) + "_inst##" + std::to_string(inst_idx); - owner.label = model_object->name; - if (model_object->instances.size() > 1) - owner.label += " (" + std::to_string(inst_idx + 1) + ")"; - owner.selected = volume->selected; - owners.emplace_back(owner); - } - } - } - - // updates print order strings - if (sorted_instances.size() > 1) { - for (size_t i = 0; i < sorted_instances.size(); ++i) { - size_t id = sorted_instances[i]->id().id; - std::vector::iterator it = std::find_if(owners.begin(), owners.end(), [id](const Owner& owner) { - return owner.model_instance_id == id; - }); - if (it != owners.end()) - it->print_order = std::string((_(L("Seq."))).ToUTF8()) + "#: " + std::to_string(i + 1); - } - } - - // calculate eye bounding boxes center zs - for (Owner& owner : owners) { - owner.eye_center_z = (world_to_eye * owner.world_box.center())(2); - } - - // sort owners by center eye zs and selection - std::sort(owners.begin(), owners.end(), [](const Owner& owner1, const Owner& owner2) { - if (!owner1.selected && owner2.selected) - return true; - else if (owner1.selected && !owner2.selected) - return false; - else - return (owner1.eye_center_z < owner2.eye_center_z); - }); - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - - // render info windows - for (const Owner& owner : owners) { - Vec3d screen_box_center = world_to_screen * owner.world_box.center(); - float x = 0.0f; - float y = 0.0f; - if (camera.get_type() == Camera::EType::Perspective) { - x = (0.5f + 0.001f * 0.5f * (float)screen_box_center(0)) * viewport[2]; - y = (0.5f - 0.001f * 0.5f * (float)screen_box_center(1)) * viewport[3]; - } else { - x = (0.5f + 0.5f * (float)screen_box_center(0)) * viewport[2]; - y = (0.5f - 0.5f * (float)screen_box_center(1)) * viewport[3]; - } - - if (x < 0.0f || viewport[2] < x || y < 0.0f || viewport[3] < y) - continue; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, owner.selected ? 3.0f : 1.5f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleColor(ImGuiCol_Border, owner.selected ? ImVec4(0.757f, 0.404f, 0.216f, 1.0f) : ImVec4(0.75f, 0.75f, 0.75f, 1.0f)); - imgui.set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.5f); - imgui.begin(owner.title, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - float win_w = ImGui::GetWindowWidth(); - float label_len = ImGui::CalcTextSize(owner.label.c_str()).x; - ImGui::SetCursorPosX(0.5f * (win_w - label_len)); - ImGui::AlignTextToFramePadding(); - imgui.text(owner.label); - - if (!owner.print_order.empty()) { - ImGui::Separator(); - float po_len = ImGui::CalcTextSize(owner.print_order.c_str()).x; - ImGui::SetCursorPosX(0.5f * (win_w - po_len)); - ImGui::AlignTextToFramePadding(); - imgui.text(owner.print_order); - } - - // force re-render while the windows gets to its final size (it takes several frames) - if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) - imgui.set_requires_extra_frame(); - - imgui.end(); - ImGui::PopStyleColor(); - ImGui::PopStyleVar(2); - } -} - -void GLCanvas3D::Tooltip::set_text(const std::string& text) -{ - // If the mouse is inside an ImGUI dialog, then the tooltip is suppressed. - m_text = m_in_imgui ? std::string() : text; -} - -void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas) -{ - static ImVec2 size(0.0f, 0.0f); - - auto validate_position = [](const Vec2d& position, const GLCanvas3D& canvas, const ImVec2& wnd_size) { - auto calc_cursor_height = []() { - float ret = 16.0f; -#ifdef _WIN32 - // see: https://forums.codeguru.com/showthread.php?449040-get-the-system-current-cursor-size - // this code is not perfect because it returns a maximum height equal to 31 even if the cursor bitmap shown on screen is bigger - // but at least it gives the same result as wxWidgets in the settings tabs - ICONINFO ii; - if (::GetIconInfo((HICON)GetCursor(), &ii) != 0) { - BITMAP bitmap; - ::GetObject(ii.hbmMask, sizeof(BITMAP), &bitmap); - int width = bitmap.bmWidth; - int height = (ii.hbmColor == nullptr) ? bitmap.bmHeight / 2 : bitmap.bmHeight; - HDC dc = ::CreateCompatibleDC(nullptr); - if (dc != nullptr) { - if (::SelectObject(dc, ii.hbmMask) != nullptr) { - for (int i = 0; i < width; ++i) { - for (int j = 0; j < height; ++j) { - if (::GetPixel(dc, i, j) != RGB(255, 255, 255)) { - if (ret < float(j)) - ret = float(j); - } - } - } - ::DeleteDC(dc); - } - } - ::DeleteObject(ii.hbmColor); - ::DeleteObject(ii.hbmMask); - } -#endif // _WIN32 - return ret; - }; - - const Size cnv_size = canvas.get_canvas_size(); - const float x = std::clamp(float(position.x()), 0.0f, float(cnv_size.get_width()) - wnd_size.x); - const float y = std::clamp(float(position.y()) + calc_cursor_height(), 0.0f, float(cnv_size.get_height()) - wnd_size.y); - return Vec2f(x, y); - }; - - if (m_text.empty()) { - m_start_time = std::chrono::steady_clock::now(); - return; - } - - // draw the tooltip as hidden until the delay is expired - // use a value of alpha slightly different from 0.0f because newer imgui does not calculate properly the window size if alpha == 0.0f - const float alpha = (std::chrono::duration_cast(std::chrono::steady_clock::now() - m_start_time).count() < 500) ? 0.01f : 1.0f; - - const Vec2f position = validate_position(mouse_position, canvas, size); - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); - imgui.set_next_window_pos(position.x(), position.y(), ImGuiCond_Always, 0.0f, 0.0f); - - imgui.begin(wxString("canvas_tooltip"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing); - ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - ImGui::TextUnformatted(m_text.c_str()); - - // force re-render while the windows gets to its final size (it may take several frames) or while hidden - if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) - imgui.set_requires_extra_frame(); - - size = ImGui::GetWindowSize(); - - imgui.end(); - ImGui::PopStyleVar(2); -} - -void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons) -{ - m_perimeter.reset(); - m_fill.reset(); - if (polygons.empty()) - return; - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - size_t triangles_count = 0; - for (const Polygon& poly : polygons) { - triangles_count += poly.points.size() - 2; - } - const size_t vertices_count = 3 * triangles_count; -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - if (m_render_fill) { - GLModel::Geometry fill_data; -#if ENABLE_LEGACY_OPENGL_REMOVAL - fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - fill_data.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; - - // vertices + indices - const ExPolygons polygons_union = union_ex(polygons); - unsigned int vertices_counter = 0; - for (const ExPolygon& poly : polygons_union) { - const std::vector triangulation = triangulate_expolygon_3d(poly); - fill_data.reserve_vertices(fill_data.vertices_count() + triangulation.size()); - fill_data.reserve_indices(fill_data.indices_count() + triangulation.size()); - for (const Vec3d& v : triangulation) { - fill_data.add_vertex((Vec3f)(v.cast() + 0.0125f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting - ++vertices_counter; - if (vertices_counter % 3 == 0) - fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); - } - } - - m_fill.init_from(std::move(fill_data)); -#else - GLModel::Geometry::Entity entity; - entity.type = GLModel::EPrimitiveType::Triangles; - entity.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; - entity.positions.reserve(vertices_count); - entity.normals.reserve(vertices_count); - entity.indices.reserve(vertices_count); - - const ExPolygons polygons_union = union_ex(polygons); - for (const ExPolygon& poly : polygons_union) { - const std::vector triangulation = triangulate_expolygon_3d(poly); - for (const Vec3d& v : triangulation) { - entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.0125f)); // add a small positive z to avoid z-fighting - entity.normals.emplace_back(Vec3f::UnitZ()); - const size_t positions_count = entity.positions.size(); - if (positions_count % 3 == 0) { - entity.indices.emplace_back(positions_count - 3); - entity.indices.emplace_back(positions_count - 2); - entity.indices.emplace_back(positions_count - 1); - } - } - } - - fill_data.entities.emplace_back(entity); - m_fill.init_from(fill_data); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_perimeter.init_from(polygons, 0.025f); // add a small positive z to avoid z-fighting -#else - GLModel::Geometry perimeter_data; - for (const Polygon& poly : polygons) { - GLModel::Geometry::Entity ent; - ent.type = GLModel::EPrimitiveType::LineLoop; - ent.positions.reserve(poly.points.size()); - ent.indices.reserve(poly.points.size()); - unsigned int id_count = 0; - for (const Point& p : poly.points) { - ent.positions.emplace_back(unscale(p.x()), unscale(p.y()), 0.025f); // add a small positive z to avoid z-fighting - ent.normals.emplace_back(Vec3f::UnitZ()); - ent.indices.emplace_back(id_count++); - } - - perimeter_data.entities.emplace_back(ent); - } - - m_perimeter.init_from(perimeter_data); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLCanvas3D::SequentialPrintClearance::render() -{ - const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; - const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#else - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (shader == nullptr) - return; - - shader->start_using(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); -#else - m_perimeter.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_perimeter.render(); - m_fill.render(); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_DEPTH_TEST)); - - shader->stop_using(); -} - -wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); -wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); -#if ENABLE_WORLD_COORDINATE -wxDEFINE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); -#endif // ENABLE_WORLD_COORDINATE -wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); -wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); -wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_COLLAPSE_SIDEBAR, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); -wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RENDER_TIMER, wxTimerEvent/*RenderTimerEvent*/); -wxDEFINE_EVENT(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, wxTimerEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, wxTimerEvent); - -const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; - -void GLCanvas3D::load_arrange_settings() -{ - std::string dist_fff_str = - wxGetApp().app_config->get("arrange", "min_object_distance_fff"); - - std::string dist_bed_fff_str = - wxGetApp().app_config->get("arrange", "min_bed_distance_fff"); - - std::string dist_fff_seq_print_str = - wxGetApp().app_config->get("arrange", "min_object_distance_fff_seq_print"); - - std::string dist_bed_fff_seq_print_str = - wxGetApp().app_config->get("arrange", "min_bed_distance_fff_seq_print"); - - std::string dist_sla_str = - wxGetApp().app_config->get("arrange", "min_object_distance_sla"); - - std::string dist_bed_sla_str = - wxGetApp().app_config->get("arrange", "min_bed_distance_sla"); - - std::string en_rot_fff_str = - wxGetApp().app_config->get("arrange", "enable_rotation_fff"); - - std::string en_rot_fff_seqp_str = - wxGetApp().app_config->get("arrange", "enable_rotation_fff_seq_print"); - - std::string en_rot_sla_str = - wxGetApp().app_config->get("arrange", "enable_rotation_sla"); - - if (!dist_fff_str.empty()) - m_arrange_settings_fff.distance = string_to_float_decimal_point(dist_fff_str); - - if (!dist_bed_fff_str.empty()) - m_arrange_settings_fff.distance_from_bed = string_to_float_decimal_point(dist_bed_fff_str); - - if (!dist_fff_seq_print_str.empty()) - m_arrange_settings_fff_seq_print.distance = string_to_float_decimal_point(dist_fff_seq_print_str); - - if (!dist_bed_fff_seq_print_str.empty()) - m_arrange_settings_fff_seq_print.distance_from_bed = string_to_float_decimal_point(dist_bed_fff_seq_print_str); - - if (!dist_sla_str.empty()) - m_arrange_settings_sla.distance = string_to_float_decimal_point(dist_sla_str); - - if (!dist_bed_sla_str.empty()) - m_arrange_settings_sla.distance_from_bed = string_to_float_decimal_point(dist_bed_sla_str); - - if (!en_rot_fff_str.empty()) - m_arrange_settings_fff.enable_rotation = (en_rot_fff_str == "1" || en_rot_fff_str == "yes"); - - if (!en_rot_fff_seqp_str.empty()) - m_arrange_settings_fff_seq_print.enable_rotation = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes"); - - if (!en_rot_sla_str.empty()) - m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); -} - -PrinterTechnology GLCanvas3D::current_printer_technology() const -{ - return m_process->current_printer_technology(); -} - -GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) - : m_canvas(canvas) - , m_context(nullptr) - , m_bed(bed) -#if ENABLE_RETINA_GL - , m_retina_helper(nullptr) -#endif - , m_in_render(false) - , m_main_toolbar(GLToolbar::Normal, "Main") - , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") - , m_gizmos(*this) - , m_use_clipping_planes(false) - , m_sidebar_field("") - , m_extra_frame_requested(false) - , m_config(nullptr) - , m_process(nullptr) - , m_model(nullptr) - , m_dirty(true) - , m_initialized(false) - , m_apply_zoom_to_volumes_filter(false) - , m_picking_enabled(false) - , m_moving_enabled(false) - , m_dynamic_background_enabled(false) - , m_multisample_allowed(false) - , m_moving(false) - , m_tab_down(false) - , m_cursor_type(Standard) - , m_reload_delayed(false) - , m_render_sla_auxiliaries(true) - , m_labels(*this) - , m_slope(m_volumes) -{ - if (m_canvas != nullptr) { - m_timer.SetOwner(m_canvas); - m_render_timer.SetOwner(m_canvas); -#if ENABLE_RETINA_GL - m_retina_helper.reset(new RetinaHelper(canvas)); -#endif // ENABLE_RETINA_GL - } - - load_arrange_settings(); - - m_selection.set_volumes(&m_volumes.volumes); -} - -GLCanvas3D::~GLCanvas3D() -{ - reset_volumes(); -} - -void GLCanvas3D::post_event(wxEvent &&event) -{ - event.SetEventObject(m_canvas); - wxPostEvent(m_canvas, event); -} - -bool GLCanvas3D::init() -{ - if (m_initialized) - return true; - - if (m_canvas == nullptr || m_context == nullptr) - return false; - - glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); -#if ENABLE_OPENGL_ES - glsafe(::glClearDepthf(1.0f)); -#else - glsafe(::glClearDepth(1.0f)); -#endif // ENABLE_OPENGL_ES - - glsafe(::glDepthFunc(GL_LESS)); - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - // Set antialiasing / multisampling - glsafe(::glDisable(GL_LINE_SMOOTH)); - glsafe(::glDisable(GL_POLYGON_SMOOTH)); - - // ambient lighting - GLfloat ambient[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; - glsafe(::glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient)); - - glsafe(::glEnable(GL_LIGHT0)); - glsafe(::glEnable(GL_LIGHT1)); - - // light from camera - GLfloat specular_cam[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT1, GL_SPECULAR, specular_cam)); - GLfloat diffuse_cam[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_cam)); - - // light from above - GLfloat specular_top[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT0, GL_SPECULAR, specular_top)); - GLfloat diffuse_top[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_top)); - - // Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. - glsafe(::glShadeModel(GL_SMOOTH)); - - // A handy trick -- have surface material mirror the color. - glsafe(::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)); - glsafe(::glEnable(GL_COLOR_MATERIAL)); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - if (m_multisample_allowed) - glsafe(::glEnable(GL_MULTISAMPLE)); - - if (m_main_toolbar.is_enabled()) - m_layers_editing.init(); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - // on linux the gl context is not valid until the canvas is not shown on screen - // we defer the geometry finalization of volumes until the first call to render() - m_volumes.finalize_geometry(true); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - if (m_gizmos.is_enabled() && !m_gizmos.init()) - std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; - - if (!_init_toolbars()) - return false; - - if (m_selection.is_enabled() && !m_selection.init()) - return false; - - m_initialized = true; - - return true; -} - -void GLCanvas3D::set_as_dirty() -{ - m_dirty = true; -} - -unsigned int GLCanvas3D::get_volumes_count() const -{ - return (unsigned int)m_volumes.volumes.size(); -} - -void GLCanvas3D::reset_volumes() -{ - if (!m_initialized) - return; - - if (m_volumes.empty()) - return; - - _set_current(); - - m_selection.clear(); - m_volumes.clear(); - m_dirty = true; - - _set_warning_notification(EWarning::ObjectOutside, false); -} - -ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const -{ - ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside; - if (m_initialized) - m_volumes.check_outside_state(m_bed.build_volume(), &state); - return state; -} - -void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx) -{ - if (current_printer_technology() != ptSLA) - return; - - m_render_sla_auxiliaries = visible; - - for (GLVolume* vol : m_volumes.volumes) { - if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) - && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) - && vol->composite_id.volume_id < 0) - vol->is_active = visible; - } -} - -void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv) -{ -#if ENABLE_RAYCAST_PICKING - std::vector>* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Volume); -#endif // ENABLE_RAYCAST_PICKING - for (GLVolume* vol : m_volumes.volumes) { - if (vol->is_wipe_tower) - vol->is_active = (visible && mo == nullptr); - else { - if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) - && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) - && (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) { - vol->is_active = visible; - if (!vol->is_modifier) - vol->color.a(1.f); - - if (instance_idx == -1) { - vol->force_native_color = false; - vol->force_neutral_color = false; - } else { - const GLGizmosManager& gm = get_gizmos_manager(); - auto gizmo_type = gm.get_current_type(); - if ( (gizmo_type == GLGizmosManager::FdmSupports - || gizmo_type == GLGizmosManager::Seam - || gizmo_type == GLGizmosManager::Cut) - && !vol->is_modifier) { - vol->force_neutral_color = true; - if (gizmo_type == GLGizmosManager::Cut) - vol->color.a(0.95f); - } - else if (gizmo_type == GLGizmosManager::MmuSegmentation) - vol->is_active = false; - else - vol->force_native_color = true; - } - } - } -#if ENABLE_RAYCAST_PICKING - auto it = std::find_if(raycasters->begin(), raycasters->end(), [vol](std::shared_ptr item) { return item->get_raycaster() == vol->mesh_raycaster.get(); }); - if (it != raycasters->end()) - (*it)->set_active(vol->is_active); -#endif // ENABLE_RAYCAST_PICKING - } - - if (visible && !mo) - toggle_sla_auxiliaries_visibility(true, mo, instance_idx); - - if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1)) - _set_warning_notification(EWarning::SomethingNotShown, true); - - if (!mo && visible) - _set_warning_notification(EWarning::SomethingNotShown, false); -} - -void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx) -{ - ModelObject* model_object = m_model->objects[obj_idx]; - for (int inst_idx = 0; inst_idx < (int)model_object->instances.size(); ++inst_idx) { - ModelInstance* instance = model_object->instances[inst_idx]; - - for (GLVolume* volume : m_volumes.volumes) { - if (volume->object_idx() == (int)obj_idx && volume->instance_idx() == inst_idx) - volume->printable = instance->printable; - } - } -} - -void GLCanvas3D::update_instance_printable_state_for_objects(const std::vector& object_idxs) -{ - for (size_t obj_idx : object_idxs) - update_instance_printable_state_for_object(obj_idx); -} - -void GLCanvas3D::set_config(const DynamicPrintConfig* config) -{ - m_config = config; - m_layers_editing.set_config(config); -} - -void GLCanvas3D::set_process(BackgroundSlicingProcess *process) -{ - m_process = process; -} - -void GLCanvas3D::set_model(Model* model) -{ - m_model = model; - m_selection.set_model(m_model); -} - -void GLCanvas3D::bed_shape_changed() -{ - refresh_camera_scene_box(); - wxGetApp().plater()->get_camera().requires_zoom_to_bed = true; - m_dirty = true; -} - -void GLCanvas3D::refresh_camera_scene_box() -{ - wxGetApp().plater()->get_camera().set_scene_box(scene_bounding_box()); -} - -BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const -{ - BoundingBoxf3 bb; - for (const GLVolume* volume : m_volumes.volumes) { - if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes)) - bb.merge(volume->transformed_bounding_box()); - } - return bb; -} - -BoundingBoxf3 GLCanvas3D::scene_bounding_box() const -{ - BoundingBoxf3 bb = volumes_bounding_box(); - bb.merge(m_bed.extended_bounding_box()); - double h = m_bed.build_volume().max_print_height(); - //FIXME why -h? - bb.min.z() = std::min(bb.min.z(), -h); - bb.max.z() = std::max(bb.max.z(), h); - return bb; -} - -bool GLCanvas3D::is_layers_editing_enabled() const -{ - return m_layers_editing.is_enabled(); -} - -bool GLCanvas3D::is_layers_editing_allowed() const -{ - return m_layers_editing.is_allowed(); -} - -void GLCanvas3D::reset_layer_height_profile() -{ - wxGetApp().plater()->take_snapshot(_L("Variable layer height - Reset")); - m_layers_editing.reset_layer_height_profile(*this); - m_layers_editing.state = LayersEditing::Completed; - m_dirty = true; -} - -void GLCanvas3D::adaptive_layer_height_profile(float quality_factor) -{ - wxGetApp().plater()->take_snapshot(_L("Variable layer height - Adaptive")); - m_layers_editing.adaptive_layer_height_profile(*this, quality_factor); - m_layers_editing.state = LayersEditing::Completed; - m_dirty = true; -} - -void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params) -{ - wxGetApp().plater()->take_snapshot(_L("Variable layer height - Smooth all")); - m_layers_editing.smooth_layer_height_profile(*this, smoothing_params); - m_layers_editing.state = LayersEditing::Completed; - m_dirty = true; -} - -bool GLCanvas3D::is_reload_delayed() const -{ - return m_reload_delayed; -} - -void GLCanvas3D::enable_layers_editing(bool enable) -{ - m_layers_editing.set_enabled(enable); - set_as_dirty(); -} - -void GLCanvas3D::enable_legend_texture(bool enable) -{ - m_gcode_viewer.enable_legend(enable); -} - -void GLCanvas3D::enable_picking(bool enable) -{ - m_picking_enabled = enable; - m_selection.set_mode(Selection::Instance); -} - -void GLCanvas3D::enable_moving(bool enable) -{ - m_moving_enabled = enable; -} - -void GLCanvas3D::enable_gizmos(bool enable) -{ - m_gizmos.set_enabled(enable); -} - -void GLCanvas3D::enable_selection(bool enable) -{ - m_selection.set_enabled(enable); -} - -void GLCanvas3D::enable_main_toolbar(bool enable) -{ - m_main_toolbar.set_enabled(enable); -} - -void GLCanvas3D::enable_undoredo_toolbar(bool enable) -{ - m_undoredo_toolbar.set_enabled(enable); -} - -void GLCanvas3D::enable_dynamic_background(bool enable) -{ - m_dynamic_background_enabled = enable; -} - -void GLCanvas3D::allow_multisample(bool allow) -{ - m_multisample_allowed = allow; -} - -void GLCanvas3D::zoom_to_bed() -{ - BoundingBoxf3 box = m_bed.build_volume().bounding_volume(); - box.min.z() = 0.0; - box.max.z() = 0.0; - _zoom_to_box(box); -} - -void GLCanvas3D::zoom_to_volumes() -{ - m_apply_zoom_to_volumes_filter = true; - _zoom_to_box(volumes_bounding_box()); - m_apply_zoom_to_volumes_filter = false; -} - -void GLCanvas3D::zoom_to_selection() -{ - if (!m_selection.is_empty()) - _zoom_to_box(m_selection.get_bounding_box()); -} - -void GLCanvas3D::zoom_to_gcode() -{ - _zoom_to_box(m_gcode_viewer.get_paths_bounding_box(), 1.05); -} - -void GLCanvas3D::select_view(const std::string& direction) -{ - wxGetApp().plater()->get_camera().select_view(direction); - if (m_canvas != nullptr) - m_canvas->Refresh(); -} - -void GLCanvas3D::update_volumes_colors_by_extruder() -{ - if (m_config != nullptr) - m_volumes.update_colors_by_extruder(m_config); -} - -void GLCanvas3D::render() -{ - if (m_in_render) { - // if called recursively, return - m_dirty = true; - return; - } - - m_in_render = true; - Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; }); - (void)in_render_guard; - - if (m_canvas == nullptr) - return; - - // ensures this canvas is current and initialized - if (!_is_shown_on_screen() || !_set_current() || !wxGetApp().init_opengl()) - return; - - if (!is_initialized() && !init()) - return; - - if (!m_main_toolbar.is_enabled()) - m_gcode_viewer.init(); - - if (! m_bed.build_volume().valid()) { - // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE - post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE)); - return; - } - -#if ENABLE_ENVIRONMENT_MAP - if (wxGetApp().is_editor()) - wxGetApp().plater()->init_environment_texture(); -#endif // ENABLE_ENVIRONMENT_MAP - -#if ENABLE_GLMODEL_STATISTICS - GLModel::reset_statistics_counters(); -#endif // ENABLE_GLMODEL_STATISTICS - - const Size& cnv_size = get_canvas_size(); - // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene - // to preview, this was called before canvas had its final size. It reported zero width - // and the viewport was set incorrectly, leading to tripping glAsserts further down - // the road (in apply_projection). That's why the minimum size is forced to 10. - Camera& camera = wxGetApp().plater()->get_camera(); -#if ENABLE_RAYCAST_PICKING - camera.set_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height())); - camera.apply_viewport(); -#else - camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height())); -#endif // ENABLE_RAYCAST_PICKING - - if (camera.requires_zoom_to_bed) { - zoom_to_bed(); - _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); - camera.requires_zoom_to_bed = false; - } - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - camera.apply_view_matrix(); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - camera.apply_projection(_max_bounding_box(true, true)); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; - glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam)); - GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f }; - glsafe(::glLightfv(GL_LIGHT0, GL_POSITION, position_top)); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - wxGetApp().imgui()->new_frame(); - - if (m_picking_enabled) { - if (m_rectangle_selection.is_dragging() && !m_rectangle_selection.is_empty()) - // picking pass using rectangle selection - _rectangular_selection_picking_pass(); - else if (!m_volumes.empty()) - // regular picking pass - _picking_pass(); -#if ENABLE_RAYCAST_PICKING_DEBUG - else { - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); - imgui.text("Picking disabled"); - imgui.end(); - } -#endif // ENABLE_RAYCAST_PICKING_DEBUG - } - - const bool is_looking_downward = camera.is_looking_downward(); - - // draw scene - glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - _render_background(); - - _render_objects(GLVolumeCollection::ERenderType::Opaque); - if (!m_main_toolbar.is_enabled()) - _render_gcode(); - _render_sla_slices(); - _render_selection(); - if (is_looking_downward) -#if ENABLE_LEGACY_OPENGL_REMOVAL - _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false, true); -#else - _render_bed(false, true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - _render_objects(GLVolumeCollection::ERenderType::Transparent); - - _render_sequential_clearance(); -#if ENABLE_RENDER_SELECTION_CENTER - _render_selection_center(); -#endif // ENABLE_RENDER_SELECTION_CENTER - if (!m_main_toolbar.is_enabled()) - _render_gcode_cog(); - - // we need to set the mouse's scene position here because the depth buffer - // could be invalidated by the following gizmo render methods - // this position is used later into on_mouse() to drag the objects - if (m_picking_enabled) - m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast()); - - // sidebar hints need to be rendered before the gizmos because the depth buffer - // could be invalidated by the following gizmo render methods - _render_selection_sidebar_hints(); - _render_current_gizmo(); - if (!is_looking_downward) -#if ENABLE_LEGACY_OPENGL_REMOVAL - _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), true, true); -#else - _render_bed(true, true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_RAYCAST_PICKING_DEBUG - if (m_picking_enabled && !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging()) - m_scene_raycaster.render_hit(camera); -#endif // ENABLE_RAYCAST_PICKING_DEBUG - -#if ENABLE_SHOW_CAMERA_TARGET - _render_camera_target(); -#endif // ENABLE_SHOW_CAMERA_TARGET - - if (m_picking_enabled && m_rectangle_selection.is_dragging()) - m_rectangle_selection.render(*this); - - // draw overlays - _render_overlays(); - - if (wxGetApp().plater()->is_render_statistic_dialog_visible()) { - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - imgui.text("FPS (SwapBuffers() calls per second):"); - ImGui::SameLine(); - imgui.text(std::to_string(m_render_stats.get_fps_and_reset_if_needed())); - ImGui::Separator(); - imgui.text("Compressed textures:"); - ImGui::SameLine(); - imgui.text(OpenGLManager::are_compressed_textures_supported() ? "supported" : "not supported"); - imgui.text("Max texture size:"); - ImGui::SameLine(); - imgui.text(std::to_string(OpenGLManager::get_gl_info().get_max_tex_size())); - imgui.end(); - } - -#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW - if (wxGetApp().is_editor() && wxGetApp().plater()->is_view3D_shown()) - wxGetApp().plater()->render_project_state_debug_window(); -#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW - -#if ENABLE_CAMERA_STATISTICS - camera.debug_render(); -#endif // ENABLE_CAMERA_STATISTICS -#if ENABLE_GLMODEL_STATISTICS - GLModel::render_statistics(); -#endif // ENABLE_GLMODEL_STATISTICS - - std::string tooltip; - - // Negative coordinate means out of the window, likely because the window was deactivated. - // In that case the tooltip should be hidden. - if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) { - if (tooltip.empty()) - tooltip = m_layers_editing.get_tooltip(*this); - - if (tooltip.empty()) - tooltip = m_gizmos.get_tooltip(); - - if (tooltip.empty()) - tooltip = m_main_toolbar.get_tooltip(); - - if (tooltip.empty()) - tooltip = m_undoredo_toolbar.get_tooltip(); - - if (tooltip.empty()) - tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip(); - - if (tooltip.empty()) - tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip(); - } - - set_tooltip(tooltip); - - if (m_tooltip_enabled) - m_tooltip.render(m_mouse.position, *this); - - wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); - - wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width()); - - wxGetApp().imgui()->render(); - - m_canvas->SwapBuffers(); - m_render_stats.increment_fps_counter(); -} - -void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type) -{ - render_thumbnail(thumbnail_data, w, h, thumbnail_params, m_volumes, camera_type); -} - -void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) -{ - switch (OpenGLManager::get_framebuffers_type()) - { - case OpenGLManager::EFramebufferType::Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } - case OpenGLManager::EFramebufferType::Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } - default: { _render_thumbnail_legacy(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } - } -} - -void GLCanvas3D::select_all() -{ - m_selection.add_all(); - m_dirty = true; - wxGetApp().obj_manipul()->set_dirty(); - m_gizmos.reset_all_states(); - m_gizmos.update_data(); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); -} - -void GLCanvas3D::deselect_all() -{ - if (m_selection.is_empty()) - return; - - m_selection.remove_all(); - wxGetApp().obj_manipul()->set_dirty(); - m_gizmos.reset_all_states(); - m_gizmos.update_data(); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); -} - -void GLCanvas3D::delete_selected() -{ - m_selection.erase(); -} - -void GLCanvas3D::ensure_on_bed(unsigned int object_idx, bool allow_negative_z) -{ - if (allow_negative_z) - return; - - typedef std::map, double> InstancesToZMap; - InstancesToZMap instances_min_z; - - for (GLVolume* volume : m_volumes.volumes) { - if (volume->object_idx() == (int)object_idx && !volume->is_modifier) { - double min_z = volume->transformed_convex_hull_bounding_box().min.z(); - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it == instances_min_z.end()) - it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; - - it->second = std::min(it->second, min_z); - } - } - - for (GLVolume* volume : m_volumes.volumes) { - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it != instances_min_z.end()) - volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); - } -} - - -const std::vector& GLCanvas3D::get_gcode_layers_zs() const -{ - return m_gcode_viewer.get_layers_zs(); -} - -std::vector GLCanvas3D::get_volumes_print_zs(bool active_only) const -{ - return m_volumes.get_current_print_zs(active_only); -} - -void GLCanvas3D::set_gcode_options_visibility_from_flags(unsigned int flags) -{ - m_gcode_viewer.set_options_visibility_from_flags(flags); -} - -void GLCanvas3D::set_toolpath_role_visibility_flags(unsigned int flags) -{ - m_gcode_viewer.set_toolpath_role_visibility_flags(flags); -} - -void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) -{ - m_gcode_viewer.set_view_type(type); -} - -void GLCanvas3D::set_volumes_z_range(const std::array& range) -{ - m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6); -} - -void GLCanvas3D::set_toolpaths_z_range(const std::array& range) -{ - if (m_gcode_viewer.has_data()) - m_gcode_viewer.set_layers_z_range(range); -} - -std::vector GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs) -{ - if (instance_idxs.empty()) { - for (unsigned int i = 0; i < model_object.instances.size(); ++i) { - instance_idxs.emplace_back(i); - } - } -#if ENABLE_LEGACY_OPENGL_REMOVAL - return m_volumes.load_object(&model_object, obj_idx, instance_idxs); -#else - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) -{ - if (0 <= obj_idx && obj_idx < (int)model.objects.size()) { - const ModelObject* model_object = model.objects[obj_idx]; - if (model_object != nullptr) - return load_object(*model_object, obj_idx, std::vector()); - } - - return std::vector(); -} - -void GLCanvas3D::mirror_selection(Axis axis) -{ - m_selection.mirror(axis); - do_mirror(L("Mirror Object")); - wxGetApp().obj_manipul()->set_dirty(); -} - -// Reload the 3D scene of -// 1) Model / ModelObjects / ModelInstances / ModelVolumes -// 2) Print bed -// 3) SLA support meshes for their respective ModelObjects / ModelInstances -// 4) Wipe tower preview -// 5) Out of bed collision status & message overlay (texture) -void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_refresh) -{ - if (m_canvas == nullptr || m_config == nullptr || m_model == nullptr) - return; - - if (!m_initialized) - return; - - _set_current(); - - m_hover_volume_idxs.clear(); - - struct ModelVolumeState { - ModelVolumeState(const GLVolume* volume) : - model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} - ModelVolumeState(const ModelVolume* model_volume, const ObjectID& instance_id, const GLVolume::CompositeID& composite_id) : - model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} - ModelVolumeState(const ObjectID& volume_id, const ObjectID& instance_id) : - model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} - bool new_geometry() const { return this->volume_idx == size_t(-1); } - const ModelVolume* model_volume; - // ObjectID of ModelVolume + ObjectID of ModelInstance - // or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance - std::pair geometry_id; - GLVolume::CompositeID composite_id; - // Volume index in the new GLVolume vector. - size_t volume_idx; - }; - std::vector model_volume_state; - std::vector aux_volume_state; - - struct GLVolumeState { - GLVolumeState() : - volume_idx(size_t(-1)) {} - GLVolumeState(const GLVolume* volume, unsigned int volume_idx) : - composite_id(volume->composite_id), volume_idx(volume_idx) {} - GLVolumeState(const GLVolume::CompositeID &composite_id) : - composite_id(composite_id), volume_idx(size_t(-1)) {} - - GLVolume::CompositeID composite_id; - // Volume index in the old GLVolume vector. - size_t volume_idx; - }; - - // SLA steps to pull the preview meshes for. - typedef std::array SLASteps; - SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad }; - struct SLASupportState { - std::array::value> step; - }; - // State of the sla_steps for all SLAPrintObjects. - std::vector sla_support_state; - - std::vector instance_ids_selected; - std::vector map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1)); - std::vector deleted_volumes; - std::vector glvolumes_new; - glvolumes_new.reserve(m_volumes.volumes.size()); - auto model_volume_state_lower = [](const ModelVolumeState& m1, const ModelVolumeState& m2) { return m1.geometry_id < m2.geometry_id; }; - - m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh; - - PrinterTechnology printer_technology = current_printer_technology(); - int volume_idx_wipe_tower_old = -1; - - // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). - // First initialize model_volumes_new_sorted & model_instances_new_sorted. - for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++object_idx) { - const ModelObject* model_object = m_model->objects[object_idx]; - for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++instance_idx) { - const ModelInstance* model_instance = model_object->instances[instance_idx]; - for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++volume_idx) { - const ModelVolume* model_volume = model_object->volumes[volume_idx]; - model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); - } - } - } - if (printer_technology == ptSLA) { - const SLAPrint* sla_print = this->sla_print(); -#ifndef NDEBUG - // Verify that the SLAPrint object is synchronized with m_model. - check_model_ids_equal(*m_model, sla_print->model()); -#endif /* NDEBUG */ - sla_support_state.reserve(sla_print->objects().size()); - for (const SLAPrintObject* print_object : sla_print->objects()) { - SLASupportState state; - for (size_t istep = 0; istep < sla_steps.size(); ++istep) { - state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); - if (state.step[istep].state == PrintStateBase::DONE) { - if (!print_object->has_mesh(sla_steps[istep])) - // Consider the DONE step without a valid mesh as invalid for the purpose - // of mesh visualization. - state.step[istep].state = PrintStateBase::INVALID; - else if (sla_steps[istep] != slaposDrillHoles) - for (const ModelInstance* model_instance : print_object->model_object()->instances) - // Only the instances, which are currently printable, will have the SLA support structures kept. - // The instances outside the print bed will have the GLVolumes of their support structures released. - if (model_instance->is_printable()) - aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); - } - } - sla_support_state.emplace_back(state); - } - } - std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); - std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower); - // Release all ModelVolume based GLVolumes not found in the current Model. Find the GLVolume of a hollowed mesh. - for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++volume_id) { - GLVolume* volume = m_volumes.volumes[volume_id]; - ModelVolumeState key(volume); - ModelVolumeState* mvs = nullptr; - if (volume->volume_idx() < 0) { - auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); - if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) - // This can be an SLA support structure that should not be rendered (in case someone used undo - // to revert to before it was generated). We only reuse the volume if that's not the case. - if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints) - mvs = &(*it); - } - else { - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) - mvs = &(*it); - } - // Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID. - // The wipe tower has its own wipe_tower_instance_id(). - if (m_selection.contains_volume(volume_id)) - instance_ids_selected.emplace_back(volume->geometry_id.second); - if (mvs == nullptr || force_full_scene_refresh) { - // This GLVolume will be released. - if (volume->is_wipe_tower) { - // There is only one wipe tower. - assert(volume_idx_wipe_tower_old == -1); -#if ENABLE_OPENGL_ES - m_wipe_tower_mesh.clear(); -#endif // ENABLE_OPENGL_ES - volume_idx_wipe_tower_old = (int)volume_id; - } - if (!m_reload_delayed) { - deleted_volumes.emplace_back(volume, volume_id); - delete volume; - } - } - else { - // This GLVolume will be reused. - volume->set_sla_shift_z(0.0); - map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); - mvs->volume_idx = glvolumes_new.size(); - glvolumes_new.emplace_back(volume); - // Update color of the volume based on the current extruder. - if (mvs->model_volume != nullptr) { - int extruder_id = mvs->model_volume->extruder_id(); - if (extruder_id != -1) - volume->extruder_id = extruder_id; - - volume->is_modifier = !mvs->model_volume->is_model_part(); - volume->set_color(color_from_model_volume(*mvs->model_volume)); - // force update of render_color alpha channel - volume->set_render_color(volume->color.is_transparent()); - - // updates volumes transformations - volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); - volume->set_volume_transformation(mvs->model_volume->get_transformation()); - - // updates volumes convex hull - if (mvs->model_volume->is_model_part() && ! volume->convex_hull()) - // Model volume was likely changed from modifier or support blocker / enforcer to a model part. - // Only model parts require convex hulls. - volume->set_convex_hull(mvs->model_volume->get_convex_hull_shared_ptr()); - } - } - } - sort_remove_duplicates(instance_ids_selected); - auto deleted_volumes_lower = [](const GLVolumeState &v1, const GLVolumeState &v2) { return v1.composite_id < v2.composite_id; }; - std::sort(deleted_volumes.begin(), deleted_volumes.end(), deleted_volumes_lower); - - if (m_reload_delayed) - return; - - bool update_object_list = false; - if (m_volumes.volumes != glvolumes_new) - update_object_list = true; - m_volumes.volumes = std::move(glvolumes_new); - for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { - const ModelObject &model_object = *m_model->objects[obj_idx]; - for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { - const ModelVolume &model_volume = *model_object.volumes[volume_idx]; - for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { - const ModelInstance &model_instance = *model_object.instances[instance_idx]; - ModelVolumeState key(model_volume.id(), model_instance.id()); - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); - if (it->new_geometry()) { - // New volume. - auto it_old_volume = std::lower_bound(deleted_volumes.begin(), deleted_volumes.end(), GLVolumeState(it->composite_id), deleted_volumes_lower); - if (it_old_volume != deleted_volumes.end() && it_old_volume->composite_id == it->composite_id) - // If a volume changed its ObjectID, but it reuses a GLVolume's CompositeID, maintain its selection. - map_glvolume_old_to_new[it_old_volume->volume_idx] = m_volumes.volumes.size(); - // Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh - // later in this function. - it->volume_idx = m_volumes.volumes.size(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx); -#else - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_volumes.volumes.back()->geometry_id = key.geometry_id; - update_object_list = true; - } else { - // Recycling an old GLVolume. - GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; - assert(existing_volume.geometry_id == key.geometry_id); - // Update the Object/Volume/Instance indices into the current Model. - if (existing_volume.composite_id != it->composite_id) { - existing_volume.composite_id = it->composite_id; - update_object_list = true; - } - } - } - } - } - if (printer_technology == ptSLA) { - size_t idx = 0; - const SLAPrint *sla_print = this->sla_print(); - std::vector shift_zs(m_model->objects.size(), 0); - double relative_correction_z = sla_print->relative_correction().z(); - if (relative_correction_z <= EPSILON) - relative_correction_z = 1.; - for (const SLAPrintObject *print_object : sla_print->objects()) { - SLASupportState &state = sla_support_state[idx ++]; - const ModelObject *model_object = print_object->model_object(); - // Find an index of the ModelObject - int object_idx; - // There may be new SLA volumes added to the scene for this print_object. - // Find the object index of this print_object in the Model::objects list. - auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); - assert(it != sla_print->model().objects.end()); - object_idx = it - sla_print->model().objects.begin(); - // Cache the Z offset to be applied to all volumes with this object_idx. - shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; - // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. - // pairs of - std::vector> instances[std::tuple_size::value]; - for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { - const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; - // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. - auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), - [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); - assert(it != model_object->instances.end()); - int instance_idx = it - model_object->instances.begin(); - for (size_t istep = 0; istep < sla_steps.size(); ++ istep) - if (sla_steps[istep] == slaposDrillHoles) { - // Hollowing is a special case, where the mesh from the backend is being loaded into the 1st volume of an instance, - // not into its own GLVolume. - // There shall always be such a GLVolume allocated. - ModelVolumeState key(model_object->volumes.front()->id(), instance.instance_id); - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); - assert(!it->new_geometry()); - GLVolume &volume = *m_volumes.volumes[it->volume_idx]; - if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) { - // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen. -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.reset(); -#else - volume.indexed_vertex_array.release_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (state.step[istep].state == PrintStateBase::DONE) { - TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); - assert(! mesh.empty()); - - // sla_trafo does not contain volume trafo. To get a mesh to create - // a new volume from, we have to apply vol trafo inverse separately. - const ModelObject& mo = *m_model->objects[volume.object_idx()]; - Transform3d trafo = sla_print->sla_trafo(mo) - * mo.volumes.front()->get_transformation().get_matrix(); - mesh.transform(trafo.inverse()); -#if ENABLE_SMOOTH_NORMALS -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.init_from(mesh, true); -#else - volume.indexed_vertex_array.load_mesh(mesh, true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#else -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.init_from(mesh); -#if ENABLE_RAYCAST_PICKING - volume.mesh_raycaster = std::make_unique(std::make_shared(mesh)); -#endif // ENABLE_RAYCAST_PICKING -#else - volume.indexed_vertex_array.load_mesh(mesh); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#endif // ENABLE_SMOOTH_NORMALS - } - else { - // Reload the original volume. -#if ENABLE_SMOOTH_NORMALS -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -#else - volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#else -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_RAYCAST_PICKING - const TriangleMesh& new_mesh = m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(); - volume.model.init_from(new_mesh); - volume.mesh_raycaster = std::make_unique(std::make_shared(new_mesh)); -#else - volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); -#endif // ENABLE_RAYCAST_PICKING -#else - volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#endif // ENABLE_SMOOTH_NORMALS - } -#if !ENABLE_LEGACY_OPENGL_REMOVAL - volume.finalize_geometry(true); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - } - //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable - // to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables - // of various concenrs (model vs. 3D print path). - volume.offsets = { state.step[istep].timestamp }; - } - else if (state.step[istep].state == PrintStateBase::DONE) { - // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created. - ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); - auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); - assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); - if (it->new_geometry()) { - // This can be an SLA support structure that should not be rendered (in case someone used undo - // to revert to before it was generated). If that's the case, we should not generate anything. - if (model_object->sla_points_status != sla::PointsStatus::NoPoints) - instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); - else - shift_zs[object_idx] = 0.; - } - else { - // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. - m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); - m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); - } - } - } - - for (size_t istep = 0; istep < sla_steps.size(); ++istep) - if (!instances[istep].empty()) -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); -#else - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed - for (GLVolume* volume : m_volumes.volumes) - if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) - volume->set_sla_shift_z(shift_zs[volume->object_idx()]); - } - - if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) { - // Should the wipe tower be visualized ? - unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); - - bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; - bool co = dynamic_cast(m_config->option("complete_objects"))->value; - - if (extruders_count > 1 && wt && !co) { - // Height of a print (Show at least a slab) - double height = std::max(m_model->bounding_box().max(2), 10.0); - - float x = dynamic_cast(m_config->option("wipe_tower_x"))->value; - float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; - float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; - float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; - - const Print *print = m_process->fff_print(); - - float depth = print->wipe_tower_data(extruders_count).depth; - float brim_width = print->wipe_tower_data(extruders_count).brim_width; - -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_OPENGL_ES - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), - brim_width, &m_wipe_tower_mesh); -#else - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), - brim_width); -#endif // ENABLE_OPENGL_ES -#else - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), - brim_width, m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (volume_idx_wipe_tower_old != -1) - map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; - } - } - - update_volumes_colors_by_extruder(); - // Update selection indices based on the old/new GLVolumeCollection. - if (m_selection.get_mode() == Selection::Instance) - m_selection.instances_changed(instance_ids_selected); - else - m_selection.volumes_changed(map_glvolume_old_to_new); - - m_gizmos.update_data(); - m_gizmos.refresh_on_off_state(); - - // Update the toolbar - if (update_object_list) - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - - // checks for geometry outside the print volume to render it accordingly - if (!m_volumes.empty()) { - ModelInstanceEPrintVolumeState state; - const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state); - const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); - const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); - - if (printer_technology != ptSLA) { - _set_warning_notification(EWarning::ObjectClashed, partlyOut); - _set_warning_notification(EWarning::ObjectOutside, fullyOut); - _set_warning_notification(EWarning::SlaSupportsOutside, false); - } - else { - const auto [res, volume] = _is_any_volume_outside(); - const bool is_support = volume != nullptr && volume->is_sla_support(); - if (is_support) { - _set_warning_notification(EWarning::ObjectClashed, false); - _set_warning_notification(EWarning::ObjectOutside, false); - _set_warning_notification(EWarning::SlaSupportsOutside, partlyOut || fullyOut); - } - else { - _set_warning_notification(EWarning::ObjectClashed, partlyOut); - _set_warning_notification(EWarning::ObjectOutside, fullyOut); - _set_warning_notification(EWarning::SlaSupportsOutside, false); - } - } - - post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, - contained_min_one && !m_model->objects.empty() && !partlyOut)); - } - else { - _set_warning_notification(EWarning::ObjectOutside, false); - _set_warning_notification(EWarning::ObjectClashed, false); - _set_warning_notification(EWarning::SlaSupportsOutside, false); - post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); - } - - refresh_camera_scene_box(); - - if (m_selection.is_empty()) { - // If no object is selected, deactivate the active gizmo, if any - // Otherwise it may be shown after cleaning the scene (if it was active while the objects were deleted) - m_gizmos.reset_all_states(); - - // If no object is selected, reset the objects manipulator on the sidebar - // to force a reset of its cache - auto manip = wxGetApp().obj_manipul(); - if (manip != nullptr) - manip->set_dirty(); - } - -#if ENABLE_RAYCAST_PICKING - // refresh volume raycasters for picking - m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume); - for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { - const GLVolume* v = m_volumes.volumes[i]; - assert(v->mesh_raycaster != nullptr); - std::shared_ptr raycaster = add_raycaster_for_picking(SceneRaycaster::EType::Volume, i, *v->mesh_raycaster, v->world_matrix()); - raycaster->set_active(v->is_active); - } - - // refresh gizmo elements raycasters for picking - GLGizmoBase* curr_gizmo = m_gizmos.get_current(); - if (curr_gizmo != nullptr) - curr_gizmo->unregister_raycasters_for_picking(); - m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo); - if (curr_gizmo != nullptr && !m_selection.is_empty()) - curr_gizmo->register_raycasters_for_picking(); -#endif // ENABLE_RAYCAST_PICKING - - // and force this canvas to be redrawn. - m_dirty = true; -} - -#if !ENABLE_LEGACY_OPENGL_REMOVAL -static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old, bool gl_initialized, size_t prealloc_size = VERTEX_BUFFER_RESERVE_SIZE) -{ - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol_old.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol_old.indexed_vertex_array = vol_new.indexed_vertex_array; - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - // Reserving number of vertices (3x position + 3x color) - vol_new.indexed_vertex_array.reserve(prealloc_size / 6); - // Finalize the old geometry, possibly move data to the graphics card. - vol_old.finalize_geometry(gl_initialized); -} -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_gcode_viewer.load(gcode_result, *this->fff_print()); -#else - m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - if (wxGetApp().is_editor()) { - m_gcode_viewer.update_shells_color_by_extruder(m_config); - _set_warning_notification_if_needed(EWarning::ToolpathOutside); - } - - m_gcode_viewer.refresh(gcode_result, str_tool_colors); - set_as_dirty(); - request_extra_frame(); -} - -void GLCanvas3D::refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) -{ - m_gcode_viewer.refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); - set_as_dirty(); - request_extra_frame(); -} - -void GLCanvas3D::load_sla_preview() -{ - const SLAPrint* print = sla_print(); - if (m_canvas != nullptr && print != nullptr) { - _set_current(); - // Release OpenGL data before generating new data. - reset_volumes(); - _load_sla_shells(); - _update_sla_shells_outside_state(); - _set_warning_notification_if_needed(EWarning::ObjectClashed); - _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); - } -} - -void GLCanvas3D::load_preview(const std::vector& str_tool_colors, const std::vector& color_print_values) -{ - const Print *print = this->fff_print(); - if (print == nullptr) - return; - - _set_current(); - - // Release OpenGL data before generating new data. - this->reset_volumes(); - - const BuildVolume &build_volume = m_bed.build_volume(); - _load_print_toolpaths(build_volume); - _load_wipe_tower_toolpaths(build_volume, str_tool_colors); - for (const PrintObject* object : print->objects()) - _load_print_object_toolpaths(*object, build_volume, str_tool_colors, color_print_values); - - _set_warning_notification_if_needed(EWarning::ToolpathOutside); -} - -void GLCanvas3D::bind_event_handlers() -{ - if (m_canvas != nullptr) { - m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this); - m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); - m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this); - m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this); - m_canvas->Bind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this); - m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); - m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); - m_canvas->Bind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this); - m_toolbar_highlighter.set_timer_owner(m_canvas, 0); - m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_toolbar_highlighter.blink(); }); - m_gizmo_highlighter.set_timer_owner(m_canvas, 0); - m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_gizmo_highlighter.blink(); }); - m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); - m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this); - - m_event_handlers_bound = true; - } -} - -void GLCanvas3D::unbind_event_handlers() -{ - if (m_canvas != nullptr && m_event_handlers_bound) { - m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this); - m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); - m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this); - m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this); - m_canvas->Unbind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this); - m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); - m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); - m_canvas->Unbind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this); - m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); - m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); - m_canvas->Unbind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this); - - m_event_handlers_bound = false; - } -} - -void GLCanvas3D::on_size(wxSizeEvent& evt) -{ - m_dirty = true; -} - -void GLCanvas3D::on_idle(wxIdleEvent& evt) -{ - if (!m_initialized) - return; - - m_dirty |= m_main_toolbar.update_items_state(); - m_dirty |= m_undoredo_toolbar.update_items_state(); - m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); - m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); - bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); - m_dirty |= mouse3d_controller_applied; - m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); - auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current(); - if (gizmo != nullptr) m_dirty |= gizmo->update_items_state(); - // ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism - bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame(); - m_dirty |= imgui_requires_extra_frame; - - if (!m_dirty) - return; - - // this needs to be done here. - // during the render launched by the refresh the value may be set again - wxGetApp().imgui()->reset_requires_extra_frame(); - - _refresh_if_shown_on_screen(); - - if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) { - m_extra_frame_requested = false; - evt.RequestMore(); - } - else - m_dirty = false; -} - -void GLCanvas3D::on_char(wxKeyEvent& evt) -{ - if (!m_initialized) - return; - - // see include/wx/defs.h enum wxKeyCode - int keyCode = evt.GetKeyCode(); - int ctrlMask = wxMOD_CONTROL; - int shiftMask = wxMOD_SHIFT; - - auto imgui = wxGetApp().imgui(); - if (imgui->update_key_data(evt)) { - render(); - return; - } - - if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu())) - return; - - if (m_gizmos.on_char(evt)) - return; - - if ((evt.GetModifiers() & ctrlMask) != 0) { - // CTRL is pressed - switch (keyCode) { -#ifdef __APPLE__ - case 'a': - case 'A': -#else /* __APPLE__ */ - case WXK_CONTROL_A: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); - break; -#ifdef __APPLE__ - case 'c': - case 'C': -#else /* __APPLE__ */ - case WXK_CONTROL_C: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLTOOLBAR_COPY)); - break; -#ifdef __APPLE__ - case 'm': - case 'M': -#else /* __APPLE__ */ - case WXK_CONTROL_M: -#endif /* __APPLE__ */ - { -#ifdef _WIN32 - if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "1") { -#endif //_WIN32 -#ifdef __APPLE__ - // On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog" - if ((evt.GetModifiers() & shiftMask) != 0) { -#endif // __APPLE__ - Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller(); - controller.show_settings_dialog(!controller.is_settings_dialog_shown()); - m_dirty = true; -#ifdef __APPLE__ - } - else - // and Cmd+M to minimize application - wxGetApp().mainframe->Iconize(); -#endif // __APPLE__ -#ifdef _WIN32 - } -#endif //_WIN32 - break; - } -#ifdef __APPLE__ - case 'v': - case 'V': -#else /* __APPLE__ */ - case WXK_CONTROL_V: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE)); - break; - - -#ifdef __APPLE__ - case 'f': - case 'F': -#else /* __APPLE__ */ - case WXK_CONTROL_F: -#endif /* __APPLE__ */ - _activate_search_toolbar_item(); - break; - - -#ifdef __APPLE__ - case 'y': - case 'Y': -#else /* __APPLE__ */ - case WXK_CONTROL_Y: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLCANVAS_REDO)); - break; -#ifdef __APPLE__ - case 'z': - case 'Z': -#else /* __APPLE__ */ - case WXK_CONTROL_Z: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); - break; - - case WXK_BACK: - case WXK_DELETE: - post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; - default: evt.Skip(); - } - } else { - switch (keyCode) - { - case WXK_BACK: - case WXK_DELETE: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; } - case WXK_ESCAPE: { deselect_all(); break; } - case WXK_F5: { - if ((wxGetApp().is_editor() && !wxGetApp().plater()->model().objects.empty()) || - (wxGetApp().is_gcode_viewer() && !wxGetApp().plater()->get_last_loaded_gcode().empty())) - post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK)); - break; - } - case '0': { select_view("iso"); break; } - case '1': { select_view("top"); break; } - case '2': { select_view("bottom"); break; } - case '3': { select_view("front"); break; } - case '4': { select_view("rear"); break; } - case '5': { select_view("left"); break; } - case '6': { select_view("right"); break; } - case '+': { - if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); - else - post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); - break; - } - case '-': { - if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); - else - post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); - break; - } - case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } - case 'A': - case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } - case 'B': - case 'b': { zoom_to_bed(); break; } - case 'C': - case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; } - case 'E': - case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; } - case 'G': - case 'g': { - if ((evt.GetModifiers() & shiftMask) != 0) { - if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt)); - } - break; - } - case 'I': - case 'i': { _update_camera_zoom(1.0); break; } - case 'K': - case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; } - case 'L': - case 'l': { - if (!m_main_toolbar.is_enabled()) - show_legend(!is_legend_shown()); - break; - } - case 'O': - case 'o': { _update_camera_zoom(-1.0); break; } - case 'Z': - case 'z': { - if (!m_selection.is_empty()) - zoom_to_selection(); - else { - if (!m_volumes.empty()) - zoom_to_volumes(); - else - _zoom_to_box(m_gcode_viewer.get_paths_bounding_box()); - } - break; - } - default: { evt.Skip(); break; } - } - } -} - -class TranslationProcessor -{ - using UpAction = std::function; - using DownAction = std::function; - - UpAction m_up_action{ nullptr }; - DownAction m_down_action{ nullptr }; - - bool m_running{ false }; - Vec3d m_direction{ Vec3d::UnitX() }; - -public: - TranslationProcessor(UpAction up_action, DownAction down_action) - : m_up_action(up_action), m_down_action(down_action) - { - } - - void process(wxKeyEvent& evt) - { - const int keyCode = evt.GetKeyCode(); - wxEventType type = evt.GetEventType(); - if (type == wxEVT_KEY_UP) { - switch (keyCode) - { - case WXK_NUMPAD_LEFT: case WXK_LEFT: - case WXK_NUMPAD_RIGHT: case WXK_RIGHT: - case WXK_NUMPAD_UP: case WXK_UP: - case WXK_NUMPAD_DOWN: case WXK_DOWN: - { - m_running = false; - m_up_action(); - break; - } - default: { break; } - } - } - else if (type == wxEVT_KEY_DOWN) { - bool apply = false; - - switch (keyCode) - { - case WXK_SHIFT: - { - if (m_running) - apply = true; - - break; - } - case WXK_NUMPAD_LEFT: - case WXK_LEFT: - { - m_direction = -Vec3d::UnitX(); - apply = true; - break; - } - case WXK_NUMPAD_RIGHT: - case WXK_RIGHT: - { - m_direction = Vec3d::UnitX(); - apply = true; - break; - } - case WXK_NUMPAD_UP: - case WXK_UP: - { - m_direction = Vec3d::UnitY(); - apply = true; - break; - } - case WXK_NUMPAD_DOWN: - case WXK_DOWN: - { - m_direction = -Vec3d::UnitY(); - apply = true; - break; - } - default: { break; } - } - - if (apply) { - m_running = true; - m_down_action(m_direction, evt.ShiftDown(), evt.CmdDown()); - } - } - } -}; - -void GLCanvas3D::on_key(wxKeyEvent& evt) -{ - static TranslationProcessor translationProcessor( - [this]() { - do_move(L("Gizmo-Move")); - m_gizmos.update_data(); - - wxGetApp().obj_manipul()->set_dirty(); - // Let the plater know that the dragging finished, so a delayed refresh - // of the scene with the background processing data should be performed. - post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); - // updates camera target constraints - refresh_camera_scene_box(); - m_dirty = true; - }, - [this](const Vec3d& direction, bool slow, bool camera_space) { - m_selection.setup_cache(); - double multiplier = slow ? 1.0 : 10.0; - - Vec3d displacement; - if (camera_space) { - Eigen::Matrix inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3); - displacement = multiplier * (inv_view_3x3 * direction); - displacement.z() = 0.0; - } - else - displacement = multiplier * direction; - -#if ENABLE_WORLD_COORDINATE - TransformationType trafo_type; - trafo_type.set_relative(); - m_selection.translate(displacement, trafo_type); -#else - m_selection.translate(displacement); -#endif // ENABLE_WORLD_COORDINATE - m_dirty = true; - } - ); - - const int keyCode = evt.GetKeyCode(); - - auto imgui = wxGetApp().imgui(); - if (imgui->update_key_data(evt)) { - render(); - } - else { - if (!m_gizmos.on_key(evt)) { - if (evt.GetEventType() == wxEVT_KEY_UP) { - if (evt.ShiftDown() && evt.ControlDown() && keyCode == WXK_SPACE) { - wxGetApp().plater()->toggle_render_statistic_dialog(); - m_dirty = true; - } - if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { - // Enable switching between 3D and Preview with Tab - // m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux - post_event(SimpleEvent(EVT_GLCANVAS_TAB)); - } - else if (keyCode == WXK_TAB && evt.ShiftDown() && ! wxGetApp().is_gcode_viewer()) { - // Collapse side-panel with Shift+Tab - post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR)); - } - else if (keyCode == WXK_SHIFT) { - translationProcessor.process(evt); - - if (m_picking_enabled && m_rectangle_selection.is_dragging()) { - _update_selection_from_hover(); - m_rectangle_selection.stop_dragging(); - m_mouse.ignore_left_up = true; - } - m_shift_kar_filter.reset_count(); - m_dirty = true; -// set_cursor(Standard); - } - else if (keyCode == WXK_ALT) { - if (m_picking_enabled && m_rectangle_selection.is_dragging()) { - _update_selection_from_hover(); - m_rectangle_selection.stop_dragging(); - m_mouse.ignore_left_up = true; - m_dirty = true; - } -// set_cursor(Standard); - } - else if (keyCode == WXK_CONTROL) { -#if ENABLE_RAYCAST_PICKING - if (m_mouse.dragging && !m_moving) { -#else - if (m_mouse.dragging) { -#endif // ENABLE_RAYCAST_PICKING - // if the user releases CTRL while rotating the 3D scene - // prevent from moving the selected volume - m_mouse.drag.move_volume_idx = -1; - m_mouse.set_start_position_3D_as_invalid(); - } - m_ctrl_kar_filter.reset_count(); - m_dirty = true; - } - else if (m_gizmos.is_enabled() && !m_selection.is_empty()) { - translationProcessor.process(evt); - - switch (keyCode) - { - case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: - case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: - { - do_rotate(L("Gizmo-Rotate")); - m_gizmos.update_data(); - - wxGetApp().obj_manipul()->set_dirty(); - // Let the plater know that the dragging finished, so a delayed refresh - // of the scene with the background processing data should be performed. - post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); - // updates camera target constraints - refresh_camera_scene_box(); - m_dirty = true; - - break; - } - default: { break; } - } - } - } - else if (evt.GetEventType() == wxEVT_KEY_DOWN) { - m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers(); - if (keyCode == WXK_SHIFT) { - translationProcessor.process(evt); - - if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) { - m_mouse.ignore_left_up = false; -// set_cursor(Cross); - } - if (m_shift_kar_filter.is_first()) - m_dirty = true; - - m_shift_kar_filter.increase_count(); - } - else if (keyCode == WXK_ALT) { - if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) { - m_mouse.ignore_left_up = false; -// set_cursor(Cross); - } - } - else if (keyCode == WXK_CONTROL) { - if (m_ctrl_kar_filter.is_first()) - m_dirty = true; - - m_ctrl_kar_filter.increase_count(); - } - else if (m_gizmos.is_enabled() && !m_selection.is_empty()) { - auto do_rotate = [this](double angle_z_rad) { - m_selection.setup_cache(); - m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint)); - m_dirty = true; -// wxGetApp().obj_manipul()->set_dirty(); - }; - - translationProcessor.process(evt); - - switch (keyCode) - { - case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: { do_rotate(0.25 * M_PI); break; } - case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: { do_rotate(-0.25 * M_PI); break; } - default: { break; } - } - } - else if (!m_gizmos.is_enabled()) { - // DoubleSlider navigation in Preview - if (keyCode == WXK_LEFT || - keyCode == WXK_RIGHT || - keyCode == WXK_UP || - keyCode == WXK_DOWN) { - if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_SLIDERS, evt)); - } - } - } - } - } - - if (keyCode != WXK_TAB - && keyCode != WXK_LEFT - && keyCode != WXK_UP - && keyCode != WXK_RIGHT - && keyCode != WXK_DOWN) { - evt.Skip(); // Needed to have EVT_CHAR generated as well - } -} - -void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) -{ -#ifdef WIN32 - // Try to filter out spurious mouse wheel events comming from 3D mouse. - if (wxGetApp().plater()->get_mouse3d_controller().process_mouse_wheel()) - return; -#endif - - if (!m_initialized) - return; - - // Ignore the wheel events if the middle button is pressed. - if (evt.MiddleIsDown()) - return; - -#if ENABLE_RETINA_GL - const float scale = m_retina_helper->get_scale_factor(); - evt.SetX(evt.GetX() * scale); - evt.SetY(evt.GetY() * scale); -#endif - - if (wxGetApp().imgui()->update_mouse_data(evt)) { - m_dirty = true; - return; - } - -#ifdef __WXMSW__ - // For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad, - // if the event is not allowed to be passed further. - // https://github.com/prusa3d/PrusaSlicer/issues/2750 - // evt.Skip() used to trigger the needed screen refresh, but it does no more. wxWakeUpIdle() seem to work now. - wxWakeUpIdle(); -#endif /* __WXMSW__ */ - - // Performs layers editing updates, if enabled - if (is_layers_editing_enabled()) { - int object_idx_selected = m_selection.get_object_idx(); - if (object_idx_selected != -1) { - // A volume is selected. Test, whether hovering over a layer thickness bar. - if (m_layers_editing.bar_rect_contains(*this, (float)evt.GetX(), (float)evt.GetY())) { - // Adjust the width of the selection. - m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f); - if (m_canvas != nullptr) - m_canvas->Refresh(); - - return; - } - } - } - - // If the Search window or Undo/Redo list is opened, - // update them according to the event - if (m_main_toolbar.is_item_pressed("search") || - m_undoredo_toolbar.is_item_pressed("undo") || - m_undoredo_toolbar.is_item_pressed("redo")) { - m_mouse_wheel = int((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); - return; - } - - // Inform gizmos about the event so they have the opportunity to react. - if (m_gizmos.on_mouse_wheel(evt)) - return; - - // Calculate the zoom delta and apply it to the current zoom factor - double direction_factor = (wxGetApp().app_config->get("reverse_mouse_wheel_zoom") == "1") ? -1.0 : 1.0; - _update_camera_zoom(direction_factor * (double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); -} - -void GLCanvas3D::on_timer(wxTimerEvent& evt) -{ - if (m_layers_editing.state == LayersEditing::Editing) - _perform_layer_editing_action(); -} - -void GLCanvas3D::on_render_timer(wxTimerEvent& evt) -{ - // no need to wake up idle - // right after this event, idle event is fired - // m_dirty = true; - // wxWakeUpIdle(); -} - - -void GLCanvas3D::schedule_extra_frame(int miliseconds) -{ - // Schedule idle event right now - if (miliseconds == 0) - { - // We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait - if (m_in_render) - miliseconds = 33; - else { - m_dirty = true; - wxWakeUpIdle(); - return; - } - } - int remaining_time = m_render_timer.GetInterval(); - // Timer is not running - if (!m_render_timer.IsRunning()) { - m_render_timer.StartOnce(miliseconds); - // Timer is running - restart only if new period is shorter than remaning period - } else { - if (miliseconds + 20 < remaining_time) { - m_render_timer.Stop(); - m_render_timer.StartOnce(miliseconds); - } - } -} - -#ifndef NDEBUG -// #define SLIC3R_DEBUG_MOUSE_EVENTS -#endif - -#ifdef SLIC3R_DEBUG_MOUSE_EVENTS -std::string format_mouse_event_debug_message(const wxMouseEvent &evt) -{ - static int idx = 0; - char buf[2048]; - std::string out; - sprintf(buf, "Mouse Event %d - ", idx ++); - out = buf; - - if (evt.Entering()) - out += "Entering "; - if (evt.Leaving()) - out += "Leaving "; - if (evt.Dragging()) - out += "Dragging "; - if (evt.Moving()) - out += "Moving "; - if (evt.Magnify()) - out += "Magnify "; - if (evt.LeftDown()) - out += "LeftDown "; - if (evt.LeftUp()) - out += "LeftUp "; - if (evt.LeftDClick()) - out += "LeftDClick "; - if (evt.MiddleDown()) - out += "MiddleDown "; - if (evt.MiddleUp()) - out += "MiddleUp "; - if (evt.MiddleDClick()) - out += "MiddleDClick "; - if (evt.RightDown()) - out += "RightDown "; - if (evt.RightUp()) - out += "RightUp "; - if (evt.RightDClick()) - out += "RightDClick "; - - sprintf(buf, "(%d, %d)", evt.GetX(), evt.GetY()); - out += buf; - return out; -} -#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ - -void GLCanvas3D::on_mouse(wxMouseEvent& evt) -{ - if (!m_initialized || !_set_current()) - return; - -#if ENABLE_RETINA_GL - const float scale = m_retina_helper->get_scale_factor(); - evt.SetX(evt.GetX() * scale); - evt.SetY(evt.GetY() * scale); -#endif - - Point pos(evt.GetX(), evt.GetY()); - - ImGuiWrapper* imgui = wxGetApp().imgui(); - if (m_tooltip.is_in_imgui() && evt.LeftUp()) - // ignore left up events coming from imgui windows and not processed by them - m_mouse.ignore_left_up = true; - m_tooltip.set_in_imgui(false); - if (imgui->update_mouse_data(evt)) { - m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast(); - m_tooltip.set_in_imgui(true); - render(); -#ifdef SLIC3R_DEBUG_MOUSE_EVENTS - printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); -#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ - m_dirty = true; - // do not return if dragging or tooltip not empty to allow for tooltip update - // also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection - if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving())) - return; - } - -#ifdef __WXMSW__ - bool on_enter_workaround = false; - if (! evt.Entering() && ! evt.Leaving() && m_mouse.position.x() == -1.0) { - // Workaround for SPE-832: There seems to be a mouse event sent to the window before evt.Entering() - m_mouse.position = pos.cast(); - render(); -#ifdef SLIC3R_DEBUG_MOUSE_EVENTS - printf((format_mouse_event_debug_message(evt) + " - OnEnter workaround\n").c_str()); -#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ - on_enter_workaround = true; - } else -#endif /* __WXMSW__ */ - { -#ifdef SLIC3R_DEBUG_MOUSE_EVENTS - printf((format_mouse_event_debug_message(evt) + " - other\n").c_str()); -#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ - } - - if (m_main_toolbar.on_mouse(evt, *this)) { - if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) - mouse_up_cleanup(); - m_mouse.set_start_position_3D_as_invalid(); - return; - } - - if (m_undoredo_toolbar.on_mouse(evt, *this)) { - if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) - mouse_up_cleanup(); - m_mouse.set_start_position_3D_as_invalid(); - return; - } - - if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) { - if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) - mouse_up_cleanup(); - m_mouse.set_start_position_3D_as_invalid(); - return; - } - - if (wxGetApp().plater()->get_view_toolbar().on_mouse(evt, *this)) { - if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) - mouse_up_cleanup(); - m_mouse.set_start_position_3D_as_invalid(); - return; - } - - for (GLVolume* volume : m_volumes.volumes) { - volume->force_sinking_contours = false; - } - - auto show_sinking_contours = [this]() { - const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); - for (unsigned int idx : idxs) { - m_volumes.volumes[idx]->force_sinking_contours = true; - } - m_dirty = true; - }; - - if (m_gizmos.on_mouse(evt)) { - if (wxWindow::FindFocus() != m_canvas) - // Grab keyboard focus for input in gizmo dialogs. - m_canvas->SetFocus(); - - if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) - mouse_up_cleanup(); - - m_mouse.set_start_position_3D_as_invalid(); - m_mouse.position = pos.cast(); - - // It should be detection of volume change - // Not only detection of some modifiers !!! - if (evt.Dragging()) { - GLGizmosManager::EType c = m_gizmos.get_current_type(); - if (current_printer_technology() == ptFFF && - fff_print()->config().complete_objects){ - if (c == GLGizmosManager::EType::Move || - c == GLGizmosManager::EType::Scale || - c == GLGizmosManager::EType::Rotate ) - update_sequential_clearance(); - } else { - if (c == GLGizmosManager::EType::Move || - c == GLGizmosManager::EType::Scale || - c == GLGizmosManager::EType::Rotate) - show_sinking_contours(); - } - } - else if (evt.LeftUp() && - m_gizmos.get_current_type() == GLGizmosManager::EType::Scale && - m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) { - wxGetApp().obj_list()->selection_changed(); - } - - return; - } - - bool any_gizmo_active = m_gizmos.get_current() != nullptr; - - int selected_object_idx = m_selection.get_object_idx(); - int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; - m_layers_editing.select_object(*m_model, layer_editing_object_idx); - - if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) { - m_mouse.drag.move_requires_threshold = false; - m_mouse.set_move_start_threshold_position_2D_as_invalid(); - } - - if (evt.ButtonDown() && wxWindow::FindFocus() != m_canvas) - // Grab keyboard focus on any mouse click event. - m_canvas->SetFocus(); - - if (evt.Entering()) { -//#if defined(__WXMSW__) || defined(__linux__) -// // On Windows and Linux needs focus in order to catch key events - // Set focus in order to remove it from object list - if (m_canvas != nullptr) { - // Only set focus, if the top level window of this canvas is active - // and ObjectList has a focus - auto p = dynamic_cast(evt.GetEventObject()); - while (p->GetParent()) - p = p->GetParent(); -#ifdef __WIN32__ - wxWindow* const obj_list = wxGetApp().obj_list()->GetMainWindow(); -#else - wxWindow* const obj_list = wxGetApp().obj_list(); -#endif - if (obj_list == p->FindFocus()) - m_canvas->SetFocus(); - m_mouse.position = pos.cast(); - m_tooltip_enabled = false; - // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while - // the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to - // change the volume hover state if any is under the mouse - // 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible, - // so forces a resize to avoid multiple renders with different sizes (seen as flickering) - _refresh_if_shown_on_screen(); - m_tooltip_enabled = true; - } - m_mouse.set_start_position_2D_as_invalid(); -//#endif - } - else if (evt.Leaving()) { - _deactivate_undo_redo_toolbar_items(); - - // to remove hover on objects when the mouse goes out of this canvas - m_mouse.position = Vec2d(-1.0, -1.0); - m_dirty = true; - } - else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) { - if (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu()) - return; - - // If user pressed left or right button we first check whether this happened - // on a volume or not. - m_layers_editing.state = LayersEditing::Unknown; - if (layer_editing_object_idx != -1 && m_layers_editing.bar_rect_contains(*this, pos(0), pos(1))) { - // A volume is selected and the mouse is inside the layer thickness bar. - // Start editing the layer height. - m_layers_editing.state = LayersEditing::Editing; - _perform_layer_editing_action(&evt); - } - else { - const bool rectangle_selection_dragging = m_rectangle_selection.is_dragging(); - if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) { - if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && - m_gizmos.get_current_type() != GLGizmosManager::FdmSupports && - m_gizmos.get_current_type() != GLGizmosManager::Seam && - m_gizmos.get_current_type() != GLGizmosManager::Cut && - m_gizmos.get_current_type() != GLGizmosManager::Measure && - m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { - m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); - m_dirty = true; - } - } - - // Select volume in this 3D canvas. - // Don't deselect a volume if layer editing is enabled or any gizmo is active. We want the object to stay selected - // during the scene manipulation. - - if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) && !rectangle_selection_dragging) { - if (evt.LeftDown() && !m_hover_volume_idxs.empty()) { - int volume_idx = get_first_hover_volume_idx(); - bool already_selected = m_selection.contains_volume(volume_idx); - bool shift_down = evt.ShiftDown(); - - Selection::IndicesList curr_idxs = m_selection.get_volume_idxs(); - - if (already_selected && shift_down) - m_selection.remove(volume_idx); - else { - m_selection.add(volume_idx, !shift_down, true); - m_mouse.drag.move_requires_threshold = !already_selected; - if (already_selected) - m_mouse.set_move_start_threshold_position_2D_as_invalid(); - else - m_mouse.drag.move_start_threshold_position_2D = pos; - } - - // propagate event through callback - if (curr_idxs != m_selection.get_volume_idxs()) { - if (m_selection.is_empty()) - m_gizmos.reset_all_states(); - else - m_gizmos.refresh_on_off_state(); - - m_gizmos.update_data(); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - m_dirty = true; - } - } - } - - if (!m_hover_volume_idxs.empty() && !m_rectangle_selection.is_dragging()) { - if (evt.LeftDown() && m_moving_enabled && m_mouse.drag.move_volume_idx == -1) { - // Only accept the initial position, if it is inside the volume bounding box. - const int volume_idx = get_first_hover_volume_idx(); - BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); - volume_bbox.offset(1.0); - const bool is_cut_connector_selected = m_selection.is_any_connector(); - if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position) && !is_cut_connector_selected) { - m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None; - // The dragging operation is initiated. - m_mouse.drag.move_volume_idx = volume_idx; - m_selection.setup_cache(); - if (!evt.CmdDown()) - m_mouse.drag.start_position_3D = m_mouse.scene_position; - m_sequential_print_clearance_first_displacement = true; -#if !ENABLE_RAYCAST_PICKING - m_moving = true; -#endif // !ENABLE_RAYCAST_PICKING - } - } - } - } - } - else if (evt.Dragging() && evt.LeftIsDown() && !evt.CmdDown() && m_layers_editing.state == LayersEditing::Unknown && - m_mouse.drag.move_volume_idx != -1 && m_mouse.is_start_position_3D_defined()) { - if (!m_mouse.drag.move_requires_threshold) { - m_mouse.dragging = true; - Vec3d cur_pos = m_mouse.drag.start_position_3D; - // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag - if (m_selection.contains_volume(get_first_hover_volume_idx())) { - const Camera& camera = wxGetApp().plater()->get_camera(); - if (std::abs(camera.get_dir_forward().z()) < EPSILON) { - // side view -> move selected volumes orthogonally to camera view direction - const Linef3 ray = mouse_ray(pos); - const Vec3d dir = ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - const Vec3d inters = ray.a + (m_mouse.drag.start_position_3D - ray.a).dot(dir) / dir.squaredNorm() * dir; - // vector from the starting position to the found intersection - const Vec3d inters_vec = inters - m_mouse.drag.start_position_3D; - - const Vec3d camera_right = camera.get_dir_right(); - const Vec3d camera_up = camera.get_dir_up(); - - // finds projection of the vector along the camera axes - const double projection_x = inters_vec.dot(camera_right); - const double projection_z = inters_vec.dot(camera_up); - - // apply offset - cur_pos = m_mouse.drag.start_position_3D + projection_x * camera_right + projection_z * camera_up; - } - else { - // Generic view - // Get new position at the same Z of the initial click point. - cur_pos = mouse_ray(pos).intersect_plane(m_mouse.drag.start_position_3D.z()); - } - } - -#if ENABLE_WORLD_COORDINATE -#if ENABLE_RAYCAST_PICKING - m_moving = true; -#endif // ENABLE_RAYCAST_PICKING - TransformationType trafo_type; - trafo_type.set_relative(); - m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type); -#else - m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); -#endif // ENABLE_WORLD_COORDINATE - if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) - update_sequential_clearance(); - wxGetApp().obj_manipul()->set_dirty(); - m_dirty = true; - } - } - else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) { - // keeps the mouse position updated while dragging the selection rectangle - m_mouse.position = pos.cast(); - m_rectangle_selection.dragging(m_mouse.position); - m_dirty = true; - } - else if (evt.Dragging()) { - m_mouse.dragging = true; - - if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) { - if (m_layers_editing.state == LayersEditing::Editing) { - _perform_layer_editing_action(&evt); - m_mouse.position = pos.cast(); - } - } - // do not process the dragging if the left mouse was set down in another canvas - else if (evt.LeftIsDown()) { - // if dragging over blank area with left button, rotate -#if ENABLE_RAYCAST_PICKING - if (!m_moving) { -#endif // ENABLE_RAYCAST_PICKING - if ((any_gizmo_active || evt.CmdDown() || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) { - const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.0) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.0); - if (wxGetApp().app_config->get("use_free_camera") == "1") - // Virtual track ball (similar to the 3DConnexion mouse). - wxGetApp().plater()->get_camera().rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.0)); - else { - // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation. - // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(), - // which checks an atomics (flushes CPU caches). - // See GH issue #3816. - Camera& camera = wxGetApp().plater()->get_camera(); - camera.recover_from_free_camera(); - camera.rotate_on_sphere(rot.x(), rot.y(), current_printer_technology() != ptSLA); - } - - m_dirty = true; - } - m_mouse.drag.start_position_3D = Vec3d((double)pos.x(), (double)pos.y(), 0.0); -#if ENABLE_RAYCAST_PICKING - } -#endif // ENABLE_RAYCAST_PICKING - } - else if (evt.MiddleIsDown() || evt.RightIsDown()) { - // If dragging over blank area with right/middle button, pan. - if (m_mouse.is_start_position_2D_defined()) { - // get point in model space at Z = 0 - float z = 0.0f; - const Vec3d cur_pos = _mouse_to_3d(pos, &z); - const Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z); - Camera& camera = wxGetApp().plater()->get_camera(); - if (wxGetApp().app_config->get("use_free_camera") != "1") - // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation. - // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(), - // which checks an atomics (flushes CPU caches). - // See GH issue #3816. - camera.recover_from_free_camera(); - - camera.set_target(camera.get_target() + orig - cur_pos); - m_dirty = true; - } - - m_mouse.drag.start_position_2D = pos; - } - } - else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) { -#if ENABLE_RAYCAST_PICKING - m_mouse.position = pos.cast(); -#endif // ENABLE_RAYCAST_PICKING - - if (m_layers_editing.state != LayersEditing::Unknown) { - m_layers_editing.state = LayersEditing::Unknown; - _stop_timer(); - m_layers_editing.accept_changes(*this); - } - else if (m_mouse.drag.move_volume_idx != -1 && m_mouse.dragging) { - do_move(L("Move Object")); - wxGetApp().obj_manipul()->set_dirty(); - // Let the plater know that the dragging finished, so a delayed refresh - // of the scene with the background processing data should be performed. - post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); - } - else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging()) { - if (evt.ShiftDown() || evt.AltDown()) - _update_selection_from_hover(); - - m_rectangle_selection.stop_dragging(); - } - else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && !is_layers_editing_enabled()) { - // deselect and propagate event through callback - if (!evt.ShiftDown() && (!any_gizmo_active || !evt.CmdDown()) && m_picking_enabled) - deselect_all(); - } - else if (evt.RightUp()) { -#if !ENABLE_RAYCAST_PICKING - m_mouse.position = pos.cast(); -#endif // !ENABLE_RAYCAST_PICKING - // forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while - // the context menu is already shown - render(); - if (!m_hover_volume_idxs.empty()) { - // if right clicking on volume, propagate event through callback (shows context menu) - int volume_idx = get_first_hover_volume_idx(); - if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower - && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::Measure)) // disable context menu when the gizmo is open - { - // forces the selection of the volume - /* m_selection.add(volume_idx); // #et_FIXME_if_needed - * To avoid extra "Add-Selection" snapshots, - * call add() with check_for_already_contained=true - * */ - m_selection.add(volume_idx, true, true); - m_gizmos.refresh_on_off_state(); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - m_gizmos.update_data(); - wxGetApp().obj_manipul()->set_dirty(); - // forces a frame render to update the view before the context menu is shown - render(); - } - } - Vec2d logical_pos = pos.cast(); -#if ENABLE_RETINA_GL - const float factor = m_retina_helper->get_scale_factor(); - logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); -#endif // ENABLE_RETINA_GL - if (!m_mouse.dragging) { - // do not post the event if the user is panning the scene - // or if right click was done over the wipe tower - const bool post_right_click_event = (m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower) && - m_gizmos.get_current_type() != GLGizmosManager::Measure; - if (post_right_click_event) - post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() })); - } - } - - mouse_up_cleanup(); - } - else if (evt.Moving()) { - m_mouse.position = pos.cast(); - - // updates gizmos overlay - if (m_selection.is_empty()) - m_gizmos.reset_all_states(); - - m_dirty = true; - } - else - evt.Skip(); - - // Detection of doubleclick on text to open emboss edit window - if (evt.LeftDClick() && m_gizmos.get_current() == nullptr && !m_hover_volume_idxs.empty()) { - for (int hover_volume_id : m_hover_volume_idxs) { - const GLVolume &hover_gl_volume = *m_volumes.volumes[hover_volume_id]; - const ModelObject* hover_object = m_model->objects[hover_gl_volume.object_idx()]; - int hover_volume_idx = hover_gl_volume.volume_idx(); - const ModelVolume* hover_volume = hover_object->volumes[hover_volume_idx]; - if (hover_volume->text_configuration.has_value()) { - //m_selection.set_mode(Selection::EMode::Volume); - //m_selection.add(hover_volume_id); // add whole instance - m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id}); - m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss); - wxGetApp().obj_list()->update_selections(); - return; - } - } - } - - if (m_moving) - show_sinking_contours(); - -#ifdef __WXMSW__ - if (on_enter_workaround) - m_mouse.position = Vec2d(-1., -1.); -#endif /* __WXMSW__ */ -} - -void GLCanvas3D::on_paint(wxPaintEvent& evt) -{ - if (m_initialized) - m_dirty = true; - else - // Call render directly, so it gets initialized immediately, not from On Idle handler. - this->render(); -} - -void GLCanvas3D::on_set_focus(wxFocusEvent& evt) -{ - m_tooltip_enabled = false; - _refresh_if_shown_on_screen(); - m_tooltip_enabled = true; -} - -Size GLCanvas3D::get_canvas_size() const -{ - int w = 0; - int h = 0; - - if (m_canvas != nullptr) - m_canvas->GetSize(&w, &h); - -#if ENABLE_RETINA_GL - const float factor = m_retina_helper->get_scale_factor(); - w *= factor; - h *= factor; -#else - const float factor = 1.0f; -#endif - - return Size(w, h, factor); -} - -Vec2d GLCanvas3D::get_local_mouse_position() const -{ - if (m_canvas == nullptr) - return Vec2d::Zero(); - - wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition()); - const double factor = -#if ENABLE_RETINA_GL - m_retina_helper->get_scale_factor(); -#else - 1.0; -#endif - return Vec2d(factor * mouse_pos.x, factor * mouse_pos.y); -} - -void GLCanvas3D::set_tooltip(const std::string& tooltip) -{ - if (m_canvas != nullptr) - m_tooltip.set_text(tooltip); -} - -void GLCanvas3D::do_move(const std::string& snapshot_type) -{ - if (m_model == nullptr) - return; - - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(snapshot_type)); - - std::set> done; // keeps track of modified instances - bool object_moved = false; - Vec3d wipe_tower_origin = Vec3d::Zero(); - - Selection::EMode selection_mode = m_selection.get_mode(); - - for (const GLVolume* v : m_volumes.volumes) { - int object_idx = v->object_idx(); - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); - - std::pair done_id(object_idx, instance_idx); - - if (0 <= object_idx && object_idx < (int)m_model->objects.size()) { - done.insert(done_id); - - // Move instances/volumes - ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) -#if ENABLE_WORLD_COORDINATE - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); -#else - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); -#endif // ENABLE_WORLD_COORDINATE - else if (selection_mode == Selection::Volume) -#if ENABLE_WORLD_COORDINATE - model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); -#else - model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); -#endif // ENABLE_WORLD_COORDINATE - - object_moved = true; - model_object->invalidate_bounding_box(); - } - } - else if (v->is_wipe_tower) - // Move a wipe tower proxy. - wipe_tower_origin = v->get_volume_offset(); - } - - // Fixes flying instances - for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; - const double shift_z = m->get_instance_min_z(i.second); - if (current_printer_technology() == ptSLA || shift_z > SINKING_Z_THRESHOLD) { - const Vec3d shift(0.0, 0.0, -shift_z); - m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); - } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); - } - - // if the selection is not valid to allow for layer editing after the move, we need to turn off the tool if it is running - // similar to void Plater::priv::selection_changed() - if (!wxGetApp().plater()->can_layers_editing() && is_layers_editing_enabled()) - post_event(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); - - if (object_moved) - post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED)); - - if (wipe_tower_origin != Vec3d::Zero()) - post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin))); - - reset_sequential_print_clearance(); - - m_dirty = true; -} - -void GLCanvas3D::do_rotate(const std::string& snapshot_type) -{ - if (m_model == nullptr) - return; - - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(snapshot_type)); - - // stores current min_z of instances - std::map, double> min_zs; - for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { - const ModelObject* obj = m_model->objects[i]; - for (int j = 0; j < static_cast(obj->instances.size()); ++j) { - if (snapshot_type == L("Gizmo-Place on Face") && m_selection.get_object_idx() == i) { - // This means we are flattening this object. In that case pretend - // that it is not sinking (even if it is), so it is placed on bed - // later on (whatever is sinking will be left sinking). - min_zs[{ i, j }] = SINKING_Z_THRESHOLD; - } - else - min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); - - } - } - - std::set> done; // keeps track of modified instances - - Selection::EMode selection_mode = m_selection.get_mode(); - - for (const GLVolume* v : m_volumes.volumes) { - if (v->is_wipe_tower) { - const Vec3d offset = v->get_volume_offset(); - post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset.x(), offset.y(), v->get_volume_rotation().z()))); - } - const int object_idx = v->object_idx(); - if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) - continue; - - const int instance_idx = v->instance_idx(); - const int volume_idx = v->volume_idx(); - - done.insert(std::pair(object_idx, instance_idx)); - - // Rotate instances/volumes. - ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) { -#if ENABLE_WORLD_COORDINATE - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); -#else - model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); -#endif // ENABLE_WORLD_COORDINATE - } - else if (selection_mode == Selection::Volume) { -#if ENABLE_WORLD_COORDINATE - model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); -#else - model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); - model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); -#endif // ENABLE_WORLD_COORDINATE - } - model_object->invalidate_bounding_box(); - } - } - - // Fixes sinking/flying instances - for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; - const double shift_z = m->get_instance_min_z(i.second); - // leave sinking instances as sinking - if (min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { - const Vec3d shift(0.0, 0.0, -shift_z); - m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); - } - - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); - } - - if (!done.empty()) - post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); - - m_dirty = true; -} - -void GLCanvas3D::do_scale(const std::string& snapshot_type) -{ - if (m_model == nullptr) - return; - - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(snapshot_type)); - - // stores current min_z of instances - std::map, double> min_zs; - if (!snapshot_type.empty()) { - for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { - const ModelObject* obj = m_model->objects[i]; - for (int j = 0; j < static_cast(obj->instances.size()); ++j) { - min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); - } - } - } - - std::set> done; // keeps track of modified instances - - Selection::EMode selection_mode = m_selection.get_mode(); - - for (const GLVolume* v : m_volumes.volumes) { - const int object_idx = v->object_idx(); - if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) - continue; - - const int instance_idx = v->instance_idx(); - const int volume_idx = v->volume_idx(); - - done.insert(std::pair(object_idx, instance_idx)); - - // Rotate instances/volumes - ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) { -#if ENABLE_WORLD_COORDINATE - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); -#else - model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); -#endif // ENABLE_WORLD_COORDINATE - } - else if (selection_mode == Selection::Volume) { -#if ENABLE_WORLD_COORDINATE - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); - model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); -#else - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); - model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); - model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); -#endif // ENABLE_WORLD_COORDINATE - } - model_object->invalidate_bounding_box(); - } - } - - // Fixes sinking/flying instances - for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; - const double shift_z = m->get_instance_min_z(i.second); - // leave sinking instances as sinking - if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { - const Vec3d shift(0.0, 0.0, -shift_z); - m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); - } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); - } - - if (!done.empty()) - post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED)); - - m_dirty = true; -} - -void GLCanvas3D::do_mirror(const std::string& snapshot_type) -{ - if (m_model == nullptr) - return; - - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(snapshot_type)); - - // stores current min_z of instances - std::map, double> min_zs; - if (!snapshot_type.empty()) { - for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { - const ModelObject* obj = m_model->objects[i]; - for (int j = 0; j < static_cast(obj->instances.size()); ++j) { - min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); - } - } - } - - std::set> done; // keeps track of modified instances - - Selection::EMode selection_mode = m_selection.get_mode(); - - for (const GLVolume* v : m_volumes.volumes) { - int object_idx = v->object_idx(); - if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) - continue; - - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); - - done.insert(std::pair(object_idx, instance_idx)); - - // Mirror instances/volumes - ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) -#if ENABLE_WORLD_COORDINATE - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); -#else - model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror()); -#endif // ENABLE_WORLD_COORDINATE - else if (selection_mode == Selection::Volume) -#if ENABLE_WORLD_COORDINATE - model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); -#else - model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror()); -#endif // ENABLE_WORLD_COORDINATE - - model_object->invalidate_bounding_box(); - } - } - - // Fixes sinking/flying instances - for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; - double shift_z = m->get_instance_min_z(i.second); - // leave sinking instances as sinking - if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { - Vec3d shift(0.0, 0.0, -shift_z); - m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); - } - wxGetApp().obj_list()->update_info_items(static_cast(i.first)); - } - - post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - - m_dirty = true; -} - -#if ENABLE_WORLD_COORDINATE -void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) -{ - if (m_model == nullptr) - return; - - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(snapshot_type)); - - std::set> done; // keeps track of modified instances - - const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); - - for (unsigned int id : idxs) { - const GLVolume* v = m_volumes.volumes[id]; - int object_idx = v->object_idx(); - if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) - continue; - - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); - - done.insert(std::pair(object_idx, instance_idx)); - - ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); - model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); - model_object->invalidate_bounding_box(); - } - } - - post_event(SimpleEvent(EVT_GLCANVAS_RESET_SKEW)); - - m_dirty = true; -} -#endif // ENABLE_WORLD_COORDINATE - -void GLCanvas3D::update_gizmos_on_off_state() -{ - set_as_dirty(); - m_gizmos.update_data(); - m_gizmos.refresh_on_off_state(); -} - -void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on) -{ - m_sidebar_field = focus_on ? opt_key : ""; - if (!m_sidebar_field.empty()) - m_gizmos.reset_all_states(); - - m_dirty = true; -} - -void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) -{ - std::string field = "layer_" + std::to_string(type) + "_" + float_to_string_decimal_point(range.first) + "_" + float_to_string_decimal_point(range.second); - handle_sidebar_focus_event(field, true); -} - -void GLCanvas3D::update_ui_from_settings() -{ - m_dirty = true; - -#if __APPLE__ - // Update OpenGL scaling on OSX after the user toggled the "use_retina_opengl" settings in Preferences dialog. - const float orig_scaling = m_retina_helper->get_scale_factor(); - - const bool use_retina = wxGetApp().app_config->get("use_retina_opengl") == "1"; - BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Use Retina OpenGL: " << use_retina; - m_retina_helper->set_use_retina(use_retina); - const float new_scaling = m_retina_helper->get_scale_factor(); - - if (new_scaling != orig_scaling) { - BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Scaling factor: " << new_scaling; - - Camera& camera = wxGetApp().plater()->get_camera(); - camera.set_zoom(camera.get_zoom() * new_scaling / orig_scaling); - _refresh_if_shown_on_screen(); - } -#endif // ENABLE_RETINA_GL - - if (wxGetApp().is_editor()) - wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get("show_collapse_button") == "1"); -} - -GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const -{ - WipeTowerInfo wti; - - for (const GLVolume* vol : m_volumes.volumes) { - if (vol->is_wipe_tower) { - wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), - m_config->opt_float("wipe_tower_y")); - wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); - const BoundingBoxf3& bb = vol->bounding_box(); - wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)}; - break; - } - } - - return wti; -} - -Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) -{ - float z0 = 0.0f; - float z1 = 1.0f; - return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)); -} - -double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const -{ - const BoundingBoxf& bbox = m_bed.build_volume().bounding_volume2d(); - return factor * std::max(bbox.size()[0], bbox.size()[1]); -} - -void GLCanvas3D::set_cursor(ECursorType type) -{ - if ((m_canvas != nullptr) && (m_cursor_type != type)) - { - switch (type) - { - case Standard: { m_canvas->SetCursor(*wxSTANDARD_CURSOR); break; } - case Cross: { m_canvas->SetCursor(*wxCROSS_CURSOR); break; } - } - - m_cursor_type = type; - } -} - -void GLCanvas3D::msw_rescale() -{ - m_gcode_viewer.invalidate_legend(); -} - -void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() -{ - std::string new_tooltip = _u8L("Switch to Settings") + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; - - m_main_toolbar.set_tooltip(get_main_toolbar_item_id("settings"), new_tooltip); -} - -bool GLCanvas3D::has_toolpaths_to_export() const -{ - return m_gcode_viewer.can_export_toolpaths(); -} - -void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const -{ - m_gcode_viewer.export_toolpaths_to_obj(filename); -} - -void GLCanvas3D::mouse_up_cleanup() -{ - m_moving = false; - m_mouse.drag.move_volume_idx = -1; - m_mouse.set_start_position_3D_as_invalid(); - m_mouse.set_start_position_2D_as_invalid(); - m_mouse.dragging = false; - m_mouse.ignore_left_up = false; - m_dirty = true; - - if (m_canvas->HasCapture()) - m_canvas->ReleaseMouse(); -} - -void GLCanvas3D::update_sequential_clearance() -{ - if (current_printer_technology() != ptFFF || !fff_print()->config().complete_objects) - return; - - if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) - return; - - // collects instance transformations from volumes - // first define temporary cache - unsigned int instances_count = 0; - std::vector>> instance_transforms; - for (size_t obj = 0; obj < m_model->objects.size(); ++obj) { - instance_transforms.emplace_back(std::vector>()); - const ModelObject* model_object = m_model->objects[obj]; - for (size_t i = 0; i < model_object->instances.size(); ++i) { - instance_transforms[obj].emplace_back(std::optional()); - ++instances_count; - } - } - - if (instances_count == 1) - return; - - // second fill temporary cache with data from volumes - for (const GLVolume* v : m_volumes.volumes) { - if (v->is_modifier || v->is_wipe_tower) - continue; - - auto& transform = instance_transforms[v->object_idx()][v->instance_idx()]; - if (!transform.has_value()) - transform = v->get_instance_transformation(); - } - - // calculates objects 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) - // this is done only the first time this method is called while moving the mouse, - // the results are then cached for following displacements - if (m_sequential_print_clearance_first_displacement) { - m_sequential_print_clearance.m_hull_2d_cache.clear(); - float shrink_factor = static_cast(scale_(0.5 * fff_print()->config().extruder_clearance_radius.value - EPSILON)); - double mitter_limit = scale_(0.1); - m_sequential_print_clearance.m_hull_2d_cache.reserve(m_model->objects.size()); - for (size_t i = 0; i < m_model->objects.size(); ++i) { - ModelObject* model_object = m_model->objects[i]; - ModelInstance* model_instance0 = model_object->instances.front(); -#if ENABLE_WORLD_COORDINATE - Geometry::Transformation trafo = model_instance0->get_transformation(); - trafo.set_offset({ 0.0, 0.0, model_instance0->get_offset().z() }); - Polygon hull_2d = offset(model_object->convex_hull_2d(trafo.get_matrix()), - // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects - // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. - shrink_factor, - jtRound, mitter_limit).front(); -#else - Polygon hull_2d = offset(model_object->convex_hull_2d(Geometry::assemble_transform({ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), - model_instance0->get_scaling_factor(), model_instance0->get_mirror())), - // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects - // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. - shrink_factor, - jtRound, mitter_limit).front(); -#endif // ENABLE_WORLD_COORDINATE - - Pointf3s& cache_hull_2d = m_sequential_print_clearance.m_hull_2d_cache.emplace_back(Pointf3s()); - cache_hull_2d.reserve(hull_2d.points.size()); - for (const Point& p : hull_2d.points) { - cache_hull_2d.emplace_back(unscale(p.x()), unscale(p.y()), 0.0); - } - } - m_sequential_print_clearance_first_displacement = false; - } - - // calculates instances 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) - Polygons polygons; - polygons.reserve(instances_count); - for (size_t i = 0; i < instance_transforms.size(); ++i) { - const auto& instances = instance_transforms[i]; - double rotation_z0 = instances.front()->get_rotation().z(); - for (const auto& instance : instances) { - Geometry::Transformation transformation; - const Vec3d& offset = instance->get_offset(); - transformation.set_offset({ offset.x(), offset.y(), 0.0 }); - transformation.set_rotation(Z, instance->get_rotation().z() - rotation_z0); - const Transform3d& trafo = transformation.get_matrix(); - const Pointf3s& hull_2d = m_sequential_print_clearance.m_hull_2d_cache[i]; - Points inst_pts; - inst_pts.reserve(hull_2d.size()); - for (size_t j = 0; j < hull_2d.size(); ++j) { - const Vec3d p = trafo * hull_2d[j]; - inst_pts.emplace_back(scaled(p.x()), scaled(p.y())); - } - polygons.emplace_back(Geometry::convex_hull(std::move(inst_pts))); - } - } - - // sends instances 2d hulls to be rendered - set_sequential_print_clearance_visible(true); - set_sequential_print_clearance_render_fill(false); - set_sequential_print_clearance_polygons(polygons); -} - -bool GLCanvas3D::is_object_sinking(int object_idx) const -{ - for (const GLVolume* v : m_volumes.volumes) { - if (v->object_idx() == object_idx && (v->is_sinking() || (!v->is_modifier && v->is_below_printbed()))) - return true; - } - return false; -} - -void GLCanvas3D::apply_retina_scale(Vec2d &screen_coordinate) const -{ -#if ENABLE_RETINA_GL - double scale = static_cast(m_retina_helper->get_scale_factor()); - screen_coordinate *= scale; -#endif // ENABLE_RETINA_GL -} - -bool GLCanvas3D::_is_shown_on_screen() const -{ - return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; -} - -// Getter for the const char*[] -static bool string_getter(const bool is_undo, int idx, const char** out_text) -{ - return wxGetApp().plater()->undo_redo_string_getter(is_undo, idx, out_text); -} - -bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) -{ - bool action_taken = false; - - ImGuiWrapper* imgui = wxGetApp().imgui(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - imgui->set_next_window_pos(pos_x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#else - const float x = pos_x * (float)wxGetApp().plater()->get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); - imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - std::string title = is_undo ? L("Undo History") : L("Redo History"); - imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - int hovered = m_imgui_undo_redo_hovered_pos; - int selected = -1; - float em = static_cast(wxGetApp().em_unit()); -#if ENABLE_RETINA_GL - em *= m_retina_helper->get_scale_factor(); -#endif - - if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected, m_mouse_wheel)) - m_imgui_undo_redo_hovered_pos = hovered; - else - m_imgui_undo_redo_hovered_pos = -1; - - if (selected >= 0) { - is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); - action_taken = true; - } - - imgui->text(wxString::Format(is_undo ? _L_PLURAL("Undo %1$d Action", "Undo %1$d Actions", hovered + 1) : _L_PLURAL("Redo %1$d Action", "Redo %1$d Actions", hovered + 1), hovered + 1)); - - imgui->end(); - - return action_taken; -} - -// Getter for the const char*[] for the search list -static bool search_string_getter(int idx, const char** label, const char** tooltip) -{ - return wxGetApp().plater()->search_string_getter(idx, label, tooltip); -} - -bool GLCanvas3D::_render_search_list(float pos_x) -{ - bool action_taken = false; - ImGuiWrapper* imgui = wxGetApp().imgui(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#else - const float x = /*pos_x * (float)wxGetApp().plater()->get_camera().get_zoom() + */0.5f * (float)get_canvas_size().get_width(); - imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - std::string title = L("Search"); - imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - int selected = -1; - bool edited = false; - float em = static_cast(wxGetApp().em_unit()); -#if ENABLE_RETINA_GL - em *= m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - - Sidebar& sidebar = wxGetApp().sidebar(); - - std::string& search_line = sidebar.get_search_line(); - char *s = new char[255]; - strcpy(s, search_line.empty() ? _u8L("Enter a search term").c_str() : search_line.c_str()); - - imgui->search_list(ImVec2(45 * em, 30 * em), &search_string_getter, s, - sidebar.get_searcher().view_params, - selected, edited, m_mouse_wheel, wxGetApp().is_localized()); - - search_line = s; - delete [] s; - if (search_line == _u8L("Enter a search term")) - search_line.clear(); - - if (edited) - sidebar.search(); - - if (selected >= 0) { - // selected == 9999 means that Esc kye was pressed - /*// revert commit https://github.com/prusa3d/PrusaSlicer/commit/91897589928789b261ca0dc735ffd46f2b0b99f2 - if (selected == 9999) - action_taken = true; - else - sidebar.jump_to_option(selected);*/ - if (selected != 9999) { - imgui->end(); // end imgui before the jump to option - sidebar.jump_to_option(selected); - return true; - } - action_taken = true; - } - - imgui->end(); - - return action_taken; -} - -bool GLCanvas3D::_render_arrange_menu(float pos_x) -{ - ImGuiWrapper *imgui = wxGetApp().imgui(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#else - auto canvas_w = float(get_canvas_size().get_width()); - const float x = pos_x * float(wxGetApp().plater()->get_camera().get_zoom()) + 0.5f * canvas_w; - imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - ArrangeSettings settings = get_arrange_settings(); - ArrangeSettings &settings_out = get_arrange_settings(); - - auto &appcfg = wxGetApp().app_config; - PrinterTechnology ptech = current_printer_technology(); - - bool settings_changed = false; - float dist_min = 0.f; - float dist_bed_min = 0.f; - std::string dist_key = "min_object_distance"; - std::string dist_bed_key = "min_bed_distance"; - std::string rot_key = "enable_rotation"; - std::string postfix; - - if (ptech == ptSLA) { - postfix = "_sla"; - } else if (ptech == ptFFF) { - auto co_opt = m_config->option("complete_objects"); - if (co_opt && co_opt->value) { - dist_min = float(min_object_distance(*m_config)); - postfix = "_fff_seq_print"; - } else { - dist_min = 0.f; - postfix = "_fff"; - } - } - - dist_key += postfix; - dist_bed_key += postfix; - rot_key += postfix; - - imgui->text(GUI::format_wxstr(_L("Press %1%left mouse button to enter the exact value"), shortkey_ctrl_prefix())); - - if (imgui->slider_float(_L("Spacing"), &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance) { - settings.distance = std::max(dist_min, settings.distance); - settings_out.distance = settings.distance; - appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); - settings_changed = true; - } - - if (imgui->slider_float(_L("Spacing from bed"), &settings.distance_from_bed, dist_bed_min, 100.0f, "%5.2f") || dist_bed_min > settings.distance_from_bed) { - settings.distance_from_bed = std::max(dist_bed_min, settings.distance_from_bed); - settings_out.distance_from_bed = settings.distance_from_bed; - appcfg->set("arrange", dist_bed_key.c_str(), float_to_string_decimal_point(settings_out.distance_from_bed)); - settings_changed = true; - } - - if (imgui->checkbox(_L("Enable rotations (slow)"), settings.enable_rotation)) { - settings_out.enable_rotation = settings.enable_rotation; - appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); - settings_changed = true; - } - - ImGui::Separator(); - - if (imgui->button(_L("Reset"))) { - settings_out = ArrangeSettings{}; - settings_out.distance = std::max(dist_min, settings_out.distance); - appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); - appcfg->set("arrange", dist_bed_key.c_str(), float_to_string_decimal_point(settings_out.distance_from_bed)); - appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); - settings_changed = true; - } - - ImGui::SameLine(); - - if (imgui->button(_L("Arrange"))) { - wxGetApp().plater()->arrange(); - } - - imgui->end(); - - return settings_changed; -} - -#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT -static void debug_output_thumbnail(const ThumbnailData& thumbnail_data) -{ - // debug export of generated image - wxImage image(thumbnail_data.width, thumbnail_data.height); - image.InitAlpha(); - - for (unsigned int r = 0; r < thumbnail_data.height; ++r) - { - unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width; - for (unsigned int c = 0; c < thumbnail_data.width; ++c) - { - unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c); - image.SetRGB((int)c, (int)r, px[0], px[1], px[2]); - image.SetAlpha((int)c, (int)r, px[3]); - } - } - - image.SaveFile("C:/prusa/test/test.png", wxBITMAP_TYPE_PNG); -} -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - -void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) -{ - auto is_visible = [](const GLVolume& v) { - bool ret = v.printable; - ret &= (!v.shader_outside_printer_detection_enabled || !v.is_outside); - return ret; - }; - - GLVolumePtrs visible_volumes; - - for (GLVolume* vol : volumes.volumes) { - if (!vol->is_modifier && !vol->is_wipe_tower && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) { - if (!thumbnail_params.printable_only || is_visible(*vol)) - visible_volumes.emplace_back(vol); - } - } - - BoundingBoxf3 volumes_box; - if (!visible_volumes.empty()) { - for (const GLVolume* vol : visible_volumes) { - volumes_box.merge(vol->transformed_bounding_box()); - } - } - else - // This happens for empty projects - volumes_box = m_bed.extended_bounding_box(); - - Camera camera; - camera.set_type(camera_type); - camera.set_scene_box(scene_bounding_box()); -#if ENABLE_RAYCAST_PICKING - camera.set_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); - camera.apply_viewport(); -#else - camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); -#endif // ENABLE_RAYCAST_PICKING - camera.zoom_to_box(volumes_box); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Transform3d& view_matrix = camera.get_view_matrix(); -#else - camera.apply_view_matrix(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - double near_z = -1.0; - double far_z = -1.0; - - if (thumbnail_params.show_bed) { - // extends the near and far z of the frustrum to avoid the bed being clipped - - // box in eye space -#if ENABLE_LEGACY_OPENGL_REMOVAL - const BoundingBoxf3 t_bed_box = m_bed.extended_bounding_box().transformed(view_matrix); -#else - const BoundingBoxf3 t_bed_box = m_bed.extended_bounding_box().transformed(camera.get_view_matrix()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - near_z = -t_bed_box.max.z(); - far_z = -t_bed_box.min.z(); - } - - camera.apply_projection(volumes_box, near_z, far_z); - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - if (thumbnail_params.transparent_background) - glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); - - glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - shader->start_using(); - shader->set_uniform("emission_factor", 0.0f); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Transform3d& projection_matrix = camera.get_projection_matrix(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - for (GLVolume* vol : visible_volumes) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - vol->model.set_color((vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); -#else - shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - // the volume may have been deactivated by an active gizmo - const bool is_active = vol->is_active; - vol->is_active = true; -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Transform3d model_matrix = vol->world_matrix(); - shader->set_uniform("view_model_matrix", view_matrix * model_matrix); - shader->set_uniform("projection_matrix", projection_matrix); - const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); - shader->set_uniform("view_normal_matrix", view_normal_matrix); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - vol->render(); - vol->is_active = is_active; - } - - shader->stop_using(); - - glsafe(::glDisable(GL_DEPTH_TEST)); - - if (thumbnail_params.show_bed) -#if ENABLE_LEGACY_OPENGL_REMOVAL - _render_bed(view_matrix, projection_matrix, !camera.is_looking_downward(), false); -#else - _render_bed(!camera.is_looking_downward(), false); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // restore background color - if (thumbnail_params.transparent_background) - glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); -} - -void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) -{ - thumbnail_data.set(w, h); - if (!thumbnail_data.is_valid()) - return; - - bool multisample = m_multisample_allowed; - if (multisample) - glsafe(::glEnable(GL_MULTISAMPLE)); - - GLint max_samples; - glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples)); - GLsizei num_samples = max_samples / 2; - - GLuint render_fbo; - glsafe(::glGenFramebuffers(1, &render_fbo)); - glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)); - - GLuint render_tex = 0; - GLuint render_tex_buffer = 0; - if (multisample) { - // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 - glsafe(::glGenRenderbuffers(1, &render_tex_buffer)); - glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer)); - glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h)); - glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer)); - } - else { - glsafe(::glGenTextures(1, &render_tex)); - glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); - } - - GLuint render_depth; - glsafe(::glGenRenderbuffers(1, &render_depth)); - glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); - if (multisample) - glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h)); - else - glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h)); - - glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); - - GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; - glsafe(::glDrawBuffers(1, drawBufs)); - - if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { - _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); - - if (multisample) { - GLuint resolve_fbo; - glsafe(::glGenFramebuffers(1, &resolve_fbo)); - glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo)); - - GLuint resolve_tex; - glsafe(::glGenTextures(1, &resolve_tex)); - glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0)); - - glsafe(::glDrawBuffers(1, drawBufs)); - - if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { - glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo)); - glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo)); - glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); - - glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo)); - glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); - } - - glsafe(::glDeleteTextures(1, &resolve_tex)); - glsafe(::glDeleteFramebuffers(1, &resolve_fbo)); - } - else - glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - debug_output_thumbnail(thumbnail_data); -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - } - - glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); - glsafe(::glDeleteRenderbuffers(1, &render_depth)); - if (render_tex_buffer != 0) - glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer)); - if (render_tex != 0) - glsafe(::glDeleteTextures(1, &render_tex)); - glsafe(::glDeleteFramebuffers(1, &render_fbo)); - - if (multisample) - glsafe(::glDisable(GL_MULTISAMPLE)); -} - -void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) -{ - thumbnail_data.set(w, h); - if (!thumbnail_data.is_valid()) - return; - - bool multisample = m_multisample_allowed; - if (multisample) - glsafe(::glEnable(GL_MULTISAMPLE)); - - GLint max_samples; - glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples)); - GLsizei num_samples = max_samples / 2; - - GLuint render_fbo; - glsafe(::glGenFramebuffersEXT(1, &render_fbo)); - glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo)); - - GLuint render_tex = 0; - GLuint render_tex_buffer = 0; - if (multisample) { - // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 - glsafe(::glGenRenderbuffersEXT(1, &render_tex_buffer)); - glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_tex_buffer)); - glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_RGBA8, w, h)); - glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, render_tex_buffer)); - } - else { - glsafe(::glGenTextures(1, &render_tex)); - glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0)); - } - - GLuint render_depth; - glsafe(::glGenRenderbuffersEXT(1, &render_depth)); - glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth)); - if (multisample) - glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h)); - else - glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h)); - - glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth)); - - GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; - glsafe(::glDrawBuffers(1, drawBufs)); - - if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) { - _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); - - if (multisample) { - GLuint resolve_fbo; - glsafe(::glGenFramebuffersEXT(1, &resolve_fbo)); - glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo)); - - GLuint resolve_tex; - glsafe(::glGenTextures(1, &resolve_tex)); - glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, resolve_tex, 0)); - - glsafe(::glDrawBuffers(1, drawBufs)); - - if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) { - glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, render_fbo)); - glsafe(::glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, resolve_fbo)); - glsafe(::glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); - - glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, resolve_fbo)); - glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); - } - - glsafe(::glDeleteTextures(1, &resolve_tex)); - glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo)); - } - else - glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - debug_output_thumbnail(thumbnail_data); -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - } - - glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); - glsafe(::glDeleteRenderbuffersEXT(1, &render_depth)); - if (render_tex_buffer != 0) - glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer)); - if (render_tex != 0) - glsafe(::glDeleteTextures(1, &render_tex)); - glsafe(::glDeleteFramebuffersEXT(1, &render_fbo)); - - if (multisample) - glsafe(::glDisable(GL_MULTISAMPLE)); -} - -void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) -{ - // check that thumbnail size does not exceed the default framebuffer size - const Size& cnv_size = get_canvas_size(); - unsigned int cnv_w = (unsigned int)cnv_size.get_width(); - unsigned int cnv_h = (unsigned int)cnv_size.get_height(); - if (w > cnv_w || h > cnv_h) { - float ratio = std::min((float)cnv_w / (float)w, (float)cnv_h / (float)h); - w = (unsigned int)(ratio * (float)w); - h = (unsigned int)(ratio * (float)h); - } - - thumbnail_data.set(w, h); - if (!thumbnail_data.is_valid()) - return; - - _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); - - glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - debug_output_thumbnail(thumbnail_data); -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - - // restore the default framebuffer size to avoid flickering on the 3D scene -#if ENABLE_RAYCAST_PICKING - wxGetApp().plater()->get_camera().apply_viewport(); -#else - wxGetApp().plater()->get_camera().apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height()); -#endif // ENABLE_RAYCAST_PICKING -} - -bool GLCanvas3D::_init_toolbars() -{ - if (!_init_main_toolbar()) - return false; - - if (!_init_undoredo_toolbar()) - return false; - - if (!_init_view_toolbar()) - return false; - - if (!_init_collapse_toolbar()) - return false; - - return true; -} - -bool GLCanvas3D::_init_main_toolbar() -{ - if (!m_main_toolbar.is_enabled()) - return true; - - BackgroundTexture::Metadata background_data; - background_data.filename = "toolbar_background.png"; - background_data.left = 16; - background_data.top = 16; - background_data.right = 16; - background_data.bottom = 16; - - if (!m_main_toolbar.init(background_data)) { - // unable to init the toolbar texture, disable it - m_main_toolbar.set_enabled(false); - return true; - } - // init arrow -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!m_main_toolbar.init_arrow("toolbar_arrow_2.svg")) -#else - BackgroundTexture::Metadata arrow_data; - arrow_data.filename = "toolbar_arrow.svg"; - arrow_data.left = 0; - arrow_data.top = 0; - arrow_data.right = 0; - arrow_data.bottom = 0; - if (!m_main_toolbar.init_arrow(arrow_data)) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - BOOST_LOG_TRIVIAL(error) << "Main toolbar failed to load arrow texture."; - - // m_gizmos is created at constructor, thus we can init arrow here. -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!m_gizmos.init_arrow("toolbar_arrow_2.svg")) -#else - if (!m_gizmos.init_arrow(arrow_data)) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - BOOST_LOG_TRIVIAL(error) << "Gizmos manager failed to load arrow texture."; - -// m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical); - m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); - m_main_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); - m_main_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); - m_main_toolbar.set_border(5.0f); - m_main_toolbar.set_separator_size(5); - m_main_toolbar.set_gap_size(4); - - GLToolbarItem::Data item; - - item.name = "add"; - item.icon_filename = "add.svg"; - item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; - item.sprite_id = 0; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "delete"; - item.icon_filename = "remove.svg"; - item.tooltip = _utf8(L("Delete")) + " [Del]"; - item.sprite_id = 1; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "deleteall"; - item.icon_filename = "delete_all.svg"; - item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; - item.sprite_id = 2; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "arrange"; - item.icon_filename = "arrange.svg"; - item.tooltip = _utf8(L("Arrange")) + " [A]\n" + _utf8(L("Arrange selection")) + " [Shift+A]\n" + _utf8(L("Click right mouse button to show arrangement options")); - item.sprite_id = 3; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; - item.right.toggable = true; - item.right.render_callback = [this](float left, float right, float, float) { - if (m_canvas != nullptr) - _render_arrange_menu(0.5f * (left + right)); - }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.right.toggable = false; - item.right.render_callback = GLToolbarItem::Default_Render_Callback; - - if (!m_main_toolbar.add_separator()) - return false; - - item.name = "copy"; - item.icon_filename = "copy.svg"; - item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; - item.sprite_id = 4; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "paste"; - item.icon_filename = "paste.svg"; - item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; - item.sprite_id = 5; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; - - item.name = "more"; - item.icon_filename = "instance_add.svg"; - item.tooltip = _utf8(L("Add instance")) + " [+]"; - item.sprite_id = 6; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; - item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; - - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "fewer"; - item.icon_filename = "instance_remove.svg"; - item.tooltip = _utf8(L("Remove instance")) + " [-]"; - item.sprite_id = 7; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; - item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; - - item.name = "splitobjects"; - item.icon_filename = "split_objects.svg"; - item.tooltip = _utf8(L("Split to objects")); - item.sprite_id = 8; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; - item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - item.name = "splitvolumes"; - item.icon_filename = "split_parts.svg"; - item.tooltip = _utf8(L("Split to parts")); - item.sprite_id = 9; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; - item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; - - item.name = "settings"; - item.icon_filename = "settings.svg"; - item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; - item.sprite_id = 10; - item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; - item.visibility_callback = []() { return (wxGetApp().app_config->get("new_settings_layout_mode") == "1" || - wxGetApp().app_config->get("dlg_settings_layout_mode") == "1"); }; - item.left.action_callback = []() { wxGetApp().mainframe->select_tab(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - /* - if (!m_main_toolbar.add_separator()) - return false; - */ - - item.name = "search"; - item.icon_filename = "search_.svg"; - item.tooltip = _utf8(L("Search")) + " [" + GUI::shortkey_ctrl_prefix() + "F]"; - item.sprite_id = 11; - item.left.toggable = true; - item.left.render_callback = [this](float left, float right, float, float) { - if (m_canvas != nullptr) { - if (!m_canvas->HasFocus()) - m_canvas->SetFocus(); - if (_render_search_list(0.5f * (left + right))) - _deactivate_search_toolbar_item(); - } - }; - item.left.action_callback = GLToolbarItem::Default_Action_Callback; - item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; - item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; - - item.name = "layersediting"; - item.icon_filename = "layers_white.svg"; - item.tooltip = _utf8(L("Variable layer height")); - item.sprite_id = 12; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; - item.visibility_callback = [this]()->bool { - bool res = current_printer_technology() == ptFFF; - // turns off if changing printer technology - if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) - force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); - - return res; - }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; - item.left.render_callback = GLToolbarItem::Default_Render_Callback; - if (!m_main_toolbar.add_item(item)) - return false; - - return true; -} - -bool GLCanvas3D::_init_undoredo_toolbar() -{ - if (!m_undoredo_toolbar.is_enabled()) - return true; - - BackgroundTexture::Metadata background_data; - background_data.filename = "toolbar_background.png"; - background_data.left = 16; - background_data.top = 16; - background_data.right = 16; - background_data.bottom = 16; - - if (!m_undoredo_toolbar.init(background_data)) { - // unable to init the toolbar texture, disable it - m_undoredo_toolbar.set_enabled(false); - return true; - } - - // init arrow -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!m_undoredo_toolbar.init_arrow("toolbar_arrow_2.svg")) -#else - BackgroundTexture::Metadata arrow_data; - arrow_data.filename = "toolbar_arrow.svg"; - arrow_data.left = 0; - arrow_data.top = 0; - arrow_data.right = 0; - arrow_data.bottom = 0; - if (!m_undoredo_toolbar.init_arrow(arrow_data)) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - BOOST_LOG_TRIVIAL(error) << "Undo/Redo toolbar failed to load arrow texture."; - -// m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical); - m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); - m_undoredo_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); - m_undoredo_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); - m_undoredo_toolbar.set_border(5.0f); - m_undoredo_toolbar.set_separator_size(5); - m_undoredo_toolbar.set_gap_size(4); - - GLToolbarItem::Data item; - - item.name = "undo"; - item.icon_filename = "undo_toolbar.svg"; - item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]\n" + _utf8(L("Click right mouse button to open/close History")); - item.sprite_id = 0; - item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; - item.right.toggable = true; - item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; - item.right.render_callback = [this](float left, float right, float, float) { - if (m_canvas != nullptr) { - if (_render_undo_redo_stack(true, 0.5f * (left + right))) - _deactivate_undo_redo_toolbar_items(); - } - }; - item.enabling_callback = [this]()->bool { - bool can_undo = wxGetApp().plater()->can_undo(); - int id = m_undoredo_toolbar.get_item_id("undo"); - - std::string curr_additional_tooltip; - m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); - - std::string new_additional_tooltip; - if (can_undo) { - std::string action; - wxGetApp().plater()->undo_redo_topmost_string_getter(true, action); - new_additional_tooltip = (boost::format(_utf8(L("Next Undo action: %1%"))) % action).str(); - } - - if (new_additional_tooltip != curr_additional_tooltip) { - m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); - set_tooltip(""); - } - return can_undo; - }; - - if (!m_undoredo_toolbar.add_item(item)) - return false; - - item.name = "redo"; - item.icon_filename = "redo_toolbar.svg"; - item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]\n" + _utf8(L("Click right mouse button to open/close History")); - item.sprite_id = 1; - item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; - item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; - item.right.render_callback = [this](float left, float right, float, float) { - if (m_canvas != nullptr) { - if (_render_undo_redo_stack(false, 0.5f * (left + right))) - _deactivate_undo_redo_toolbar_items(); - } - }; - item.enabling_callback = [this]()->bool { - bool can_redo = wxGetApp().plater()->can_redo(); - int id = m_undoredo_toolbar.get_item_id("redo"); - - std::string curr_additional_tooltip; - m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); - - std::string new_additional_tooltip; - if (can_redo) { - std::string action; - wxGetApp().plater()->undo_redo_topmost_string_getter(false, action); - new_additional_tooltip = (boost::format(_utf8(L("Next Redo action: %1%"))) % action).str(); - } - - if (new_additional_tooltip != curr_additional_tooltip) { - m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); - set_tooltip(""); - } - return can_redo; - }; - - if (!m_undoredo_toolbar.add_item(item)) - return false; - /* - if (!m_undoredo_toolbar.add_separator()) - return false; - */ - return true; -} - -bool GLCanvas3D::_init_view_toolbar() -{ - return wxGetApp().plater()->init_view_toolbar(); -} - -bool GLCanvas3D::_init_collapse_toolbar() -{ - return wxGetApp().plater()->init_collapse_toolbar(); -} - -bool GLCanvas3D::_set_current() -{ - return m_context != nullptr && m_canvas->SetCurrent(*m_context); -} - -void GLCanvas3D::_resize(unsigned int w, unsigned int h) -{ - if (m_canvas == nullptr && m_context == nullptr) - return; - - const std::array new_size = { w, h }; - if (m_old_size == new_size) - return; - - m_old_size = new_size; - - auto *imgui = wxGetApp().imgui(); - imgui->set_display_size(static_cast(w), static_cast(h)); - const float font_size = 1.5f * wxGetApp().em_unit(); -#if ENABLE_RETINA_GL - imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); -#else - imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f); -#endif - - this->request_extra_frame(); - - // ensures that this canvas is current - _set_current(); -} - -BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const -{ - BoundingBoxf3 bb = volumes_bounding_box(); - - // The following is a workaround for gizmos not being taken in account when calculating the tight camera frustrum - // A better solution would ask the gizmo manager for the bounding box of the current active gizmo, if any - if (include_gizmos && m_gizmos.is_running()) { - const BoundingBoxf3 sel_bb = m_selection.get_bounding_box(); - const Vec3d sel_bb_center = sel_bb.center(); - const Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones(); - bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by)); - } - - const BoundingBoxf3 bed_bb = include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume(); - bb.merge(bed_bb); - - if (!m_main_toolbar.is_enabled()) - bb.merge(m_gcode_viewer.get_max_bounding_box()); - - // clamp max bb size with respect to bed bb size - if (!m_picking_enabled) { - static const double max_scale_factor = 2.0; - const Vec3d bb_size = bb.size(); - const Vec3d bed_bb_size = m_bed.build_volume().bounding_volume().size(); - - if ((bed_bb_size.x() > 0.0 && bb_size.x() > max_scale_factor * bed_bb_size.x()) || - (bed_bb_size.y() > 0.0 && bb_size.y() > max_scale_factor * bed_bb_size.y()) || - (bed_bb_size.z() > 0.0 && bb_size.z() > max_scale_factor * bed_bb_size.z())) { - const Vec3d bed_bb_center = bed_bb.center(); - const Vec3d extend_by = max_scale_factor * bed_bb_size; - bb = BoundingBoxf3(bed_bb_center - extend_by, bed_bb_center + extend_by); - } - } - - return bb; -} - -void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor) -{ - wxGetApp().plater()->get_camera().zoom_to_box(box, margin_factor); - m_dirty = true; -} - -void GLCanvas3D::_update_camera_zoom(double zoom) -{ - wxGetApp().plater()->get_camera().update_zoom(zoom); - m_dirty = true; -} - -void GLCanvas3D::_refresh_if_shown_on_screen() -{ - if (_is_shown_on_screen()) { - const Size& cnv_size = get_canvas_size(); - _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); - - // Because of performance problems on macOS, where PaintEvents are not delivered - // frequently enough, we call render() here directly when we can. - render(); - } -} - -#if ENABLE_RAYCAST_PICKING -void GLCanvas3D::_picking_pass() -{ - if (!m_picking_enabled || m_mouse.dragging || m_mouse.position == Vec2d(DBL_MAX, DBL_MAX) || m_gizmos.is_dragging()) { -#if ENABLE_RAYCAST_PICKING_DEBUG - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); - imgui.text("Picking disabled"); - imgui.end(); -#endif // ENABLE_RAYCAST_PICKING_DEBUG - return; - } - - m_hover_volume_idxs.clear(); - - const ClippingPlane clipping_plane = m_gizmos.get_clipping_plane().inverted_normal(); - const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(m_mouse.position, wxGetApp().plater()->get_camera(), &clipping_plane); - if (hit.is_valid()) { - switch (hit.type) - { - case SceneRaycaster::EType::Volume: - { - if (0 <= hit.raycaster_id && hit.raycaster_id < (int)m_volumes.volumes.size()) { - const GLVolume* volume = m_volumes.volumes[hit.raycaster_id]; - if (volume->is_active && !volume->disabled && (volume->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) { - // do not add the volume id if any gizmo is active and CTRL is pressed - if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) - m_hover_volume_idxs.emplace_back(hit.raycaster_id); - m_gizmos.set_hover_id(-1); - } - } - else - assert(false); - - break; - } - case SceneRaycaster::EType::Gizmo: - { - const Size& cnv_size = get_canvas_size(); - const bool inside = 0 <= m_mouse.position.x() && m_mouse.position.x() < cnv_size.get_width() && - 0 <= m_mouse.position.y() && m_mouse.position.y() < cnv_size.get_height(); - m_gizmos.set_hover_id(inside ? hit.raycaster_id : -1); - break; - } - case SceneRaycaster::EType::Bed: - { - m_gizmos.set_hover_id(-1); - break; - } - default: - { - assert(false); - break; - } - } - } - else - m_gizmos.set_hover_id(-1); - - _update_volumes_hover_state(); - -#if ENABLE_RAYCAST_PICKING_DEBUG - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); - std::string object_type = "None"; - switch (hit.type) - { - case SceneRaycaster::EType::Bed: { object_type = "Bed"; break; } - case SceneRaycaster::EType::Gizmo: { object_type = "Gizmo element"; break; } - case SceneRaycaster::EType::Volume: - { - if (m_volumes.volumes[hit.raycaster_id]->is_wipe_tower) - object_type = "Volume (Wipe tower)"; - else if (m_volumes.volumes[hit.raycaster_id]->volume_idx() == -int(slaposPad)) - object_type = "Volume (SLA pad)"; - else if (m_volumes.volumes[hit.raycaster_id]->volume_idx() == -int(slaposSupportTree)) - object_type = "Volume (SLA supports)"; - else if (m_volumes.volumes[hit.raycaster_id]->is_modifier) - object_type = "Volume (Modifier)"; - else - object_type = "Volume (Part)"; - break; - } - default: { break; } - } - - auto add_strings_row_to_table = [&imgui](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - imgui.text_colored(col_1_color, col_1.c_str()); - ImGui::TableSetColumnIndex(1); - imgui.text_colored(col_2_color, col_2.c_str()); - }; - - char buf[1024]; - if (hit.type != SceneRaycaster::EType::None) { - if (ImGui::BeginTable("Hit", 2)) { - add_strings_row_to_table("Object ID", ImGuiWrapper::COL_ORANGE_LIGHT, std::to_string(hit.raycaster_id), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - add_strings_row_to_table("Type", ImGuiWrapper::COL_ORANGE_LIGHT, object_type, ImGui::GetStyleColorVec4(ImGuiCol_Text)); - sprintf(buf, "%.3f, %.3f, %.3f", hit.position.x(), hit.position.y(), hit.position.z()); - add_strings_row_to_table("Position", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - sprintf(buf, "%.3f, %.3f, %.3f", hit.normal.x(), hit.normal.y(), hit.normal.z()); - add_strings_row_to_table("Normal", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - ImGui::EndTable(); - } - } - else - imgui.text("NO HIT"); - - ImGui::Separator(); - imgui.text("Registered for picking:"); - if (ImGui::BeginTable("Raycasters", 2)) { - sprintf(buf, "%d (%d)", (int)m_scene_raycaster.beds_count(), (int)m_scene_raycaster.active_beds_count()); - add_strings_row_to_table("Beds", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - sprintf(buf, "%d (%d)", (int)m_scene_raycaster.volumes_count(), (int)m_scene_raycaster.active_volumes_count()); - add_strings_row_to_table("Volumes", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - sprintf(buf, "%d (%d)", (int)m_scene_raycaster.gizmos_count(), (int)m_scene_raycaster.active_gizmos_count()); - add_strings_row_to_table("Gizmo elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - ImGui::EndTable(); - } - imgui.end(); -#endif // ENABLE_RAYCAST_PICKING_DEBUG -} -#else -void GLCanvas3D::_picking_pass() -{ - if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX) && !m_gizmos.is_dragging()) { - m_hover_volume_idxs.clear(); - - // Render the object for picking. - // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. - // Better to use software ray - casting on a bounding - box hierarchy. - - if (m_multisample_allowed) - // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. - glsafe(::glDisable(GL_MULTISAMPLE)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); - if (m_camera_clipping_plane.is_active()) { - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data().data()); - ::glEnable(GL_CLIP_PLANE0); - } -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - _render_volumes_for_picking(); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - if (m_camera_clipping_plane.is_active()) - ::glDisable(GL_CLIP_PLANE0); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Camera& camera = wxGetApp().plater()->get_camera(); - _render_bed_for_picking(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward()); -#else - _render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - m_gizmos.render_current_gizmo_for_picking_pass(); - - if (m_multisample_allowed) - glsafe(::glEnable(GL_MULTISAMPLE)); - - int volume_id = -1; - int gizmo_id = -1; - - std::array color = { 0, 0, 0, 0 }; - const Size& cnv_size = get_canvas_size(); - bool inside = 0 <= m_mouse.position(0) && m_mouse.position(0) < cnv_size.get_width() && 0 <= m_mouse.position(1) && m_mouse.position(1) < cnv_size.get_height(); - if (inside) { - glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position.y() - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color.data())); - if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3]) { - // Only non-interpolated colors are valid, those have their lowest three bits zeroed. - // we reserve color = (0,0,0) for occluders (as the printbed) - // volumes' id are shifted by 1 - // see: _render_volumes_for_picking() - unsigned int id = picking_encode(color[0], color[1], color[2]); - volume_id = id - 1; - // gizmos' id are instead properly encoded by the color - gizmo_id = id; - } - } - if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { - // do not add the volume id if any gizmo is active and CTRL is pressed - if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) - m_hover_volume_idxs.emplace_back(volume_id); - m_gizmos.set_hover_id(-1); - } - else - m_gizmos.set_hover_id(inside && (unsigned int)gizmo_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - gizmo_id) : -1); - - _update_volumes_hover_state(); - } -} -#endif // ENABLE_RAYCAST_PICKING - -void GLCanvas3D::_rectangular_selection_picking_pass() -{ - m_gizmos.set_hover_id(-1); - - std::set idxs; - - if (m_picking_enabled) { -#if ENABLE_RAYCAST_PICKING - const size_t width = std::max(m_rectangle_selection.get_width(), 1); - const size_t height = std::max(m_rectangle_selection.get_height(), 1); - - const OpenGLManager::EFramebufferType framebuffers_type = OpenGLManager::get_framebuffers_type(); - bool use_framebuffer = framebuffers_type != OpenGLManager::EFramebufferType::Unknown; - - GLuint render_fbo = 0; - GLuint render_tex = 0; - GLuint render_depth = 0; - if (use_framebuffer) { - // setup a framebuffer which covers only the selection rectangle - if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { - glsafe(::glGenFramebuffers(1, &render_fbo)); - glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)); - } - else { - glsafe(::glGenFramebuffersEXT(1, &render_fbo)); - glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo)); - } - glsafe(::glGenTextures(1, &render_tex)); - glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); - if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { - glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); - glsafe(::glGenRenderbuffers(1, &render_depth)); - glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); -#if ENABLE_OPENGL_ES - glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height)); -#else - glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height)); -#endif // ENABLE_OPENGL_ES - glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); - } - else { - glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0)); - glsafe(::glGenRenderbuffersEXT(1, &render_depth)); - glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth)); - glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height)); - glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth)); - } - const GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; - glsafe(::glDrawBuffers(1, drawBufs)); - if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { - if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - use_framebuffer = false; - } - else { - if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) - use_framebuffer = false; - } - } -#endif // ENABLE_RAYCAST_PICKING - - if (m_multisample_allowed) - // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. - glsafe(::glDisable(GL_MULTISAMPLE)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - -#if ENABLE_RAYCAST_PICKING - const Camera& main_camera = wxGetApp().plater()->get_camera(); - Camera framebuffer_camera; - framebuffer_camera.set_type(main_camera.get_type()); - const Camera* camera = &main_camera; - if (use_framebuffer) { - // setup a camera which covers only the selection rectangle - const std::array& viewport = camera->get_viewport(); - const double near_left = camera->get_near_left(); - const double near_bottom = camera->get_near_bottom(); - const double near_width = camera->get_near_width(); - const double near_height = camera->get_near_height(); - - const double ratio_x = near_width / double(viewport[2]); - const double ratio_y = near_height / double(viewport[3]); - - const double rect_near_left = near_left + double(m_rectangle_selection.get_left()) * ratio_x; - const double rect_near_bottom = near_bottom + (double(viewport[3]) - double(m_rectangle_selection.get_bottom())) * ratio_y; - double rect_near_right = near_left + double(m_rectangle_selection.get_right()) * ratio_x; - double rect_near_top = near_bottom + (double(viewport[3]) - double(m_rectangle_selection.get_top())) * ratio_y; - - if (rect_near_left == rect_near_right) - rect_near_right = rect_near_left + ratio_x; - if (rect_near_bottom == rect_near_top) - rect_near_top = rect_near_bottom + ratio_y; - - framebuffer_camera.look_at(camera->get_position(), camera->get_target(), camera->get_dir_up()); - framebuffer_camera.apply_projection(rect_near_left, rect_near_right, rect_near_bottom, rect_near_top, camera->get_near_z(), camera->get_far_z()); - framebuffer_camera.set_viewport(0, 0, width, height); - framebuffer_camera.apply_viewport(); - camera = &framebuffer_camera; - } - - _render_volumes_for_picking(*camera); -#else - _render_volumes_for_picking(); -#endif // ENABLE_RAYCAST_PICKING -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_RAYCAST_PICKING - _render_bed_for_picking(camera->get_view_matrix(), camera->get_projection_matrix(), !camera->is_looking_downward()); -#else - const Camera& camera = wxGetApp().plater()->get_camera(); - _render_bed_for_picking(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward()); -#endif // ENABLE_RAYCAST_PICKING -#else - _render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - if (m_multisample_allowed) - glsafe(::glEnable(GL_MULTISAMPLE)); - -#if ENABLE_RAYCAST_PICKING - const size_t px_count = width * height; - - const size_t left = use_framebuffer ? 0 : (size_t)m_rectangle_selection.get_left(); - const size_t top = use_framebuffer ? 0 : (size_t)get_canvas_size().get_height() - (size_t)m_rectangle_selection.get_top(); -#else - int width = std::max((int)m_rectangle_selection.get_width(), 1); - int height = std::max((int)m_rectangle_selection.get_height(), 1); - int px_count = width * height; - - int left = (int)m_rectangle_selection.get_left(); - int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top(); - if (left >= 0 && top >= 0) { -#endif // ENABLE_RAYCAST_PICKING -#define USE_PARALLEL 1 -#if USE_PARALLEL - struct Pixel - { - std::array data; - // Only non-interpolated colors are valid, those have their lowest three bits zeroed. - bool valid() const { return picking_checksum_alpha_channel(data[0], data[1], data[2]) == data[3]; } - // we reserve color = (0,0,0) for occluders (as the printbed) - // volumes' id are shifted by 1 - // see: _render_volumes_for_picking() - int id() const { return data[0] + (data[1] << 8) + (data[2] << 16) - 1; } - }; - - std::vector frame(px_count); - glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); - - tbb::spin_mutex mutex; - tbb::parallel_for(tbb::blocked_range(0, frame.size(), (size_t)width), - [this, &frame, &idxs, &mutex](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) - if (frame[i].valid()) { - int volume_id = frame[i].id(); - if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { - mutex.lock(); - idxs.insert(volume_id); - mutex.unlock(); - } - } - }); -#else - std::vector frame(4 * px_count); - glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); - - for (int i = 0; i < px_count; ++i) - { - int px_id = 4 * i; - int volume_id = frame[px_id] + (frame[px_id + 1] << 8) + (frame[px_id + 2] << 16); - if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) - idxs.insert(volume_id); - } -#endif // USE_PARALLEL -#if ENABLE_RAYCAST_PICKING - if (camera != &main_camera) - main_camera.apply_viewport(); - - if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { - glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); - if (render_depth != 0) - glsafe(::glDeleteRenderbuffers(1, &render_depth)); - if (render_fbo != 0) - glsafe(::glDeleteFramebuffers(1, &render_fbo)); - } - else if (framebuffers_type == OpenGLManager::EFramebufferType::Ext) { - glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); - if (render_depth != 0) - glsafe(::glDeleteRenderbuffersEXT(1, &render_depth)); - if (render_fbo != 0) - glsafe(::glDeleteFramebuffersEXT(1, &render_fbo)); - } - - if (render_tex != 0) - glsafe(::glDeleteTextures(1, &render_tex)); -#else - } -#endif // ENABLE_RAYCAST_PICKING - } - - m_hover_volume_idxs.assign(idxs.begin(), idxs.end()); - _update_volumes_hover_state(); -} - -void GLCanvas3D::_render_background() -{ - bool use_error_color = false; - if (wxGetApp().is_editor()) { - use_error_color = m_dynamic_background_enabled && - (current_printer_technology() != ptSLA || !m_volumes.empty()); - - if (!m_volumes.empty()) - use_error_color &= _is_any_volume_outside().first; - else - use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); - } - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - glsafe(::glMatrixMode(GL_PROJECTION)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - // Draws a bottom to top gradient over the complete screen. - glsafe(::glDisable(GL_DEPTH_TEST)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const ColorRGBA bottom_color = use_error_color ? ERROR_BG_DARK_COLOR : DEFAULT_BG_DARK_COLOR; - - if (!m_background.is_initialized()) { - m_background.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2 }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - init_data.add_vertex(Vec2f(-1.0f, -1.0f), Vec2f(0.0f, 0.0f)); - init_data.add_vertex(Vec2f(1.0f, -1.0f), Vec2f(1.0f, 0.0f)); - init_data.add_vertex(Vec2f(1.0f, 1.0f), Vec2f(1.0f, 1.0f)); - init_data.add_vertex(Vec2f(-1.0f, 1.0f), Vec2f(0.0f, 1.0f)); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_background.init_from(std::move(init_data)); - } - - GLShaderProgram* shader = wxGetApp().get_shader("background"); - if (shader != nullptr) { - shader->start_using(); - shader->set_uniform("top_color", use_error_color ? ERROR_BG_LIGHT_COLOR : DEFAULT_BG_LIGHT_COLOR); - shader->set_uniform("bottom_color", bottom_color); - m_background.render(); - shader->stop_using(); - } -#else - ::glBegin(GL_QUADS); - ::glColor3fv(use_error_color ? ERROR_BG_DARK_COLOR.data(): DEFAULT_BG_DARK_COLOR.data()); - ::glVertex2f(-1.0f, -1.0f); - ::glVertex2f(1.0f, -1.0f); - - ::glColor3fv(use_error_color ? ERROR_BG_LIGHT_COLOR.data() : DEFAULT_BG_LIGHT_COLOR.data()); - ::glVertex2f(1.0f, 1.0f); - ::glVertex2f(-1.0f, 1.0f); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_DEPTH_TEST)); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glPopMatrix()); - glsafe(::glMatrixMode(GL_MODELVIEW)); - glsafe(::glPopMatrix()); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL -} - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes) -#else -void GLCanvas3D::_render_bed(bool bottom, bool show_axes) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -{ - float scale_factor = 1.0; -#if ENABLE_RETINA_GL - scale_factor = m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - - bool show_texture = ! bottom || - (m_gizmos.get_current_type() != GLGizmosManager::FdmSupports - && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports - && m_gizmos.get_current_type() != GLGizmosManager::Hollow - && m_gizmos.get_current_type() != GLGizmosManager::Seam - && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes, show_texture); -#else - m_bed.render(*this, bottom, scale_factor, show_axes, show_texture); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void GLCanvas3D::_render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) -#else -void GLCanvas3D::_render_bed_for_picking(bool bottom) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -{ - float scale_factor = 1.0; -#if ENABLE_RETINA_GL - scale_factor = m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_bed.render_for_picking(*this, view_matrix, projection_matrix, bottom, scale_factor); -#else - m_bed.render_for_picking(*this, bottom, scale_factor); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) -{ - if (m_volumes.empty()) - return; - - glsafe(::glEnable(GL_DEPTH_TEST)); - - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); - - if (m_picking_enabled) - // Update the layer editing selection to the first object selected, update the current object maximum Z. - m_layers_editing.select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); - - if (const BuildVolume &build_volume = m_bed.build_volume(); build_volume.valid()) { - switch (build_volume.type()) { - case BuildVolume::Type::Rectangle: { - const BoundingBox3Base bed_bb = build_volume.bounding_volume().inflated(BuildVolume::SceneEpsilon); - m_volumes.set_print_volume({ 0, // circle - { float(bed_bb.min.x()), float(bed_bb.min.y()), float(bed_bb.max.x()), float(bed_bb.max.y()) }, - { 0.0f, float(build_volume.max_print_height()) } }); - break; - } - case BuildVolume::Type::Circle: { - m_volumes.set_print_volume({ 1, // rectangle - { unscaled(build_volume.circle().center.x()), unscaled(build_volume.circle().center.y()), unscaled(build_volume.circle().radius + BuildVolume::SceneEpsilon), 0.0f }, - { 0.0f, float(build_volume.max_print_height() + BuildVolume::SceneEpsilon) } }); - break; - } - default: - case BuildVolume::Type::Convex: - case BuildVolume::Type::Custom: { - m_volumes.set_print_volume({ static_cast(type), - { -FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX }, - { -FLT_MAX, FLT_MAX } } - ); - } - } - if (m_requires_check_outside_state) { - m_volumes.check_outside_state(build_volume, nullptr); - m_requires_check_outside_state = false; - } - } - - if (m_use_clipping_planes) - m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); - else - m_volumes.set_z_range(-FLT_MAX, FLT_MAX); - - m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); - m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); - m_volumes.set_show_non_manifold_edges(!m_gizmos.is_hiding_instances() && m_gizmos.get_current_type() != GLGizmosManager::Simplify); - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); - if (shader != nullptr) { - shader->start_using(); - - switch (type) - { - default: - case GLVolumeCollection::ERenderType::Opaque: - { - if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { - int object_id = m_layers_editing.last_object_id; -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Camera& camera = wxGetApp().plater()->get_camera(); - m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix(), [object_id](const GLVolume& volume) { - // Which volume to paint without the layer height profile shader? - return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); - }); -#else - m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { - // Which volume to paint without the layer height profile shader? - return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); - }); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - // Let LayersEditing handle rendering of the active object using the layer height profile shader. - m_layers_editing.render_volumes(*this, m_volumes); - } - else { - // do not cull backfaces to show broken geometry, if any -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Camera& camera = wxGetApp().plater()->get_camera(); - m_volumes.render(type, m_picking_enabled, camera.get_view_matrix(), camera.get_projection_matrix(), [this](const GLVolume& volume) { - return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); - }); -#else - m_volumes.render(type, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) { - return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); - }); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - // In case a painting gizmo is open, it should render the painted triangles - // before transparent objects are rendered. Otherwise they would not be - // visible when inside modifier meshes etc. - { - GLGizmosManager& gm = get_gizmos_manager(); -// GLGizmosManager::EType type = gm.get_current_type(); - if (dynamic_cast(gm.get_current())) { - shader->stop_using(); - gm.render_painter_gizmo(); - shader->start_using(); - } - } - break; - } - case GLVolumeCollection::ERenderType::Transparent: - { -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Camera& camera = wxGetApp().plater()->get_camera(); - m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix()); -#else - m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - break; - } - } - shader->stop_using(); - } - - m_camera_clipping_plane = ClippingPlane::ClipsNothing(); -} - -void GLCanvas3D::_render_gcode() -{ - m_gcode_viewer.render(); -} - -void GLCanvas3D::_render_gcode_cog() -{ - m_gcode_viewer.render_cog(); -} - -void GLCanvas3D::_render_selection() -{ - float scale_factor = 1.0; -#if ENABLE_RETINA_GL - scale_factor = m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - - if (!m_gizmos.is_running()) - m_selection.render(scale_factor); -} - -void GLCanvas3D::_render_sequential_clearance() -{ - if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) - return; - - switch (m_gizmos.get_current_type()) - { - case GLGizmosManager::EType::Flatten: - case GLGizmosManager::EType::Cut: - case GLGizmosManager::EType::Hollow: - case GLGizmosManager::EType::SlaSupports: - case GLGizmosManager::EType::FdmSupports: - case GLGizmosManager::EType::Seam: { return; } - default: { break; } - } - - m_sequential_print_clearance.render(); -} - -#if ENABLE_RENDER_SELECTION_CENTER -void GLCanvas3D::_render_selection_center() -{ - m_selection.render_center(m_gizmos.is_dragging()); -} -#endif // ENABLE_RENDER_SELECTION_CENTER - -void GLCanvas3D::_check_and_update_toolbar_icon_scale() -{ - // Don't update a toolbar scale, when we are on a Preview - if (wxGetApp().plater()->is_preview_shown()) - return; - - float scale = wxGetApp().toolbar_icon_scale(); - Size cnv_size = get_canvas_size(); - - float size = GLToolbar::Default_Icons_Size * scale; - - // Set current size for all top toolbars. It will be used for next calculations - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); -#if ENABLE_RETINA_GL - const float sc = m_retina_helper->get_scale_factor() * scale; - m_main_toolbar.set_scale(sc); - m_undoredo_toolbar.set_scale(sc); - collapse_toolbar.set_scale(sc); - size *= m_retina_helper->get_scale_factor(); -#else - m_main_toolbar.set_icons_size(size); - m_undoredo_toolbar.set_icons_size(size); - collapse_toolbar.set_icons_size(size); -#endif // ENABLE_RETINA_GL - - float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); - int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt(); - float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars - - // calculate scale needed for items in all top toolbars - // the std::max() is there because on some Linux dialects/virtual machines this code is called when the canvas has not been properly initialized yet, - // leading to negative values for the scale. - // See: https://github.com/prusa3d/PrusaSlicer/issues/8563 - // https://github.com/supermerill/SuperSlicer/issues/854 - float new_h_scale = std::max((cnv_size.get_width() - noitems_width), 1.0f) / (items_cnt * GLToolbar::Default_Icons_Size); - - items_cnt = m_gizmos.get_selectable_icons_cnt() + 3; // +3 means a place for top and view toolbars and separators in gizmos toolbar - - // calculate scale needed for items in the gizmos toolbar - float new_v_scale = cnv_size.get_height() / (items_cnt * GLGizmosManager::Default_Icons_Size); - - // set minimum scale as a auto scale for the toolbars - float new_scale = std::min(new_h_scale, new_v_scale); -#if ENABLE_RETINA_GL - new_scale /= m_retina_helper->get_scale_factor(); -#endif - if (fabs(new_scale - scale) > 0.01) // scale is changed by 1% and more - wxGetApp().set_auto_toolbar_icon_scale(new_scale); -} - -void GLCanvas3D::_render_overlays() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - // ensure that the textures are renderered inside the frustrum - const Camera& camera = wxGetApp().plater()->get_camera(); - glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.005))); - // ensure that the overlay fits the frustrum near z plane - double gui_scale = camera.get_gui_scale(); - glsafe(::glScaled(gui_scale, gui_scale, 1.0)); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - _check_and_update_toolbar_icon_scale(); - - _render_gizmos_overlay(); - - // main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed - // to correctly place them -#if ENABLE_RETINA_GL - const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); - m_main_toolbar.set_scale(scale); - m_undoredo_toolbar.set_scale(scale); - wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); -#else - const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); - m_main_toolbar.set_icons_size(size); - m_undoredo_toolbar.set_icons_size(size); - wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); -#endif // ENABLE_RETINA_GL - - _render_main_toolbar(); - _render_undoredo_toolbar(); - _render_collapse_toolbar(); - _render_view_toolbar(); - - if (m_layers_editing.last_object_id >= 0 && m_layers_editing.object_max_z() > 0.0f) - m_layers_editing.render_overlay(*this); - - const ConfigOptionBool* opt = dynamic_cast(m_config->option("complete_objects")); - bool sequential_print = opt != nullptr && opt->value; - std::vector sorted_instances; - if (sequential_print) { - for (ModelObject* model_object : m_model->objects) - for (ModelInstance* model_instance : model_object->instances) { - sorted_instances.emplace_back(model_instance); - } - } - m_labels.render(sorted_instances); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glPopMatrix()); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL -} - -#if ENABLE_RAYCAST_PICKING -void GLCanvas3D::_render_volumes_for_picking(const Camera& camera) const -#else -void GLCanvas3D::_render_volumes_for_picking() const -#endif // ENABLE_RAYCAST_PICKING -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat_clip"); - if (shader == nullptr) - return; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // do not cull backfaces to show broken geometry, if any - glsafe(::glDisable(GL_CULL_FACE)); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_RAYCAST_PICKING - const Transform3d& view_matrix = camera.get_view_matrix(); -#else - const Transform3d& view_matrix = wxGetApp().plater()->get_camera().get_view_matrix(); -#endif // ENABLE_RAYCAST_PICKING - for (size_t type = 0; type < 2; ++ type) { - GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::ERenderType::Opaque : GLVolumeCollection::ERenderType::Transparent, view_matrix); - for (const GLVolumeWithIdAndZ& volume : to_render) - if (!volume.first->disabled && (volume.first->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) { - // Object picking mode. Render the object with a color encoding the object index. - // we reserve color = (0,0,0) for occluders (as the printbed) - // so we shift volumes' id by 1 to get the proper color - const unsigned int id = 1 + volume.second.first; -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume.first->model.set_color(picking_decode(id)); - shader->start_using(); -#if ENABLE_RAYCAST_PICKING - shader->set_uniform("view_model_matrix", view_matrix * volume.first->world_matrix()); -#else - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix() * volume.first->world_matrix()); -#endif // ENABLE_RAYCAST_PICKING - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("volume_world_matrix", volume.first->world_matrix()); - shader->set_uniform("z_range", m_volumes.get_z_range()); - shader->set_uniform("clipping_plane", m_volumes.get_clipping_plane()); -#else - glsafe(::glColor4fv(picking_decode(id).data())); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - volume.first->render(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_CULL_FACE)); -} - -void GLCanvas3D::_render_current_gizmo() const -{ - m_gizmos.render_current_gizmo(); -} - -void GLCanvas3D::_render_gizmos_overlay() -{ -#if ENABLE_RETINA_GL -// m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor()); - const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); - m_gizmos.set_overlay_scale(scale); //! #ys_FIXME_experiment -#else -// m_gizmos.set_overlay_scale(m_canvas->GetContentScaleFactor()); -// m_gizmos.set_overlay_scale(wxGetApp().em_unit()*0.1f); - const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); - m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment -#endif /* __WXMSW__ */ - - m_gizmos.render_overlay(); - - if (m_gizmo_highlighter.m_render_arrow) - m_gizmos.render_arrow(*this, m_gizmo_highlighter.m_gizmo_type); -} - -void GLCanvas3D::_render_main_toolbar() -{ - if (!m_main_toolbar.is_enabled()) - return; - - const Size cnv_size = get_canvas_size(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - const float top = 0.5f * (float)cnv_size.get_height(); -#else - const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; -#if ENABLE_LEGACY_OPENGL_REMOVAL - const float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width); -#else - const float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - m_main_toolbar.set_position(top, left); - m_main_toolbar.render(*this); - if (m_toolbar_highlighter.m_render_arrow) - m_main_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); -} - -void GLCanvas3D::_render_undoredo_toolbar() -{ - if (!m_undoredo_toolbar.is_enabled()) - return; - - const Size cnv_size = get_canvas_size(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - const float top = 0.5f * (float)cnv_size.get_height(); -#else - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - - const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; -#if ENABLE_LEGACY_OPENGL_REMOVAL - const float left = m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width); -#else - const float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - m_undoredo_toolbar.set_position(top, left); - m_undoredo_toolbar.render(*this); - if (m_toolbar_highlighter.m_render_arrow) - m_undoredo_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); -} - -void GLCanvas3D::_render_collapse_toolbar() const -{ - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - - const Size cnv_size = get_canvas_size(); - const float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0; -#if ENABLE_LEGACY_OPENGL_REMOVAL - const float top = 0.5f * (float)cnv_size.get_height(); - const float left = 0.5f * (float)cnv_size.get_width() - collapse_toolbar.get_width() - band; -#else - const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - - const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - const float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - collapse_toolbar.set_position(top, left); - collapse_toolbar.render(*this); -} - -void GLCanvas3D::_render_view_toolbar() const -{ - GLToolbar& view_toolbar = wxGetApp().plater()->get_view_toolbar(); - -#if ENABLE_RETINA_GL - const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(); -#if __APPLE__ - view_toolbar.set_scale(scale); -#else // if GTK3 - const float size = int(GLGizmosManager::Default_Icons_Size * scale); - view_toolbar.set_icons_size(size); -#endif // __APPLE__ -#else - const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); - view_toolbar.set_icons_size(size); -#endif // ENABLE_RETINA_GL - - const Size cnv_size = get_canvas_size(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - // places the toolbar on the bottom-left corner of the 3d scene - const float top = -0.5f * (float)cnv_size.get_height() + view_toolbar.get_height(); - const float left = -0.5f * (float)cnv_size.get_width(); -#else - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - - // places the toolbar on the bottom-left corner of the 3d scene - float top = (-0.5f * (float)cnv_size.get_height() + view_toolbar.get_height()) * inv_zoom; - float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - view_toolbar.set_position(top, left); - view_toolbar.render(*this); -} - -#if ENABLE_SHOW_CAMERA_TARGET -void GLCanvas3D::_render_camera_target() -{ - static const float half_length = 5.0f; - - glsafe(::glDisable(GL_DEPTH_TEST)); -#if ENABLE_GL_CORE_PROFILE - if (!OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glLineWidth(2.0f)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Vec3f& target = wxGetApp().plater()->get_camera().get_target().cast(); - m_camera_target.target = target.cast(); - - for (int i = 0; i < 3; ++i) { - if (!m_camera_target.axis[i].is_initialized()) { - m_camera_target.axis[i].reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = (i == X) ? ColorRGBA::X() : ((i == Y) ? ColorRGBA::Y() : ColorRGBA::Z()); - init_data.reserve_vertices(2); - init_data.reserve_indices(2); - - // vertices - if (i == X) { - init_data.add_vertex(Vec3f(-half_length, 0.0f, 0.0f)); - init_data.add_vertex(Vec3f(+half_length, 0.0f, 0.0f)); - } - else if (i == Y) { - init_data.add_vertex(Vec3f(0.0f, -half_length, 0.0f)); - init_data.add_vertex(Vec3f(0.0f, +half_length, 0.0f)); - } - else { - init_data.add_vertex(Vec3f(0.0f, 0.0f, -half_length)); - init_data.add_vertex(Vec3f(0.0f, 0.0f, +half_length)); - } - - // indices - init_data.add_line(0, 1); - - m_camera_target.axis[i].init_from(std::move(init_data)); - } - } - -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); -#else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE - if (shader != nullptr) { - shader->start_using(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(m_camera_target.target)); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.5f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - for (int i = 0; i < 3; ++i) { - m_camera_target.axis[i].render(); - } - shader->stop_using(); - } -#else - ::glBegin(GL_LINES); - const Vec3d& target = wxGetApp().plater()->get_camera().get_target(); - // draw line for x axis - ::glColor3f(1.0f, 0.0f, 0.0f); - ::glVertex3d(target.x() - half_length, target.y(), target.z()); - ::glVertex3d(target.x() + half_length, target.y(), target.z()); - // draw line for y axis - ::glColor3f(0.0f, 1.0f, 0.0f); - ::glVertex3d(target.x(), target.y() - half_length, target.z()); - ::glVertex3d(target.x(), target.y() + half_length, target.z()); - // draw line for z axis - ::glColor3f(0.0f, 0.0f, 1.0f); - ::glVertex3d(target.x(), target.y(), target.z() - half_length); - ::glVertex3d(target.x(), target.y(), target.z() + half_length); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} -#endif // ENABLE_SHOW_CAMERA_TARGET - -void GLCanvas3D::_render_sla_slices() -{ - if (!m_use_clipping_planes || current_printer_technology() != ptSLA) - return; - - const SLAPrint* print = this->sla_print(); - const PrintObjects& print_objects = print->objects(); - if (print_objects.empty()) - // nothing to render, return - return; - - double clip_min_z = -m_clipping_planes[0].get_data()[3]; - double clip_max_z = m_clipping_planes[1].get_data()[3]; - for (unsigned int i = 0; i < (unsigned int)print_objects.size(); ++i) { - const SLAPrintObject* obj = print_objects[i]; - - if (!obj->is_step_done(slaposSliceSupports)) - continue; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - SlaCap::ObjectIdToModelsMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); - SlaCap::ObjectIdToModelsMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); -#else - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - { - if (it_caps_bottom == m_sla_caps[0].triangles.end()) - it_caps_bottom = m_sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; - if (!m_sla_caps[0].matches(clip_min_z)) { - m_sla_caps[0].z = clip_min_z; -#if ENABLE_LEGACY_OPENGL_REMOVAL - it_caps_bottom->second.object.reset(); - it_caps_bottom->second.supports.reset(); -#else - it_caps_bottom->second.object.clear(); - it_caps_bottom->second.supports.clear(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - if (it_caps_top == m_sla_caps[1].triangles.end()) - it_caps_top = m_sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; - if (!m_sla_caps[1].matches(clip_max_z)) { - m_sla_caps[1].z = clip_max_z; -#if ENABLE_LEGACY_OPENGL_REMOVAL - it_caps_top->second.object.reset(); - it_caps_top->second.supports.reset(); -#else - it_caps_top->second.object.clear(); - it_caps_top->second.supports.clear(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLModel& bottom_obj_triangles = it_caps_bottom->second.object; - GLModel& bottom_sup_triangles = it_caps_bottom->second.supports; - GLModel& top_obj_triangles = it_caps_top->second.object; - GLModel& top_sup_triangles = it_caps_top->second.supports; -#else - Pointf3s &bottom_obj_triangles = it_caps_bottom->second.object; - Pointf3s &bottom_sup_triangles = it_caps_bottom->second.supports; - Pointf3s &top_obj_triangles = it_caps_top->second.object; - Pointf3s &top_sup_triangles = it_caps_top->second.supports; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - auto init_model = [](GLModel& model, const Pointf3s& triangles, const ColorRGBA& color) { - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(triangles.size()); - init_data.reserve_indices(triangles.size() / 3); - init_data.color = color; - - unsigned int vertices_count = 0; - for (const Vec3d& v : triangles) { - init_data.add_vertex((Vec3f)v.cast()); - ++vertices_count; - if (vertices_count % 3 == 0) - init_data.add_triangle(vertices_count - 3, vertices_count - 2, vertices_count - 1); - } - - if (!init_data.is_empty()) - model.init_from(std::move(init_data)); - }; - - if ((!bottom_obj_triangles.is_initialized() || !bottom_sup_triangles.is_initialized() || - !top_obj_triangles.is_initialized() || !top_sup_triangles.is_initialized()) && !obj->get_slice_index().empty()) { -#else - if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && - !obj->get_slice_index().empty()) { -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - double layer_height = print->default_object_config().layer_height.value; - double initial_layer_height = print->material_config().initial_layer_height.value; - bool left_handed = obj->is_left_handed(); - - coord_t key_zero = obj->get_slice_index().front().print_level(); - // Slice at the center of the slab starting at clip_min_z will be rendered for the lower plane. - coord_t key_low = coord_t((clip_min_z - initial_layer_height + layer_height) / SCALING_FACTOR) + key_zero; - // Slice at the center of the slab ending at clip_max_z will be rendered for the upper plane. - coord_t key_high = coord_t((clip_max_z - initial_layer_height) / SCALING_FACTOR) + key_zero; - - const SliceRecord& slice_low = obj->closest_slice_to_print_level(key_low, coord_t(SCALED_EPSILON)); - const SliceRecord& slice_high = obj->closest_slice_to_print_level(key_high, coord_t(SCALED_EPSILON)); - - // Offset to avoid OpenGL Z fighting between the object's horizontal surfaces and the triangluated surfaces of the cuts. - const double plane_shift_z = 0.002; - - if (slice_low.is_valid()) { - const ExPolygons& obj_bottom = slice_low.get_slice(soModel); - const ExPolygons& sup_bottom = slice_low.get_slice(soSupport); -#if ENABLE_LEGACY_OPENGL_REMOVAL - // calculate model bottom cap - if (!bottom_obj_triangles.is_initialized() && !obj_bottom.empty()) - init_model(bottom_obj_triangles, triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); - // calculate support bottom cap - if (!bottom_sup_triangles.is_initialized() && !sup_bottom.empty()) - init_model(bottom_sup_triangles, triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); -#else - // calculate model bottom cap - if (bottom_obj_triangles.empty() && !obj_bottom.empty()) - bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, ! left_handed); - // calculate support bottom cap - if (bottom_sup_triangles.empty() && !sup_bottom.empty()) - bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - if (slice_high.is_valid()) { - const ExPolygons& obj_top = slice_high.get_slice(soModel); - const ExPolygons& sup_top = slice_high.get_slice(soSupport); -#if ENABLE_LEGACY_OPENGL_REMOVAL - // calculate model top cap - if (!top_obj_triangles.is_initialized() && !obj_top.empty()) - init_model(top_obj_triangles, triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); - // calculate support top cap - if (!top_sup_triangles.is_initialized() && !sup_top.empty()) - init_model(top_sup_triangles, triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); -#else - // calculate model top cap - if (top_obj_triangles.empty() && !obj_top.empty()) - top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed); - // calculate support top cap - if (top_sup_triangles.empty() && !sup_top.empty()) - top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); - - for (const SLAPrintObject::Instance& inst : obj->instances()) { - const Camera& camera = wxGetApp().plater()->get_camera(); -#if ENABLE_WORLD_COORDINATE - Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::translation_transform({ unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0 }) * - Geometry::rotation_transform(inst.rotation * Vec3d::UnitZ()); - if (obj->is_left_handed()) - view_model_matrix = view_model_matrix * Geometry::scale_transform({ -1.0f, 1.0f, 1.0f }); -#else - const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(Vec3d(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0), - inst.rotation * Vec3d::UnitZ(), Vec3d::Ones(), - obj->is_left_handed() ? /* The polygons are mirrored by X */ Vec3d(-1.0f, 1.0f, 1.0f) : Vec3d::Ones()); -#endif // ENABLE_WORLD_COORDINATE - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - - bottom_obj_triangles.render(); - top_obj_triangles.render(); - bottom_sup_triangles.render(); - top_sup_triangles.render(); - } - - shader->stop_using(); - } -#else - if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) { - for (const SLAPrintObject::Instance& inst : obj->instances()) { - glsafe(::glPushMatrix()); - glsafe(::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0)); - glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0f, 0.0f, 1.0f)); - if (obj->is_left_handed()) - // The polygons are mirrored by X. - glsafe(::glScalef(-1.0f, 1.0f, 1.0f)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); - if (!bottom_obj_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_obj_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_obj_triangles.size())); - } - if (! top_obj_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_obj_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_obj_triangles.size())); - } - glsafe(::glColor3f(1.0f, 0.0f, 0.37f)); - if (! bottom_sup_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_sup_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_sup_triangles.size())); - } - if (! top_sup_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_sup_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_sup_triangles.size())); - } - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glPopMatrix()); - } - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } -} - -void GLCanvas3D::_render_selection_sidebar_hints() -{ - m_selection.render_sidebar_hints(m_sidebar_field); -} - -void GLCanvas3D::_update_volumes_hover_state() -{ - // skip update if the Gizmo Measure is active - if (m_gizmos.get_current_type() == GLGizmosManager::Measure) - return; - - for (GLVolume* v : m_volumes.volumes) { - v->hover = GLVolume::HS_None; - } - - if (m_hover_volume_idxs.empty()) - return; - - const bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); - const bool shift_pressed = wxGetKeyState(WXK_SHIFT); - const bool alt_pressed = wxGetKeyState(WXK_ALT); - - if (alt_pressed && (shift_pressed || ctrl_pressed)) { - // illegal combinations of keys - m_hover_volume_idxs.clear(); - return; - } - - bool hover_modifiers_only = true; - for (int i : m_hover_volume_idxs) { - if (!m_volumes.volumes[i]->is_modifier) { - hover_modifiers_only = false; - break; - } - } - - std::set> hover_instances; - for (int i : m_hover_volume_idxs) { - const GLVolume& v = *m_volumes.volumes[i]; - hover_instances.insert(std::make_pair(v.object_idx(), v.instance_idx())); - } - - bool hover_from_single_instance = hover_instances.size() == 1; - - if (hover_modifiers_only && !hover_from_single_instance) { - // do not allow to select volumes from different instances - m_hover_volume_idxs.clear(); - return; - } - - for (int i : m_hover_volume_idxs) { - GLVolume& volume = *m_volumes.volumes[i]; - if (volume.hover != GLVolume::HS_None) - continue; - - const bool deselect = volume.selected && ((shift_pressed && m_rectangle_selection.is_empty()) || (alt_pressed && !m_rectangle_selection.is_empty())); - const bool select = !volume.selected && (m_rectangle_selection.is_empty() || (shift_pressed && !m_rectangle_selection.is_empty())); - - if (select || deselect) { - const bool as_volume = - volume.is_modifier && hover_from_single_instance && !ctrl_pressed && - ( - !deselect || - (deselect && !m_selection.is_single_full_instance() && volume.object_idx() == m_selection.get_object_idx() && volume.instance_idx() == m_selection.get_instance_idx()) - ); - - if (as_volume) - volume.hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; - else { - const int object_idx = volume.object_idx(); - const int instance_idx = volume.instance_idx(); - - for (GLVolume* v : m_volumes.volumes) { - if (v->object_idx() == object_idx && v->instance_idx() == instance_idx) - v->hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; - } - } - } - else if (volume.selected) - volume.hover = GLVolume::HS_Hover; - } -} - -void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt) -{ - int object_idx_selected = m_layers_editing.last_object_id; - if (object_idx_selected == -1) - return; - - // A volume is selected. Test, whether hovering over a layer thickness bar. - if (evt != nullptr) { - const Rect& rect = LayersEditing::get_bar_rect_screen(*this); - float b = rect.get_bottom(); - m_layers_editing.last_z = m_layers_editing.object_max_z() * (b - evt->GetY() - 1.0f) / (b - rect.get_top()); - m_layers_editing.last_action = - evt->ShiftDown() ? (evt->RightIsDown() ? LAYER_HEIGHT_EDIT_ACTION_SMOOTH : LAYER_HEIGHT_EDIT_ACTION_REDUCE) : - (evt->RightIsDown() ? LAYER_HEIGHT_EDIT_ACTION_INCREASE : LAYER_HEIGHT_EDIT_ACTION_DECREASE); - } - - m_layers_editing.adjust_layer_height_profile(); - _refresh_if_shown_on_screen(); - - // Automatic action on mouse down with the same coordinate. - _start_timer(); -} - -Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z) -{ - if (m_canvas == nullptr) - return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); - -#if ENABLE_RAYCAST_PICKING - if (z == nullptr) { - const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(mouse_pos.cast(), wxGetApp().plater()->get_camera(), nullptr); - return hit.is_valid() ? hit.position.cast() : _mouse_to_bed_3d(mouse_pos); - } - else { - const Camera& camera = wxGetApp().plater()->get_camera(); - const Vec4i viewport(camera.get_viewport().data()); - Vec3d out; - igl::unproject(Vec3d(mouse_pos.x(), viewport[3] - mouse_pos.y(), *z), camera.get_view_matrix().matrix(), camera.get_projection_matrix().matrix(), viewport, out); - return out; - } -#else - const Camera& camera = wxGetApp().plater()->get_camera(); - const Matrix4d modelview = camera.get_view_matrix().matrix(); - const Matrix4d projection = camera.get_projection_matrix().matrix(); - const Vec4i viewport(camera.get_viewport().data()); - - const int y = viewport[3] - mouse_pos.y(); - float mouse_z; - if (z == nullptr) - glsafe(::glReadPixels(mouse_pos.x(), y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z)); - else - mouse_z = *z; - - Vec3d out; - igl::unproject(Vec3d(mouse_pos.x(), y, mouse_z), modelview, projection, viewport, out); - return out; -#endif // ENABLE_RAYCAST_PICKING -} - -Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos) -{ - return mouse_ray(mouse_pos).intersect_plane(0.0); -} - -void GLCanvas3D::_start_timer() -{ - m_timer.Start(100, wxTIMER_CONTINUOUS); -} - -void GLCanvas3D::_stop_timer() -{ - m_timer.Stop(); -} - -void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) -{ - const Print *print = this->fff_print(); - if (print == nullptr) - return; - - if (! print->is_step_done(psSkirtBrim)) - return; - - if (!print->has_skirt() && !print->has_brim()) - return; - - const ColorRGBA color = ColorRGBA::GREENISH(); - - // number of skirt layers - size_t total_layer_count = 0; - for (const PrintObject* print_object : print->objects()) { - total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); - } - size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min(print->config().skirt_height.value, total_layer_count); - if (skirt_height == 0 && print->has_brim()) - skirt_height = 1; - - // Get first skirt_height layers. - //FIXME This code is fishy. It may not work for multiple objects with different layering due to variable layer height feature. - // This is not critical as this is just an initial preview. - const PrintObject* highest_object = *std::max_element(print->objects().begin(), print->objects().end(), [](auto l, auto r){ return l->layers().size() < r->layers().size(); }); - std::vector print_zs; - print_zs.reserve(skirt_height * 2); - for (size_t i = 0; i < std::min(skirt_height, highest_object->layers().size()); ++ i) - print_zs.emplace_back(float(highest_object->layers()[i]->print_z)); - // Only add skirt for the raft layers. - for (size_t i = 0; i < std::min(skirt_height, std::min(highest_object->slicing_parameters().raft_layers(), highest_object->support_layers().size())); ++ i) - print_zs.emplace_back(float(highest_object->support_layers()[i]->print_z)); - sort_remove_duplicates(print_zs); - skirt_height = std::min(skirt_height, print_zs.size()); - print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLVolume* volume = m_volumes.new_toolpath_volume(color); - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; -#else - GLVolume *volume = m_volumes.new_toolpath_volume(color, VERTEX_BUFFER_RESERVE_SIZE); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t i = 0; i < skirt_height; ++ i) { - volume->print_zs.emplace_back(print_zs[i]); -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume->offsets.emplace_back(init_data.indices_count()); - if (i == 0) - _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), init_data); - _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), init_data); -#else - volume->offsets.emplace_back(volume->indexed_vertex_array.quad_indices.size()); - volume->offsets.emplace_back(volume->indexed_vertex_array.triangle_indices.size()); - if (i == 0) - _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), *volume); - _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), *volume); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (init_data.vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { - volume->model.init_from(std::move(init_data)); -#else - if (volume->indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - GLVolume &vol = *volume; - volume = m_volumes.new_toolpath_volume(vol.color); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - } - } -#if ENABLE_LEGACY_OPENGL_REMOVAL - volume->model.init_from(std::move(init_data)); - volume->is_outside = !contains(build_volume, volume->model); -#else - volume->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(volume->indexed_vertex_array.vertices_and_normals_interleaved, volume->indexed_vertex_array.bounding_box()); - volume->indexed_vertex_array.finalize_geometry(m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) -{ - std::vector tool_colors; - decode_colors(str_tool_colors, tool_colors); - - struct Ctxt - { - const PrintInstances *shifted_copies; - std::vector layers; - bool has_perimeters; - bool has_infill; - bool has_support; - const std::vector* tool_colors; - bool is_single_material_print; - int extruders_cnt; - const std::vector* color_print_values; - - static ColorRGBA color_perimeters() { return ColorRGBA::YELLOW(); } - static ColorRGBA color_infill() { return ColorRGBA::REDISH(); } - static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } - static ColorRGBA color_pause_or_custom_code() { return ColorRGBA::GRAY(); } - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return color_by_tool() ? tool_colors->size() : 0; } - const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } - - // For coloring by a color_print(M600), return a parsed color. - bool color_by_color_print() const { return color_print_values!=nullptr; } - const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const { - const CustomGCode::Item value{layers[layer_idx]->print_z + EPSILON, CustomGCode::Custom, 0, ""}; - auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); - return (it - color_print_values->begin()) % number_tools(); - } - - const size_t color_print_color_idx_by_layer_idx_and_extruder(const size_t layer_idx, const int extruder) const - { - const coordf_t print_z = layers[layer_idx]->print_z; - - auto it = std::find_if(color_print_values->begin(), color_print_values->end(), - [print_z](const CustomGCode::Item& code) - { return fabs(code.print_z - print_z) < EPSILON; }); - if (it != color_print_values->end()) { - CustomGCode::Type type = it->type; - // pause print or custom Gcode - if (type == CustomGCode::PausePrint || - (type != CustomGCode::ColorChange && type != CustomGCode::ToolChange)) - return number_tools()-1; // last color item is a gray color for pause print or custom G-code - - // change tool (extruder) - if (type == CustomGCode::ToolChange) - return get_color_idx_for_tool_change(it, extruder); - // change color for current extruder - if (type == CustomGCode::ColorChange) { - int color_idx = get_color_idx_for_color_change(it, extruder); - if (color_idx >= 0) - return color_idx; - } - } - - const CustomGCode::Item value{print_z + EPSILON, CustomGCode::Custom, 0, ""}; - it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); - while (it != color_print_values->begin()) { - --it; - // change color for current extruder - if (it->type == CustomGCode::ColorChange) { - int color_idx = get_color_idx_for_color_change(it, extruder); - if (color_idx >= 0) - return color_idx; - } - // change tool (extruder) - if (it->type == CustomGCode::ToolChange) - return get_color_idx_for_tool_change(it, extruder); - } - - return std::min(extruders_cnt - 1, std::max(extruder - 1, 0));; - } - - private: - int get_m600_color_idx(std::vector::const_iterator it) const - { - int shift = 0; - while (it != color_print_values->begin()) { - --it; - if (it->type == CustomGCode::ColorChange) - shift++; - } - return extruders_cnt + shift; - } - - int get_color_idx_for_tool_change(std::vector::const_iterator it, const int extruder) const - { - const int current_extruder = it->extruder == 0 ? extruder : it->extruder; - if (number_tools() == size_t(extruders_cnt + 1)) // there is no one "M600" - return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); - - auto it_n = it; - while (it_n != color_print_values->begin()) { - --it_n; - if (it_n->type == CustomGCode::ColorChange && it_n->extruder == current_extruder) - return get_m600_color_idx(it_n); - } - - return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); - } - - int get_color_idx_for_color_change(std::vector::const_iterator it, const int extruder) const - { - if (extruders_cnt == 1) - return get_m600_color_idx(it); - - auto it_n = it; - bool is_tool_change = false; - while (it_n != color_print_values->begin()) { - --it_n; - if (it_n->type == CustomGCode::ToolChange) { - is_tool_change = true; - if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder)) - return get_m600_color_idx(it); - break; - } - } - if (!is_tool_change && it->extruder == extruder) - return get_m600_color_idx(it); - - return -1; - } - - } ctxt; - - ctxt.has_perimeters = print_object.is_step_done(posPerimeters); - ctxt.has_infill = print_object.is_step_done(posInfill); - ctxt.has_support = print_object.is_step_done(posSupportMaterial); - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - ctxt.color_print_values = color_print_values.empty() ? nullptr : &color_print_values; - ctxt.is_single_material_print = this->fff_print()->extruders().size()==1; - ctxt.extruders_cnt = wxGetApp().extruders_edited_cnt(); - - ctxt.shifted_copies = &print_object.instances(); - - // order layers by print_z - { - size_t nlayers = 0; - if (ctxt.has_perimeters || ctxt.has_infill) - nlayers = print_object.layers().size(); - if (ctxt.has_support) - nlayers += print_object.support_layers().size(); - ctxt.layers.reserve(nlayers); - } - if (ctxt.has_perimeters || ctxt.has_infill) - for (const Layer *layer : print_object.layers()) - ctxt.layers.emplace_back(layer); - if (ctxt.has_support) - for (const Layer *layer : print_object.support_layers()) - ctxt.layers.emplace_back(layer); - std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); - - // Maximum size of an allocation block: 32MB / sizeof(float) - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); - - const bool is_selected_separate_extruder = m_selected_extruder > 0 && ctxt.color_by_color_print(); - - //FIXME Improve the heuristics for a grain size. - size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { - // Allocate the volume before locking. - GLVolume *volume = new GLVolume(color); - volume->is_extrusion_path = true; -#if ENABLE_LEGACY_OPENGL_REMOVAL - // to prevent sending data to gpu (in the main thread) while - // editing the model geometry - volume->model.disable_render(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - tbb::spin_mutex::scoped_lock lock; - // Lock by ROII, so if the emplace_back() fails, the lock will be released. - lock.acquire(new_volume_mutex); - m_volumes.volumes.emplace_back(volume); - lock.release(); - return volume; - }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - // Limit the number of threads as the code below does not scale well due to memory pressure. - // (most of the time is spent in malloc / free / memmove) - // Not using all the threads leaves some of the threads to G-code generator. - tbb::task_arena limited_arena(std::min(tbb::this_task_arena::max_concurrency(), 4)); - limited_arena.execute([&ctxt, grain_size, &new_volume, is_selected_separate_extruder, this]{ - tbb::parallel_for( - tbb::blocked_range(0, ctxt.layers.size(), grain_size), - [&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range& range) { - GLVolumePtrs vols; -#if ENABLE_LEGACY_OPENGL_REMOVAL - std::vector geometries; - auto select_geometry = [&ctxt, &geometries](size_t layer_idx, int extruder, int feature) -> GLModel::Geometry& { - return geometries[ctxt.color_by_color_print() ? - ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : - ctxt.color_by_tool() ? - std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : - feature - ]; - }; -#else - auto volume = [&ctxt, &vols](size_t layer_idx, int extruder, int feature) -> GLVolume& { - return *vols[ctxt.color_by_color_print() ? - ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : - ctxt.color_by_tool() ? - std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : - feature - ]; - }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (ctxt.color_by_color_print() || ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) { - vols.emplace_back(new_volume(ctxt.color_tool(i))); -#if ENABLE_LEGACY_OPENGL_REMOVAL - geometries.emplace_back(GLModel::Geometry()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - else { - vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; -#if ENABLE_LEGACY_OPENGL_REMOVAL - geometries = { GLModel::Geometry(), GLModel::Geometry(), GLModel::Geometry() }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - assert(vols.size() == geometries.size()); - for (GLModel::Geometry& g : geometries) { - g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - } -#else - for (GLVolume *vol : vols) - // Reserving number of vertices (3x position + 3x color) - vol->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { - const Layer *layer = ctxt.layers[idx_layer]; - - if (is_selected_separate_extruder) { - bool at_least_one_has_correct_extruder = false; - for (const LayerRegion* layerm : layer->regions()) { - if (layerm->slices.surfaces.empty()) - continue; - const PrintRegionConfig& cfg = layerm->region().config(); - if (cfg.perimeter_extruder.value == m_selected_extruder || - cfg.infill_extruder.value == m_selected_extruder || - cfg.solid_infill_extruder.value == m_selected_extruder ) { - at_least_one_has_correct_extruder = true; - break; - } - } - if (!at_least_one_has_correct_extruder) - continue; - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume* vol = vols[i]; - if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { - vol->print_zs.emplace_back(layer->print_z); - vol->offsets.emplace_back(geometries[i].indices_count()); - } - } -#else - for (GLVolume* vol : vols) - if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { - vol->print_zs.emplace_back(layer->print_z); - vol->offsets.emplace_back(vol->indexed_vertex_array.quad_indices.size()); - vol->offsets.emplace_back(vol->indexed_vertex_array.triangle_indices.size()); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - for (const PrintInstance &instance : *ctxt.shifted_copies) { - const Point © = instance.shift; - for (const LayerRegion *layerm : layer->regions()) { - if (is_selected_separate_extruder) { - const PrintRegionConfig& cfg = layerm->region().config(); - if (cfg.perimeter_extruder.value != m_selected_extruder || - cfg.infill_extruder.value != m_selected_extruder || - cfg.solid_infill_extruder.value != m_selected_extruder) - continue; - } - if (ctxt.has_perimeters) -#if ENABLE_LEGACY_OPENGL_REMOVAL - _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - select_geometry(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); -#else - _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (ctxt.has_infill) { - for (const ExtrusionEntity *ee : layerm->fills.entities) { - // fill represents infill extrusions of a single island. - const auto *fill = dynamic_cast(ee); - if (! fill->entities.empty()) -#if ENABLE_LEGACY_OPENGL_REMOVAL - _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - select_geometry(idx_layer, is_solid_infill(fill->entities.front()->role()) ? - layerm->region().config().solid_infill_extruder : - layerm->region().config().infill_extruder, 1)); -#else - _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - volume(idx_layer, - is_solid_infill(fill->entities.front()->role()) ? - layerm->region().config().solid_infill_extruder : - layerm->region().config().infill_extruder, - 1)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - } - if (ctxt.has_support) { - const SupportLayer *support_layer = dynamic_cast(layer); - if (support_layer) { - for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) -#if ENABLE_LEGACY_OPENGL_REMOVAL - _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - select_geometry(idx_layer, (extrusion_entity->role() == erSupportMaterial) ? - support_layer->object()->config().support_material_extruder : - support_layer->object()->config().support_material_interface_extruder, 2)); -#else - _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - volume(idx_layer, - (extrusion_entity->role() == erSupportMaterial) ? - support_layer->object()->config().support_material_extruder : - support_layer->object()->config().support_material_interface_extruder, - 2)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - } - // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { - vol.model.init_from(std::move(geometries[i])); -#else - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - vols[i] = new_volume(vol.color); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - reserve_new_volume_finalize_old_volume(*vols[i], vol, false); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - } - } - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t i = 0; i < vols.size(); ++i) { - if (!geometries[i].is_empty()) - vols[i]->model.init_from(std::move(geometries[i])); - } -#else - for (GLVolume *vol : vols) - // Ideally one would call vol->indexed_vertex_array.finalize() here to move the buffers to the OpenGL driver, - // but this code runs in parallel and the OpenGL driver is not thread safe. - vol->indexed_vertex_array.shrink_to_fit(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - }); - }); // task arena - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); - // Remove empty volumes from the newly added volumes. - { - for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) - if ((*ptr_it)->empty()) { - delete *ptr_it; - *ptr_it = nullptr; - } - m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); - } - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { - GLVolume* v = m_volumes.volumes[i]; -#if ENABLE_LEGACY_OPENGL_REMOVAL - v->is_outside = !contains(build_volume, v->model); - // We are done editinig the model, now it can be sent to gpu - v->model.enable_render(); -#else - v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); - v->indexed_vertex_array.finalize_geometry(m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); -} - -void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, const std::vector& str_tool_colors) -{ - const Print *print = this->fff_print(); - if (print == nullptr || print->wipe_tower_data().tool_changes.empty()) - return; - - if (!print->is_step_done(psWipeTower)) - return; - - std::vector tool_colors; - decode_colors(str_tool_colors, tool_colors); - - struct Ctxt - { - const Print *print; - const std::vector *tool_colors; - Vec2f wipe_tower_pos; - float wipe_tower_angle; - - static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() : 0; } - const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } - int volume_idx(int tool, int feature) const { - return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; - } - - const std::vector& tool_change(size_t idx) { - const auto &tool_changes = print->wipe_tower_data().tool_changes; - return priming.empty() ? - ((idx == tool_changes.size()) ? final : tool_changes[idx]) : - ((idx == 0) ? priming : (idx == tool_changes.size() + 1) ? final : tool_changes[idx - 1]); - } - std::vector priming; - std::vector final; - } ctxt; - - ctxt.print = print; - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - if (print->wipe_tower_data().priming && print->config().single_extruder_multi_material_priming) - for (int i=0; i<(int)print->wipe_tower_data().priming.get()->size(); ++i) - ctxt.priming.emplace_back(print->wipe_tower_data().priming.get()->at(i)); - if (print->wipe_tower_data().final_purge) - ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get()); - - ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI; - ctxt.wipe_tower_pos = Vec2f(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); - - //FIXME Improve the heuristics for a grain size. - size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); - size_t grain_size = std::max(n_items / 128, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { - auto *volume = new GLVolume(color); - volume->is_extrusion_path = true; -#if ENABLE_LEGACY_OPENGL_REMOVAL - // to prevent sending data to gpu (in the main thread) while - // editing the model geometry - volume->model.disable_render(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - tbb::spin_mutex::scoped_lock lock; - lock.acquire(new_volume_mutex); - m_volumes.volumes.emplace_back(volume); - lock.release(); - return volume; - }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - std::vector volumes_per_thread(n_items); - tbb::parallel_for( - tbb::blocked_range(0, n_items, grain_size), - [&ctxt, &new_volume](const tbb::blocked_range& range) { - // Bounding box of this slab of a wipe tower. - GLVolumePtrs vols; -#if ENABLE_LEGACY_OPENGL_REMOVAL - std::vector geometries; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) { - vols.emplace_back(new_volume(ctxt.color_tool(i))); -#if ENABLE_LEGACY_OPENGL_REMOVAL - geometries.emplace_back(GLModel::Geometry()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - else { - vols = { new_volume(ctxt.color_support()) }; -#if ENABLE_LEGACY_OPENGL_REMOVAL - geometries = { GLModel::Geometry() }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - assert(vols.size() == geometries.size()); - for (GLModel::Geometry& g : geometries) { - g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - } -#else - for (GLVolume *volume : vols) - // Reserving number of vertices (3x position + 3x color) - volume->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { - const std::vector &layer = ctxt.tool_change(idx_layer); - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { - vol.print_zs.emplace_back(layer.front().print_z); -#if ENABLE_LEGACY_OPENGL_REMOVAL - vol.offsets.emplace_back(geometries[i].indices_count()); -#else - vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - for (const WipeTower::ToolChangeResult &extrusions : layer) { - for (size_t i = 1; i < extrusions.extrusions.size();) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; - if (e.width == 0.) { - ++i; - continue; - } - size_t j = i + 1; - if (ctxt.color_by_tool()) - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j); - else - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j); - size_t n_lines = j - i; - Lines lines; - std::vector widths; - std::vector heights; - lines.reserve(n_lines); - widths.reserve(n_lines); - heights.assign(n_lines, extrusions.layer_height); - WipeTower::Extrusion e_prev = extrusions.extrusions[i-1]; - - if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation - e_prev.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e_prev.pos; - e_prev.pos += ctxt.wipe_tower_pos; - } - - for (; i < j; ++i) { - WipeTower::Extrusion e = extrusions.extrusions[i]; - assert(e.width > 0.f); - if (!extrusions.priming) { - e.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e.pos; - e.pos += ctxt.wipe_tower_pos; - } - - lines.emplace_back(Point::new_scale(e_prev.pos.x(), e_prev.pos.y()), Point::new_scale(e.pos.x(), e.pos.y())); - widths.emplace_back(e.width); - - e_prev = e; - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, - geometries[ctxt.volume_idx(e.tool, 0)]); -#else - _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, - *vols[ctxt.volume_idx(e.tool, 0)]); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { - vol.model.init_from(std::move(geometries[i])); -#else - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - vols[i] = new_volume(vol.color); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - reserve_new_volume_finalize_old_volume(*vols[i], vol, false); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - } - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (size_t i = 0; i < vols.size(); ++i) { - if (!geometries[i].is_empty()) - vols[i]->model.init_from(std::move(geometries[i])); - } -#else - for (GLVolume *vol : vols) - vol->indexed_vertex_array.shrink_to_fit(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - }); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); - // Remove empty volumes from the newly added volumes. - { - for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) - if ((*ptr_it)->empty()) { - delete *ptr_it; - *ptr_it = nullptr; - } - m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); - } - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { - GLVolume* v = m_volumes.volumes[i]; -#if ENABLE_LEGACY_OPENGL_REMOVAL - v->is_outside = !contains(build_volume, v->model); - // We are done editinig the model, now it can be sent to gpu - v->model.enable_render(); -#else - v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); - v->indexed_vertex_array.finalize_geometry(m_initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); -} - -// While it looks like we can call -// this->reload_scene(true, true) -// the two functions are quite different: -// 1) This function only loads objects, for which the step slaposSliceSupports already finished. Therefore objects outside of the print bed never load. -// 2) This function loads object mesh with the relative scaling correction (the "relative_correction" parameter) was applied, -// therefore the mesh may be slightly larger or smaller than the mesh shown in the 3D scene. -void GLCanvas3D::_load_sla_shells() -{ - const SLAPrint* print = this->sla_print(); - if (print->objects().empty()) - // nothing to render, return - return; - - auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance, - const TriangleMesh& mesh, const ColorRGBA& color, bool outside_printer_detection_enabled) { - m_volumes.volumes.emplace_back(new GLVolume(color)); - GLVolume& v = *m_volumes.volumes.back(); -#if ENABLE_SMOOTH_NORMALS -#if ENABLE_LEGACY_OPENGL_REMOVAL - v.model.init_from(mesh, true); -#else - v.indexed_vertex_array.load_mesh(mesh, true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#else -#if ENABLE_LEGACY_OPENGL_REMOVAL - v.model.init_from(mesh); -#else - v.indexed_vertex_array.load_mesh(mesh); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#endif // ENABLE_SMOOTH_NORMALS -#if !ENABLE_LEGACY_OPENGL_REMOVAL - v.indexed_vertex_array.finalize_geometry(m_initialized); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; - v.composite_id.volume_id = volume_id; - v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0.0)); - v.set_instance_rotation({ 0.0, 0.0, (double)instance.rotation }); - v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); - v.set_convex_hull(mesh.convex_hull_3d()); - }; - - // adds objects' volumes - for (const SLAPrintObject* obj : print->objects()) - if (obj->is_step_done(slaposSliceSupports)) { - unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); - for (const SLAPrintObject::Instance& instance : obj->instances()) { - add_volume(*obj, 0, instance, obj->get_mesh_to_print(), GLVolume::MODEL_COLOR[0], true); - // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when - // through the update_volumes_colors_by_extruder() call. - m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); - if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) - add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true); - if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad)) - add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); - } - double shift_z = obj->get_current_elevation(); - for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { - // apply shift z - m_volumes.volumes[i]->set_sla_shift_z(shift_z); - } - } - - update_volumes_colors_by_extruder(); -} - -void GLCanvas3D::_update_sla_shells_outside_state() -{ - check_volumes_outside_state(); -} - -void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) -{ - _set_current(); - bool show = false; - if (!m_volumes.empty()) { - if (current_printer_technology() == ptSLA) { - const auto [res, volume] = _is_any_volume_outside(); - if (res) { - if (warning == EWarning::ObjectClashed) - show = !volume->is_sla_support(); - else if (warning == EWarning::SlaSupportsOutside) - show = volume->is_sla_support(); - } - } - else - show = _is_any_volume_outside().first; - } - else { - if (wxGetApp().is_editor()) { - if (current_printer_technology() != ptSLA) - show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); - } - } - - _set_warning_notification(warning, show); -} - -void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) -{ - enum ErrorType{ - PLATER_WARNING, - PLATER_ERROR, - SLICING_ERROR - }; - std::string text; - ErrorType error = ErrorType::PLATER_WARNING; - switch (warning) { - case EWarning::ObjectOutside: text = _u8L("An object outside the print area was detected."); break; - case EWarning::ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = ErrorType::SLICING_ERROR; break; - case EWarning::SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break; - case EWarning::SomethingNotShown: text = _u8L("Some objects are not visible during editing."); break; - case EWarning::ObjectClashed: - text = _u8L("An object outside the print area was detected.\n" - "Resolve the current problem to continue slicing."); - error = ErrorType::PLATER_ERROR; - break; - } - auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); - switch (error) - { - case PLATER_WARNING: - if (state) - notification_manager.push_plater_warning_notification(text); - else - notification_manager.close_plater_warning_notification(text); - break; - case PLATER_ERROR: - if (state) - notification_manager.push_plater_error_notification(text); - else - notification_manager.close_plater_error_notification(text); - break; - case SLICING_ERROR: - if (state) - notification_manager.push_slicing_error_notification(text); - else - notification_manager.close_slicing_error_notification(text); - break; - default: - break; - } -} - -std::pair GLCanvas3D::_is_any_volume_outside() const -{ - for (const GLVolume* volume : m_volumes.volumes) { - if (volume != nullptr && volume->is_outside) - return std::make_pair(true, volume); - } - - return std::make_pair(false, nullptr); -} - -void GLCanvas3D::_update_selection_from_hover() -{ - bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); - bool selection_changed = false; - - if (m_hover_volume_idxs.empty()) { - if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) { - selection_changed = ! m_selection.is_empty(); - m_selection.remove_all(); - } - } - - GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); - - bool hover_modifiers_only = true; - for (int i : m_hover_volume_idxs) { - if (!m_volumes.volumes[i]->is_modifier) { - hover_modifiers_only = false; - break; - } - } - - if (!m_rectangle_selection.is_empty()) { - if (state == GLSelectionRectangle::EState::Select) { - bool contains_all = true; - for (int i : m_hover_volume_idxs) { - if (!m_selection.contains_volume((unsigned int)i)) { - contains_all = false; - break; - } - } - - // the selection is going to be modified (Add) - if (!contains_all) { - wxGetApp().plater()->take_snapshot(_L("Selection-Add from rectangle"), UndoRedo::SnapshotType::Selection); - selection_changed = true; - } - } - else { - bool contains_any = false; - for (int i : m_hover_volume_idxs) { - if (m_selection.contains_volume((unsigned int)i)) { - contains_any = true; - break; - } - } - - // the selection is going to be modified (Remove) - if (contains_any) { - wxGetApp().plater()->take_snapshot(_L("Selection-Remove from rectangle"), UndoRedo::SnapshotType::Selection); - selection_changed = true; - } - } - } - - if (!selection_changed) - return; - - Plater::SuppressSnapshots suppress(wxGetApp().plater()); - - if (state == GLSelectionRectangle::EState::Select && !ctrl_pressed) - m_selection.clear(); - - for (int i : m_hover_volume_idxs) { - if (state == GLSelectionRectangle::EState::Select) { - if (hover_modifiers_only) { - const GLVolume& v = *m_volumes.volumes[i]; - m_selection.add_volume(v.object_idx(), v.volume_idx(), v.instance_idx(), false); - } - else - m_selection.add(i, false); - } - else - m_selection.remove(i); - } - - if (m_selection.is_empty()) - m_gizmos.reset_all_states(); - else - m_gizmos.refresh_on_off_state(); - - m_gizmos.update_data(); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - m_dirty = true; -} - -bool GLCanvas3D::_deactivate_undo_redo_toolbar_items() -{ - if (m_undoredo_toolbar.is_item_pressed("undo")) { - m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("undo"), *this); - return true; - } - else if (m_undoredo_toolbar.is_item_pressed("redo")) { - m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("redo"), *this); - return true; - } - - return false; -} - -bool GLCanvas3D::is_search_pressed() const -{ - return m_main_toolbar.is_item_pressed("search"); -} - -bool GLCanvas3D::_deactivate_arrange_menu() -{ - if (m_main_toolbar.is_item_pressed("arrange")) { - m_main_toolbar.force_right_action(m_main_toolbar.get_item_id("arrange"), *this); - return true; - } - - return false; -} - -bool GLCanvas3D::_deactivate_search_toolbar_item() -{ - if (is_search_pressed()) { - m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this); - return true; - } - - return false; -} - -bool GLCanvas3D::_activate_search_toolbar_item() -{ - if (!m_main_toolbar.is_item_pressed("search")) { - m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this); - return true; - } - - return false; -} - -bool GLCanvas3D::_deactivate_collapse_toolbar_items() -{ - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - if (collapse_toolbar.is_item_pressed("print")) { - collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this); - return true; - } - - return false; -} - -void GLCanvas3D::highlight_toolbar_item(const std::string& item_name) -{ - GLToolbarItem* item = m_main_toolbar.get_item(item_name); - if (!item) - item = m_undoredo_toolbar.get_item(item_name); - if (!item || !item->is_visible()) - return; - m_toolbar_highlighter.init(item, this); -} - -void GLCanvas3D::highlight_gizmo(const std::string& gizmo_name) -{ - GLGizmosManager::EType gizmo = m_gizmos.get_gizmo_from_name(gizmo_name); - if(gizmo == GLGizmosManager::EType::Undefined) - return; - m_gizmo_highlighter.init(&m_gizmos, gizmo, this); -} - -const Print* GLCanvas3D::fff_print() const -{ - return (m_process == nullptr) ? nullptr : m_process->fff_print(); -} - -const SLAPrint* GLCanvas3D::sla_print() const -{ - return (m_process == nullptr) ? nullptr : m_process->sla_print(); -} - -void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const -{ - DynamicPrintConfig cfg; - cfg.opt("wipe_tower_x", true)->value = m_pos(X); - cfg.opt("wipe_tower_y", true)->value = m_pos(Y); - cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; - wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); -} - -void GLCanvas3D::RenderTimer::Notify() -{ - wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this)); -} - -void GLCanvas3D::ToolbarHighlighterTimer::Notify() -{ - wxPostEvent((wxEvtHandler*)GetOwner(), ToolbarHighlighterTimerEvent(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, *this)); -} - -void GLCanvas3D::GizmoHighlighterTimer::Notify() -{ - wxPostEvent((wxEvtHandler*)GetOwner(), GizmoHighlighterTimerEvent(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, *this)); -} - -void GLCanvas3D::ToolbarHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) -{ - m_timer.SetOwner(owner, timerid); -} - -void GLCanvas3D::ToolbarHighlighter::init(GLToolbarItem* toolbar_item, GLCanvas3D* canvas) -{ - if (m_timer.IsRunning()) - invalidate(); - if (!toolbar_item || !canvas) - return; - - m_timer.Start(300, false); - - m_toolbar_item = toolbar_item; - m_canvas = canvas; -} - -void GLCanvas3D::ToolbarHighlighter::invalidate() -{ - m_timer.Stop(); - - if (m_toolbar_item) { - m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::NotHighlighted); - } - m_toolbar_item = nullptr; - m_blink_counter = 0; - m_render_arrow = false; -} - -void GLCanvas3D::ToolbarHighlighter::blink() -{ - if (m_toolbar_item) { - char state = m_toolbar_item->get_highlight(); - if (state != (char)GLToolbarItem::EHighlightState::HighlightedShown) - m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedShown); - else - m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedHidden); - - m_render_arrow = !m_render_arrow; - m_canvas->set_as_dirty(); - } - else - invalidate(); - - if ((++m_blink_counter) >= 11) - invalidate(); -} - -void GLCanvas3D::GizmoHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) -{ - m_timer.SetOwner(owner, timerid); -} - -void GLCanvas3D::GizmoHighlighter::init(GLGizmosManager* manager, GLGizmosManager::EType gizmo, GLCanvas3D* canvas) -{ - if (m_timer.IsRunning()) - invalidate(); - if (!gizmo || !canvas) - return; - - m_timer.Start(300, false); - - m_gizmo_manager = manager; - m_gizmo_type = gizmo; - m_canvas = canvas; -} - -void GLCanvas3D::GizmoHighlighter::invalidate() -{ - m_timer.Stop(); - - if (m_gizmo_manager) { - m_gizmo_manager->set_highlight(GLGizmosManager::EType::Undefined, false); - } - m_gizmo_manager = nullptr; - m_gizmo_type = GLGizmosManager::EType::Undefined; - m_blink_counter = 0; - m_render_arrow = false; -} - -void GLCanvas3D::GizmoHighlighter::blink() -{ - if (m_gizmo_manager) { - if (m_blink_counter % 2 == 0) - m_gizmo_manager->set_highlight(m_gizmo_type, true); - else - m_gizmo_manager->set_highlight(m_gizmo_type, false); - - m_render_arrow = !m_render_arrow; - m_canvas->set_as_dirty(); - } - else - invalidate(); - - if ((++m_blink_counter) >= 11) - invalidate(); -} - -} // namespace GUI -} // namespace Slic3r +#include "libslic3r/libslic3r.h" +#include "GLCanvas3D.hpp" + +#include + +#include "libslic3r/BuildVolume.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/Technologies.hpp" +#include "libslic3r/Tesselate.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "3DBed.hpp" +#include "3DScene.hpp" +#include "BackgroundSlicingProcess.hpp" +#include "GLShader.hpp" +#include "GUI.hpp" +#include "Tab.hpp" +#include "GUI_Preview.hpp" +#include "OpenGLManager.hpp" +#include "Plater.hpp" +#include "MainFrame.hpp" +#include "GUI_App.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "Mouse3DController.hpp" +#include "I18N.hpp" +#include "NotificationManager.hpp" +#include "format.hpp" + +#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + +#if ENABLE_RETINA_GL +#include "slic3r/Utils/RetinaHelper.hpp" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx. +#include "libslic3r/Print.hpp" +#include "libslic3r/SLAPrint.hpp" + +#include "wxExtensions.hpp" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include "DoubleSlider.hpp" + +#include + +static constexpr const float TRACKBALLSIZE = 0.8f; + +#if ENABLE_LEGACY_OPENGL_REMOVAL +static const Slic3r::ColorRGBA DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f, 1.0f }; +static const Slic3r::ColorRGBA ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f, 1.0f }; +static const Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f, 1.0f }; +#else +static const Slic3r::ColorRGB DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f }; +static const Slic3r::ColorRGB DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f }; +static const Slic3r::ColorRGB ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f }; +static const Slic3r::ColorRGB ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +// Number of floats +static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB +// Reserve size in number of floats. +#if !ENABLE_LEGACY_OPENGL_REMOVAL +static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE = 131072 * 2; // 1.05MB +// Reserve size in number of floats, maximum sum of all preallocated buffers. +//static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4; // 128MB +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +namespace Slic3r { +namespace GUI { + +#ifdef __WXGTK3__ +// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support. +RetinaHelper::RetinaHelper(wxWindow* window) : m_window(window), m_self(nullptr) {} +RetinaHelper::~RetinaHelper() {} +float RetinaHelper::get_scale_factor() { return float(m_window->GetContentScaleFactor()); } +#endif // __WXGTK3__ + +// Fixed the collision between BuildVolume::Type::Convex and macro Convex defined inside /usr/include/X11/X.h that is included by WxWidgets 3.0. +#if defined(__linux__) && defined(Convex) +#undef Convex +#endif + +GLCanvas3D::LayersEditing::~LayersEditing() +{ + if (m_z_texture_id != 0) { + glsafe(::glDeleteTextures(1, &m_z_texture_id)); + m_z_texture_id = 0; + } + delete m_slicing_parameters; +} + +const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f; + +void GLCanvas3D::LayersEditing::init() +{ + glsafe(::glGenTextures(1, (GLuint*)&m_z_texture_id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); + if (!OpenGLManager::get_gl_info().is_core_profile() || !OpenGLManager::get_gl_info().is_mesa()) { + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + } + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); +} + +void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) +{ + m_config = config; + delete m_slicing_parameters; + m_slicing_parameters = nullptr; + m_layers_texture.valid = false; +} + +void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) +{ + const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr; + // Maximum height of an object changes when the object gets rotated or scaled. + // Changing maximum height of an object will invalidate the layer heigth editing profile. + // m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently. + const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast(model_object_new->bounding_box().max.z()); + if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || + (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { + m_layer_height_profile.clear(); + m_layer_height_profile_modified = false; + delete m_slicing_parameters; + m_slicing_parameters = nullptr; + m_layers_texture.valid = false; + this->last_object_id = object_id; + m_model_object = model_object_new; + m_object_max_z = new_max_z; + } +} + +bool GLCanvas3D::LayersEditing::is_allowed() const +{ + return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0; +} + +bool GLCanvas3D::LayersEditing::is_enabled() const +{ + return m_enabled; +} + +void GLCanvas3D::LayersEditing::set_enabled(bool enabled) +{ + m_enabled = is_allowed() && enabled; +} + +float GLCanvas3D::LayersEditing::s_overlay_window_width; + +void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) +{ + if (!m_enabled) + return; + + const Size& cnv_size = canvas.get_canvas_size(); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.set_next_window_pos(static_cast(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, + static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); + + imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Left mouse button:")); + ImGui::SameLine(); + imgui.text(_L("Add detail")); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Right mouse button:")); + ImGui::SameLine(); + imgui.text(_L("Remove detail")); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Left mouse button:")); + ImGui::SameLine(); + imgui.text(_L("Reset to base")); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Right mouse button:")); + ImGui::SameLine(); + imgui.text(_L("Smoothing")); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mouse wheel:")); + ImGui::SameLine(); + imgui.text(_L("Increase/decrease edit area")); + + ImGui::Separator(); + if (imgui.button(_L("Adaptive"))) + wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality)); + + ImGui::SameLine(); + float text_align = ImGui::GetCursorPosX(); + ImGui::AlignTextToFramePadding(); + imgui.text(_L("Quality / Speed")); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8()); + ImGui::EndTooltip(); + } + + ImGui::SameLine(); + float widget_align = ImGui::GetCursorPosX(); + ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); + m_adaptive_quality = std::clamp(m_adaptive_quality, 0.0f, 1.f); + imgui.slider_float("", &m_adaptive_quality, 0.0f, 1.f, "%.2f"); + + ImGui::Separator(); + if (imgui.button(_L("Smooth"))) + wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params)); + + ImGui::SameLine(); + ImGui::SetCursorPosX(text_align); + ImGui::AlignTextToFramePadding(); + imgui.text(_L("Radius")); + ImGui::SameLine(); + ImGui::SetCursorPosX(widget_align); + ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); + int radius = (int)m_smooth_params.radius; + if (ImGui::SliderInt("##1", &radius, 1, 10)) { + radius = std::clamp(radius, 1, 10); + m_smooth_params.radius = (unsigned int)radius; + } + + ImGui::SetCursorPosX(text_align); + ImGui::AlignTextToFramePadding(); + imgui.text(_L("Keep min")); + ImGui::SameLine(); + if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization + ImGui::SetCursorPosX(widget_align); + + ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); + imgui.checkbox("##2", m_smooth_params.keep_min); + + ImGui::Separator(); + if (imgui.button(_L("Reset"))) + wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); + + GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/; + imgui.end(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + render_active_object_annotations(canvas); + render_profile(canvas); +#else + const Rect& bar_rect = get_bar_rect_viewport(canvas); + render_active_object_annotations(canvas, bar_rect); + render_profile(bar_rect); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) +{ + const Vec2d mouse_pos = canvas.get_local_mouse_position(); + const Rect& rect = get_bar_rect_screen(canvas); + float x = (float)mouse_pos.x(); + float y = (float)mouse_pos.y(); + float t = rect.get_top(); + float b = rect.get_bottom(); + + return (rect.get_left() <= x && x <= rect.get_right() && t <= y && y <= b) ? + // Inside the bar. + (b - y - 1.0f) / (b - t - 1.0f) : + // Outside the bar. + -1000.0f; +} + +bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y) +{ + const Rect& rect = get_bar_rect_screen(canvas); + return rect.get_left() <= x && x <= rect.get_right() && rect.get_top() <= y && y <= rect.get_bottom(); +} + +Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float w = (float)cnv_size.get_width(); + float h = (float)cnv_size.get_height(); + + return { w - thickness_bar_width(canvas), 0.0f, w, h }; +} + +#if !ENABLE_LEGACY_OPENGL_REMOVAL +Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float half_w = 0.5f * (float)cnv_size.get_width(); + float half_h = 0.5f * (float)cnv_size.get_height(); + float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + return { (half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom }; +} +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +bool GLCanvas3D::LayersEditing::is_initialized() const +{ + return wxGetApp().get_shader("variable_layer_height") != nullptr; +} + +std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const +{ + std::string ret; + if (m_enabled && m_layer_height_profile.size() >= 4) { + float z = get_cursor_z_relative(canvas); + if (z != -1000.0f) { + z *= m_object_max_z; + + float h = 0.0f; + for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2) { + const float zi = static_cast(m_layer_height_profile[i]); + const float zi_1 = static_cast(m_layer_height_profile[i - 2]); + if (zi_1 <= z && z <= zi) { + float dz = zi - zi_1; + h = (dz != 0.0f) ? static_cast(lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz)) : + static_cast(m_layer_height_profile[i + 1]); + break; + } + } + if (h > 0.0f) + ret = std::to_string(h); + } + } + return ret; +} + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas) +{ + const Size cnv_size = canvas.get_canvas_size(); + const float cnv_width = (float)cnv_size.get_width(); + const float cnv_height = (float)cnv_size.get_height(); + if (cnv_width == 0.0f || cnv_height == 0.0f) + return; + + const float cnv_inv_width = 1.0f / cnv_width; +#else +void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) +{ +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); + if (shader == nullptr) + return; + + shader->start_using(); + + shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z)); + shader->set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height); + shader->set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); + shader->set_uniform("z_cursor_band_width", band_width); + shader->set_uniform("object_max_z", m_object_max_z); +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); + + // Render the color bar +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!m_profile.background.is_initialized() || m_profile.old_canvas_width != cnv_width) { + m_profile.old_canvas_width = cnv_width; + m_profile.background.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3T2 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + const float l = 1.0f - 2.0f * THICKNESS_BAR_WIDTH * cnv_inv_width; + const float r = 1.0f; + const float t = 1.0f; + const float b = -1.0f; + init_data.add_vertex(Vec3f(l, b, 0.0f), Vec3f::UnitZ(), Vec2f(0.0f, 0.0f)); + init_data.add_vertex(Vec3f(r, b, 0.0f), Vec3f::UnitZ(), Vec2f(1.0f, 0.0f)); + init_data.add_vertex(Vec3f(r, t, 0.0f), Vec3f::UnitZ(), Vec2f(1.0f, 1.0f)); + init_data.add_vertex(Vec3f(l, t, 0.0f), Vec3f::UnitZ(), Vec2f(0.0f, 1.0f)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_profile.background.init_from(std::move(init_data)); + } + + m_profile.background.render(); +#else + const float l = bar_rect.get_left(); + const float r = bar_rect.get_right(); + const float t = bar_rect.get_top(); + const float b = bar_rect.get_bottom(); + + ::glBegin(GL_QUADS); + ::glNormal3f(0.0f, 0.0f, 1.0f); + ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(l, b); + ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(r, b); + ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t); + ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + + shader->stop_using(); +} + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void GLCanvas3D::LayersEditing::render_profile(const GLCanvas3D& canvas) +#else +void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +{ + //FIXME show some kind of legend. + + if (!m_slicing_parameters) + return; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Size cnv_size = canvas.get_canvas_size(); + const float cnv_width = (float)cnv_size.get_width(); + const float cnv_height = (float)cnv_size.get_height(); + if (cnv_width == 0.0f || cnv_height == 0.0f) + return; + + // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. + const float scale_x = THICKNESS_BAR_WIDTH / float(1.12 * m_slicing_parameters->max_layer_height); + const float scale_y = cnv_height / m_object_max_z; + + const float cnv_inv_width = 1.0f / cnv_width; + const float cnv_inv_height = 1.0f / cnv_height; +#else + // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. + const float scale_x = bar_rect.get_width() / float(1.12 * m_slicing_parameters->max_layer_height); + const float scale_y = bar_rect.get_height() / m_object_max_z; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + // Baseline + if (!m_profile.baseline.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) { + m_profile.baseline.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2 }; + init_data.color = ColorRGBA::BLACK(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + const float axis_x = 2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_slicing_parameters->layer_height) * scale_x) * cnv_inv_width - 0.5f); + init_data.add_vertex(Vec2f(axis_x, -1.0f)); + init_data.add_vertex(Vec2f(axis_x, 1.0f)); + + // indices + init_data.add_line(0, 1); + + m_profile.baseline.init_from(std::move(init_data)); + } + + if (!m_profile.profile.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) { + m_profile.old_layer_height_profile = m_layer_height_profile; + m_profile.profile.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2 }; + init_data.color = ColorRGBA::BLUE(); + init_data.reserve_vertices(m_layer_height_profile.size() / 2); + init_data.reserve_indices(m_layer_height_profile.size() / 2); + + // vertices + indices + for (unsigned int i = 0; i < (unsigned int)m_layer_height_profile.size(); i += 2) { + init_data.add_vertex(Vec2f(2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_layer_height_profile[i + 1]) * scale_x) * cnv_inv_width - 0.5f), + 2.0f * (float(m_layer_height_profile[i]) * scale_y * cnv_inv_height - 0.5))); + init_data.add_index(i / 2); + } + + m_profile.profile.init_from(std::move(init_data)); + } + +#if ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_CORE_PROFILE + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); +#if ENABLE_GL_CORE_PROFILE + const std::array& viewport = wxGetApp().plater()->get_camera().get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#endif // ENABLE_GL_CORE_PROFILE + m_profile.baseline.render(); + m_profile.profile.render(); + shader->stop_using(); + } +#else + const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x; + + // Baseline + glsafe(::glColor3f(0.0f, 0.0f, 0.0f)); + ::glBegin(GL_LINE_STRIP); + ::glVertex2f(x, bar_rect.get_bottom()); + ::glVertex2f(x, bar_rect.get_top()); + glsafe(::glEnd()); + + // Curve + glsafe(::glColor3f(0.0f, 0.0f, 1.0f)); + ::glBegin(GL_LINE_STRIP); + for (unsigned int i = 0; i < m_layer_height_profile.size(); i += 2) + ::glVertex2f(bar_rect.get_left() + (float)m_layer_height_profile[i + 1] * scale_x, bar_rect.get_bottom() + (float)m_layer_height_profile[i] * scale_y); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes) +{ + assert(this->is_allowed()); + assert(this->last_object_id != -1); + + GLShaderProgram* current_shader = wxGetApp().get_current_shader(); + ScopeGuard guard([current_shader]() { if (current_shader != nullptr) current_shader->start_using(); }); + if (current_shader != nullptr) + current_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); + if (shader == nullptr) + return; + + shader->start_using(); + + generate_layer_height_texture(); + + // Uniforms were resolved, go ahead using the layer editing shader. + shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * float(m_object_max_z))); + shader->set_uniform("z_texture_row_to_normalized", 1.0f / float(m_layers_texture.height)); + shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas))); + shader->set_uniform("z_cursor_band_width", float(this->band_width)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // Initialize the layer height texture mapping. + const GLsizei w = (GLsizei)m_layers_texture.width; + const GLsizei h = (GLsizei)m_layers_texture.height; + const GLsizei half_w = w / 2; + const GLsizei half_h = h / 2; + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); + glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); + for (GLVolume* glvolume : volumes.volumes) { + // Render the object using the layer editing shader and texture. + if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) + continue; + + shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); + shader->set_uniform("object_max_z", 0.0f); +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d model_matrix = glvolume->world_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glvolume->render(); + } + // Revert back to the previous shader. + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); +} + +void GLCanvas3D::LayersEditing::adjust_layer_height_profile() +{ + this->update_slicing_parameters(); + PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile); + Slic3r::adjust_layer_height_profile(*m_slicing_parameters, m_layer_height_profile, this->last_z, this->strength, this->band_width, this->last_action); + m_layer_height_profile_modified = true; + m_layers_texture.valid = false; +} + +void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas) +{ + const_cast(m_model_object)->layer_height_profile.clear(); + m_layer_height_profile.clear(); + m_layers_texture.valid = false; + canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); +} + +void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor) +{ + this->update_slicing_parameters(); + m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, quality_factor); + const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); + m_layers_texture.valid = false; + canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); +} + +void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params) +{ + this->update_slicing_parameters(); + m_layer_height_profile = smooth_height_profile(m_layer_height_profile, *m_slicing_parameters, smoothing_params); + const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); + m_layers_texture.valid = false; + canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); +} + +void GLCanvas3D::LayersEditing::generate_layer_height_texture() +{ + this->update_slicing_parameters(); + // Always try to update the layer height profile. + bool update = ! m_layers_texture.valid; + if (PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile)) { + // Initialized to the default value. + m_layer_height_profile_modified = false; + update = true; + } + // Update if the layer height profile was changed, or when the texture is not valid. + if (! update && ! m_layers_texture.data.empty() && m_layers_texture.cells > 0) + // Texture is valid, don't update. + return; + + if (m_layers_texture.data.empty()) { + m_layers_texture.width = 1024; + m_layers_texture.height = 1024; + m_layers_texture.levels = 2; + m_layers_texture.data.assign(m_layers_texture.width * m_layers_texture.height * 5, 0); + } + + bool level_of_detail_2nd_level = true; + m_layers_texture.cells = Slic3r::generate_layer_height_texture( + *m_slicing_parameters, + Slic3r::generate_object_layers(*m_slicing_parameters, m_layer_height_profile), + m_layers_texture.data.data(), m_layers_texture.height, m_layers_texture.width, level_of_detail_2nd_level); + m_layers_texture.valid = true; +} + +void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) +{ + if (last_object_id >= 0) { + if (m_layer_height_profile_modified) { + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit")); + const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); + canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().obj_list()->update_info_items(last_object_id); + } + } + m_layer_height_profile_modified = false; +} + +void GLCanvas3D::LayersEditing::update_slicing_parameters() +{ + if (m_slicing_parameters == nullptr) { + m_slicing_parameters = new SlicingParameters(); + *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); + } +} + +float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas) +{ + return +#if ENABLE_RETINA_GL + canvas.get_canvas_size().get_scale_factor() +#else + canvas.get_wxglcanvas()->GetContentScaleFactor() +#endif + * THICKNESS_BAR_WIDTH; +} + + +const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); +const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); +const int GLCanvas3D::Mouse::Drag::MoveThresholdPx = 5; + +GLCanvas3D::Mouse::Drag::Drag() + : start_position_2D(Invalid_2D_Point) + , start_position_3D(Invalid_3D_Point) + , move_volume_idx(-1) + , move_requires_threshold(false) + , move_start_threshold_position_2D(Invalid_2D_Point) +{ +} + +GLCanvas3D::Mouse::Mouse() + : dragging(false) + , position(DBL_MAX, DBL_MAX) + , scene_position(DBL_MAX, DBL_MAX, DBL_MAX) + , ignore_left_up(false) +{ +} + +void GLCanvas3D::Labels::render(const std::vector& sorted_instances) const +{ + if (!m_enabled || !is_shown()) + return; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Model* model = m_canvas.get_model(); + if (model == nullptr) + return; + + Transform3d world_to_eye = camera.get_view_matrix(); + Transform3d world_to_screen = camera.get_projection_matrix() * world_to_eye; + const std::array& viewport = camera.get_viewport(); + + struct Owner + { + int obj_idx; + int inst_idx; + size_t model_instance_id; + BoundingBoxf3 world_box; + double eye_center_z; + std::string title; + std::string label; + std::string print_order; + bool selected; + }; + + // collect owners world bounding boxes and data from volumes + std::vector owners; + const GLVolumeCollection& volumes = m_canvas.get_volumes(); + for (const GLVolume* volume : volumes.volumes) { + int obj_idx = volume->object_idx(); + if (0 <= obj_idx && obj_idx < (int)model->objects.size()) { + int inst_idx = volume->instance_idx(); + std::vector::iterator it = std::find_if(owners.begin(), owners.end(), [obj_idx, inst_idx](const Owner& owner) { + return (owner.obj_idx == obj_idx) && (owner.inst_idx == inst_idx); + }); + if (it != owners.end()) { + it->world_box.merge(volume->transformed_bounding_box()); + it->selected &= volume->selected; + } else { + const ModelObject* model_object = model->objects[obj_idx]; + Owner owner; + owner.obj_idx = obj_idx; + owner.inst_idx = inst_idx; + owner.model_instance_id = model_object->instances[inst_idx]->id().id; + owner.world_box = volume->transformed_bounding_box(); + owner.title = "object" + std::to_string(obj_idx) + "_inst##" + std::to_string(inst_idx); + owner.label = model_object->name; + if (model_object->instances.size() > 1) + owner.label += " (" + std::to_string(inst_idx + 1) + ")"; + owner.selected = volume->selected; + owners.emplace_back(owner); + } + } + } + + // updates print order strings + if (sorted_instances.size() > 1) { + for (size_t i = 0; i < sorted_instances.size(); ++i) { + size_t id = sorted_instances[i]->id().id; + std::vector::iterator it = std::find_if(owners.begin(), owners.end(), [id](const Owner& owner) { + return owner.model_instance_id == id; + }); + if (it != owners.end()) + it->print_order = std::string((_(L("Seq."))).ToUTF8()) + "#: " + std::to_string(i + 1); + } + } + + // calculate eye bounding boxes center zs + for (Owner& owner : owners) { + owner.eye_center_z = (world_to_eye * owner.world_box.center())(2); + } + + // sort owners by center eye zs and selection + std::sort(owners.begin(), owners.end(), [](const Owner& owner1, const Owner& owner2) { + if (!owner1.selected && owner2.selected) + return true; + else if (owner1.selected && !owner2.selected) + return false; + else + return (owner1.eye_center_z < owner2.eye_center_z); + }); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + // render info windows + for (const Owner& owner : owners) { + Vec3d screen_box_center = world_to_screen * owner.world_box.center(); + float x = 0.0f; + float y = 0.0f; + if (camera.get_type() == Camera::EType::Perspective) { + x = (0.5f + 0.001f * 0.5f * (float)screen_box_center(0)) * viewport[2]; + y = (0.5f - 0.001f * 0.5f * (float)screen_box_center(1)) * viewport[3]; + } else { + x = (0.5f + 0.5f * (float)screen_box_center(0)) * viewport[2]; + y = (0.5f - 0.5f * (float)screen_box_center(1)) * viewport[3]; + } + + if (x < 0.0f || viewport[2] < x || y < 0.0f || viewport[3] < y) + continue; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, owner.selected ? 3.0f : 1.5f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Border, owner.selected ? ImVec4(0.757f, 0.404f, 0.216f, 1.0f) : ImVec4(0.75f, 0.75f, 0.75f, 1.0f)); + imgui.set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.5f); + imgui.begin(owner.title, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + float win_w = ImGui::GetWindowWidth(); + float label_len = ImGui::CalcTextSize(owner.label.c_str()).x; + ImGui::SetCursorPosX(0.5f * (win_w - label_len)); + ImGui::AlignTextToFramePadding(); + imgui.text(owner.label); + + if (!owner.print_order.empty()) { + ImGui::Separator(); + float po_len = ImGui::CalcTextSize(owner.print_order.c_str()).x; + ImGui::SetCursorPosX(0.5f * (win_w - po_len)); + ImGui::AlignTextToFramePadding(); + imgui.text(owner.print_order); + } + + // force re-render while the windows gets to its final size (it takes several frames) + if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) + imgui.set_requires_extra_frame(); + + imgui.end(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(2); + } +} + +void GLCanvas3D::Tooltip::set_text(const std::string& text) +{ + // If the mouse is inside an ImGUI dialog, then the tooltip is suppressed. + m_text = m_in_imgui ? std::string() : text; +} + +void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas) +{ + static ImVec2 size(0.0f, 0.0f); + + auto validate_position = [](const Vec2d& position, const GLCanvas3D& canvas, const ImVec2& wnd_size) { + auto calc_cursor_height = []() { + float ret = 16.0f; +#ifdef _WIN32 + // see: https://forums.codeguru.com/showthread.php?449040-get-the-system-current-cursor-size + // this code is not perfect because it returns a maximum height equal to 31 even if the cursor bitmap shown on screen is bigger + // but at least it gives the same result as wxWidgets in the settings tabs + ICONINFO ii; + if (::GetIconInfo((HICON)GetCursor(), &ii) != 0) { + BITMAP bitmap; + ::GetObject(ii.hbmMask, sizeof(BITMAP), &bitmap); + int width = bitmap.bmWidth; + int height = (ii.hbmColor == nullptr) ? bitmap.bmHeight / 2 : bitmap.bmHeight; + HDC dc = ::CreateCompatibleDC(nullptr); + if (dc != nullptr) { + if (::SelectObject(dc, ii.hbmMask) != nullptr) { + for (int i = 0; i < width; ++i) { + for (int j = 0; j < height; ++j) { + if (::GetPixel(dc, i, j) != RGB(255, 255, 255)) { + if (ret < float(j)) + ret = float(j); + } + } + } + ::DeleteDC(dc); + } + } + ::DeleteObject(ii.hbmColor); + ::DeleteObject(ii.hbmMask); + } +#endif // _WIN32 + return ret; + }; + + const Size cnv_size = canvas.get_canvas_size(); + const float x = std::clamp(float(position.x()), 0.0f, float(cnv_size.get_width()) - wnd_size.x); + const float y = std::clamp(float(position.y()) + calc_cursor_height(), 0.0f, float(cnv_size.get_height()) - wnd_size.y); + return Vec2f(x, y); + }; + + if (m_text.empty()) { + m_start_time = std::chrono::steady_clock::now(); + return; + } + + // draw the tooltip as hidden until the delay is expired + // use a value of alpha slightly different from 0.0f because newer imgui does not calculate properly the window size if alpha == 0.0f + const float alpha = (std::chrono::duration_cast(std::chrono::steady_clock::now() - m_start_time).count() < 500) ? 0.01f : 1.0f; + + const Vec2f position = validate_position(mouse_position, canvas, size); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); + imgui.set_next_window_pos(position.x(), position.y(), ImGuiCond_Always, 0.0f, 0.0f); + + imgui.begin(wxString("canvas_tooltip"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + ImGui::TextUnformatted(m_text.c_str()); + + // force re-render while the windows gets to its final size (it may take several frames) or while hidden + if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) + imgui.set_requires_extra_frame(); + + size = ImGui::GetWindowSize(); + + imgui.end(); + ImGui::PopStyleVar(2); +} + +void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons) +{ + m_perimeter.reset(); + m_fill.reset(); + if (polygons.empty()) + return; + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + size_t triangles_count = 0; + for (const Polygon& poly : polygons) { + triangles_count += poly.points.size() - 2; + } + const size_t vertices_count = 3 * triangles_count; +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + if (m_render_fill) { + GLModel::Geometry fill_data; +#if ENABLE_LEGACY_OPENGL_REMOVAL + fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + fill_data.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; + + // vertices + indices + const ExPolygons polygons_union = union_ex(polygons); + unsigned int vertices_counter = 0; + for (const ExPolygon& poly : polygons_union) { + const std::vector triangulation = triangulate_expolygon_3d(poly); + fill_data.reserve_vertices(fill_data.vertices_count() + triangulation.size()); + fill_data.reserve_indices(fill_data.indices_count() + triangulation.size()); + for (const Vec3d& v : triangulation) { + fill_data.add_vertex((Vec3f)(v.cast() + 0.0125f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting + ++vertices_counter; + if (vertices_counter % 3 == 0) + fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + } + } + + m_fill.init_from(std::move(fill_data)); +#else + GLModel::Geometry::Entity entity; + entity.type = GLModel::EPrimitiveType::Triangles; + entity.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; + entity.positions.reserve(vertices_count); + entity.normals.reserve(vertices_count); + entity.indices.reserve(vertices_count); + + const ExPolygons polygons_union = union_ex(polygons); + for (const ExPolygon& poly : polygons_union) { + const std::vector triangulation = triangulate_expolygon_3d(poly); + for (const Vec3d& v : triangulation) { + entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.0125f)); // add a small positive z to avoid z-fighting + entity.normals.emplace_back(Vec3f::UnitZ()); + const size_t positions_count = entity.positions.size(); + if (positions_count % 3 == 0) { + entity.indices.emplace_back(positions_count - 3); + entity.indices.emplace_back(positions_count - 2); + entity.indices.emplace_back(positions_count - 1); + } + } + } + + fill_data.entities.emplace_back(entity); + m_fill.init_from(fill_data); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_perimeter.init_from(polygons, 0.025f); // add a small positive z to avoid z-fighting +#else + GLModel::Geometry perimeter_data; + for (const Polygon& poly : polygons) { + GLModel::Geometry::Entity ent; + ent.type = GLModel::EPrimitiveType::LineLoop; + ent.positions.reserve(poly.points.size()); + ent.indices.reserve(poly.points.size()); + unsigned int id_count = 0; + for (const Point& p : poly.points) { + ent.positions.emplace_back(unscale(p.x()), unscale(p.y()), 0.025f); // add a small positive z to avoid z-fighting + ent.normals.emplace_back(Vec3f::UnitZ()); + ent.indices.emplace_back(id_count++); + } + + perimeter_data.entities.emplace_back(ent); + } + + m_perimeter.init_from(perimeter_data); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLCanvas3D::SequentialPrintClearance::render() +{ + const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; + const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (shader == nullptr) + return; + + shader->start_using(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); +#else + m_perimeter.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + m_perimeter.render(); + m_fill.render(); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_DEPTH_TEST)); + + shader->stop_using(); +} + +wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); +wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); +#if ENABLE_WORLD_COORDINATE +wxDEFINE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); +#endif // ENABLE_WORLD_COORDINATE +wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); +wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); +wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_COLLAPSE_SIDEBAR, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); +wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RENDER_TIMER, wxTimerEvent/*RenderTimerEvent*/); +wxDEFINE_EVENT(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, wxTimerEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, wxTimerEvent); + +const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; + +void GLCanvas3D::load_arrange_settings() +{ + std::string dist_fff_str = + wxGetApp().app_config->get("arrange", "min_object_distance_fff"); + + std::string dist_bed_fff_str = + wxGetApp().app_config->get("arrange", "min_bed_distance_fff"); + + std::string dist_fff_seq_print_str = + wxGetApp().app_config->get("arrange", "min_object_distance_fff_seq_print"); + + std::string dist_bed_fff_seq_print_str = + wxGetApp().app_config->get("arrange", "min_bed_distance_fff_seq_print"); + + std::string dist_sla_str = + wxGetApp().app_config->get("arrange", "min_object_distance_sla"); + + std::string dist_bed_sla_str = + wxGetApp().app_config->get("arrange", "min_bed_distance_sla"); + + std::string en_rot_fff_str = + wxGetApp().app_config->get("arrange", "enable_rotation_fff"); + + std::string en_rot_fff_seqp_str = + wxGetApp().app_config->get("arrange", "enable_rotation_fff_seq_print"); + + std::string en_rot_sla_str = + wxGetApp().app_config->get("arrange", "enable_rotation_sla"); + + if (!dist_fff_str.empty()) + m_arrange_settings_fff.distance = string_to_float_decimal_point(dist_fff_str); + + if (!dist_bed_fff_str.empty()) + m_arrange_settings_fff.distance_from_bed = string_to_float_decimal_point(dist_bed_fff_str); + + if (!dist_fff_seq_print_str.empty()) + m_arrange_settings_fff_seq_print.distance = string_to_float_decimal_point(dist_fff_seq_print_str); + + if (!dist_bed_fff_seq_print_str.empty()) + m_arrange_settings_fff_seq_print.distance_from_bed = string_to_float_decimal_point(dist_bed_fff_seq_print_str); + + if (!dist_sla_str.empty()) + m_arrange_settings_sla.distance = string_to_float_decimal_point(dist_sla_str); + + if (!dist_bed_sla_str.empty()) + m_arrange_settings_sla.distance_from_bed = string_to_float_decimal_point(dist_bed_sla_str); + + if (!en_rot_fff_str.empty()) + m_arrange_settings_fff.enable_rotation = (en_rot_fff_str == "1" || en_rot_fff_str == "yes"); + + if (!en_rot_fff_seqp_str.empty()) + m_arrange_settings_fff_seq_print.enable_rotation = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes"); + + if (!en_rot_sla_str.empty()) + m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); +} + +PrinterTechnology GLCanvas3D::current_printer_technology() const +{ + return m_process->current_printer_technology(); +} + +GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) + : m_canvas(canvas) + , m_context(nullptr) + , m_bed(bed) +#if ENABLE_RETINA_GL + , m_retina_helper(nullptr) +#endif + , m_in_render(false) + , m_main_toolbar(GLToolbar::Normal, "Main") + , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") + , m_gizmos(*this) + , m_use_clipping_planes(false) + , m_sidebar_field("") + , m_extra_frame_requested(false) + , m_config(nullptr) + , m_process(nullptr) + , m_model(nullptr) + , m_dirty(true) + , m_initialized(false) + , m_apply_zoom_to_volumes_filter(false) + , m_picking_enabled(false) + , m_moving_enabled(false) + , m_dynamic_background_enabled(false) + , m_multisample_allowed(false) + , m_moving(false) + , m_tab_down(false) + , m_cursor_type(Standard) + , m_reload_delayed(false) + , m_render_sla_auxiliaries(true) + , m_labels(*this) + , m_slope(m_volumes) +{ + if (m_canvas != nullptr) { + m_timer.SetOwner(m_canvas); + m_render_timer.SetOwner(m_canvas); +#if ENABLE_RETINA_GL + m_retina_helper.reset(new RetinaHelper(canvas)); +#endif // ENABLE_RETINA_GL + } + + load_arrange_settings(); + + m_selection.set_volumes(&m_volumes.volumes); +} + +GLCanvas3D::~GLCanvas3D() +{ + reset_volumes(); +} + +void GLCanvas3D::post_event(wxEvent &&event) +{ + event.SetEventObject(m_canvas); + wxPostEvent(m_canvas, event); +} + +bool GLCanvas3D::init() +{ + if (m_initialized) + return true; + + if (m_canvas == nullptr || m_context == nullptr) + return false; + + glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); +#if ENABLE_OPENGL_ES + glsafe(::glClearDepthf(1.0f)); +#else + glsafe(::glClearDepth(1.0f)); +#endif // ENABLE_OPENGL_ES + + glsafe(::glDepthFunc(GL_LESS)); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + // Set antialiasing / multisampling + glsafe(::glDisable(GL_LINE_SMOOTH)); + glsafe(::glDisable(GL_POLYGON_SMOOTH)); + + // ambient lighting + GLfloat ambient[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; + glsafe(::glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient)); + + glsafe(::glEnable(GL_LIGHT0)); + glsafe(::glEnable(GL_LIGHT1)); + + // light from camera + GLfloat specular_cam[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; + glsafe(::glLightfv(GL_LIGHT1, GL_SPECULAR, specular_cam)); + GLfloat diffuse_cam[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; + glsafe(::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_cam)); + + // light from above + GLfloat specular_top[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; + glsafe(::glLightfv(GL_LIGHT0, GL_SPECULAR, specular_top)); + GLfloat diffuse_top[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; + glsafe(::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_top)); + + // Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. + glsafe(::glShadeModel(GL_SMOOTH)); + + // A handy trick -- have surface material mirror the color. + glsafe(::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)); + glsafe(::glEnable(GL_COLOR_MATERIAL)); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + if (m_multisample_allowed) + glsafe(::glEnable(GL_MULTISAMPLE)); + + if (m_main_toolbar.is_enabled()) + m_layers_editing.init(); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + // on linux the gl context is not valid until the canvas is not shown on screen + // we defer the geometry finalization of volumes until the first call to render() + m_volumes.finalize_geometry(true); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + if (m_gizmos.is_enabled() && !m_gizmos.init()) + std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; + + if (!_init_toolbars()) + return false; + + if (m_selection.is_enabled() && !m_selection.init()) + return false; + + m_initialized = true; + + return true; +} + +void GLCanvas3D::set_as_dirty() +{ + m_dirty = true; +} + +unsigned int GLCanvas3D::get_volumes_count() const +{ + return (unsigned int)m_volumes.volumes.size(); +} + +void GLCanvas3D::reset_volumes() +{ + if (!m_initialized) + return; + + if (m_volumes.empty()) + return; + + _set_current(); + + m_selection.clear(); + m_volumes.clear(); + m_dirty = true; + + _set_warning_notification(EWarning::ObjectOutside, false); +} + +ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const +{ + ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside; + if (m_initialized) + m_volumes.check_outside_state(m_bed.build_volume(), &state); + return state; +} + +void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx) +{ + if (current_printer_technology() != ptSLA) + return; + + m_render_sla_auxiliaries = visible; + + for (GLVolume* vol : m_volumes.volumes) { + if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) + && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) + && vol->composite_id.volume_id < 0) + vol->is_active = visible; + } +} + +void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv) +{ +#if ENABLE_RAYCAST_PICKING + std::vector>* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Volume); +#endif // ENABLE_RAYCAST_PICKING + for (GLVolume* vol : m_volumes.volumes) { + if (vol->is_wipe_tower) + vol->is_active = (visible && mo == nullptr); + else { + if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) + && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) + && (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) { + vol->is_active = visible; + if (!vol->is_modifier) + vol->color.a(1.f); + + if (instance_idx == -1) { + vol->force_native_color = false; + vol->force_neutral_color = false; + } else { + const GLGizmosManager& gm = get_gizmos_manager(); + auto gizmo_type = gm.get_current_type(); + if ( (gizmo_type == GLGizmosManager::FdmSupports + || gizmo_type == GLGizmosManager::Seam + || gizmo_type == GLGizmosManager::Cut) + && !vol->is_modifier) { + vol->force_neutral_color = true; + if (gizmo_type == GLGizmosManager::Cut) + vol->color.a(0.95f); + } + else if (gizmo_type == GLGizmosManager::MmuSegmentation) + vol->is_active = false; + else + vol->force_native_color = true; + } + } + } +#if ENABLE_RAYCAST_PICKING + auto it = std::find_if(raycasters->begin(), raycasters->end(), [vol](std::shared_ptr item) { return item->get_raycaster() == vol->mesh_raycaster.get(); }); + if (it != raycasters->end()) + (*it)->set_active(vol->is_active); +#endif // ENABLE_RAYCAST_PICKING + } + + if (visible && !mo) + toggle_sla_auxiliaries_visibility(true, mo, instance_idx); + + if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1)) + _set_warning_notification(EWarning::SomethingNotShown, true); + + if (!mo && visible) + _set_warning_notification(EWarning::SomethingNotShown, false); +} + +void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx) +{ + ModelObject* model_object = m_model->objects[obj_idx]; + for (int inst_idx = 0; inst_idx < (int)model_object->instances.size(); ++inst_idx) { + ModelInstance* instance = model_object->instances[inst_idx]; + + for (GLVolume* volume : m_volumes.volumes) { + if (volume->object_idx() == (int)obj_idx && volume->instance_idx() == inst_idx) + volume->printable = instance->printable; + } + } +} + +void GLCanvas3D::update_instance_printable_state_for_objects(const std::vector& object_idxs) +{ + for (size_t obj_idx : object_idxs) + update_instance_printable_state_for_object(obj_idx); +} + +void GLCanvas3D::set_config(const DynamicPrintConfig* config) +{ + m_config = config; + m_layers_editing.set_config(config); +} + +void GLCanvas3D::set_process(BackgroundSlicingProcess *process) +{ + m_process = process; +} + +void GLCanvas3D::set_model(Model* model) +{ + m_model = model; + m_selection.set_model(m_model); +} + +void GLCanvas3D::bed_shape_changed() +{ + refresh_camera_scene_box(); + wxGetApp().plater()->get_camera().requires_zoom_to_bed = true; + m_dirty = true; +} + +void GLCanvas3D::refresh_camera_scene_box() +{ + wxGetApp().plater()->get_camera().set_scene_box(scene_bounding_box()); +} + +BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const +{ + BoundingBoxf3 bb; + for (const GLVolume* volume : m_volumes.volumes) { + if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes)) + bb.merge(volume->transformed_bounding_box()); + } + return bb; +} + +BoundingBoxf3 GLCanvas3D::scene_bounding_box() const +{ + BoundingBoxf3 bb = volumes_bounding_box(); + bb.merge(m_bed.extended_bounding_box()); + double h = m_bed.build_volume().max_print_height(); + //FIXME why -h? + bb.min.z() = std::min(bb.min.z(), -h); + bb.max.z() = std::max(bb.max.z(), h); + return bb; +} + +bool GLCanvas3D::is_layers_editing_enabled() const +{ + return m_layers_editing.is_enabled(); +} + +bool GLCanvas3D::is_layers_editing_allowed() const +{ + return m_layers_editing.is_allowed(); +} + +void GLCanvas3D::reset_layer_height_profile() +{ + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Reset")); + m_layers_editing.reset_layer_height_profile(*this); + m_layers_editing.state = LayersEditing::Completed; + m_dirty = true; +} + +void GLCanvas3D::adaptive_layer_height_profile(float quality_factor) +{ + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Adaptive")); + m_layers_editing.adaptive_layer_height_profile(*this, quality_factor); + m_layers_editing.state = LayersEditing::Completed; + m_dirty = true; +} + +void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params) +{ + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Smooth all")); + m_layers_editing.smooth_layer_height_profile(*this, smoothing_params); + m_layers_editing.state = LayersEditing::Completed; + m_dirty = true; +} + +bool GLCanvas3D::is_reload_delayed() const +{ + return m_reload_delayed; +} + +void GLCanvas3D::enable_layers_editing(bool enable) +{ + m_layers_editing.set_enabled(enable); + set_as_dirty(); +} + +void GLCanvas3D::enable_legend_texture(bool enable) +{ + m_gcode_viewer.enable_legend(enable); +} + +void GLCanvas3D::enable_picking(bool enable) +{ + m_picking_enabled = enable; + m_selection.set_mode(Selection::Instance); +} + +void GLCanvas3D::enable_moving(bool enable) +{ + m_moving_enabled = enable; +} + +void GLCanvas3D::enable_gizmos(bool enable) +{ + m_gizmos.set_enabled(enable); +} + +void GLCanvas3D::enable_selection(bool enable) +{ + m_selection.set_enabled(enable); +} + +void GLCanvas3D::enable_main_toolbar(bool enable) +{ + m_main_toolbar.set_enabled(enable); +} + +void GLCanvas3D::enable_undoredo_toolbar(bool enable) +{ + m_undoredo_toolbar.set_enabled(enable); +} + +void GLCanvas3D::enable_dynamic_background(bool enable) +{ + m_dynamic_background_enabled = enable; +} + +void GLCanvas3D::allow_multisample(bool allow) +{ + m_multisample_allowed = allow; +} + +void GLCanvas3D::zoom_to_bed() +{ + BoundingBoxf3 box = m_bed.build_volume().bounding_volume(); + box.min.z() = 0.0; + box.max.z() = 0.0; + _zoom_to_box(box); +} + +void GLCanvas3D::zoom_to_volumes() +{ + m_apply_zoom_to_volumes_filter = true; + _zoom_to_box(volumes_bounding_box()); + m_apply_zoom_to_volumes_filter = false; +} + +void GLCanvas3D::zoom_to_selection() +{ + if (!m_selection.is_empty()) + _zoom_to_box(m_selection.get_bounding_box()); +} + +void GLCanvas3D::zoom_to_gcode() +{ + _zoom_to_box(m_gcode_viewer.get_paths_bounding_box(), 1.05); +} + +void GLCanvas3D::select_view(const std::string& direction) +{ + wxGetApp().plater()->get_camera().select_view(direction); + if (m_canvas != nullptr) + m_canvas->Refresh(); +} + +void GLCanvas3D::update_volumes_colors_by_extruder() +{ + if (m_config != nullptr) + m_volumes.update_colors_by_extruder(m_config); +} + +void GLCanvas3D::render() +{ + if (m_in_render) { + // if called recursively, return + m_dirty = true; + return; + } + + m_in_render = true; + Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; }); + (void)in_render_guard; + + if (m_canvas == nullptr) + return; + + // ensures this canvas is current and initialized + if (!_is_shown_on_screen() || !_set_current() || !wxGetApp().init_opengl()) + return; + + if (!is_initialized() && !init()) + return; + + if (!m_main_toolbar.is_enabled()) + m_gcode_viewer.init(); + + if (! m_bed.build_volume().valid()) { + // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE + post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE)); + return; + } + +#if ENABLE_ENVIRONMENT_MAP + if (wxGetApp().is_editor()) + wxGetApp().plater()->init_environment_texture(); +#endif // ENABLE_ENVIRONMENT_MAP + +#if ENABLE_GLMODEL_STATISTICS + GLModel::reset_statistics_counters(); +#endif // ENABLE_GLMODEL_STATISTICS + + const Size& cnv_size = get_canvas_size(); + // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene + // to preview, this was called before canvas had its final size. It reported zero width + // and the viewport was set incorrectly, leading to tripping glAsserts further down + // the road (in apply_projection). That's why the minimum size is forced to 10. + Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_RAYCAST_PICKING + camera.set_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height())); + camera.apply_viewport(); +#else + camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height())); +#endif // ENABLE_RAYCAST_PICKING + + if (camera.requires_zoom_to_bed) { + zoom_to_bed(); + _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + camera.requires_zoom_to_bed = false; + } + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + camera.apply_view_matrix(); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + camera.apply_projection(_max_bounding_box(true, true)); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; + glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam)); + GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f }; + glsafe(::glLightfv(GL_LIGHT0, GL_POSITION, position_top)); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + wxGetApp().imgui()->new_frame(); + + if (m_picking_enabled) { + if (m_rectangle_selection.is_dragging() && !m_rectangle_selection.is_empty()) + // picking pass using rectangle selection + _rectangular_selection_picking_pass(); + else if (!m_volumes.empty()) + // regular picking pass + _picking_pass(); +#if ENABLE_RAYCAST_PICKING_DEBUG + else { + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); + imgui.text("Picking disabled"); + imgui.end(); + } +#endif // ENABLE_RAYCAST_PICKING_DEBUG + } + + const bool is_looking_downward = camera.is_looking_downward(); + + // draw scene + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + _render_background(); + + _render_objects(GLVolumeCollection::ERenderType::Opaque); + if (!m_main_toolbar.is_enabled()) + _render_gcode(); + _render_sla_slices(); + _render_selection(); + if (is_looking_downward) +#if ENABLE_LEGACY_OPENGL_REMOVAL + _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false, true); +#else + _render_bed(false, true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + _render_objects(GLVolumeCollection::ERenderType::Transparent); + + _render_sequential_clearance(); +#if ENABLE_RENDER_SELECTION_CENTER + _render_selection_center(); +#endif // ENABLE_RENDER_SELECTION_CENTER + if (!m_main_toolbar.is_enabled()) + _render_gcode_cog(); + + // we need to set the mouse's scene position here because the depth buffer + // could be invalidated by the following gizmo render methods + // this position is used later into on_mouse() to drag the objects + if (m_picking_enabled) + m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast()); + + // sidebar hints need to be rendered before the gizmos because the depth buffer + // could be invalidated by the following gizmo render methods + _render_selection_sidebar_hints(); + _render_current_gizmo(); + if (!is_looking_downward) +#if ENABLE_LEGACY_OPENGL_REMOVAL + _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), true, true); +#else + _render_bed(true, true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_RAYCAST_PICKING_DEBUG + if (m_picking_enabled && !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging()) + m_scene_raycaster.render_hit(camera); +#endif // ENABLE_RAYCAST_PICKING_DEBUG + +#if ENABLE_SHOW_CAMERA_TARGET + _render_camera_target(); +#endif // ENABLE_SHOW_CAMERA_TARGET + + if (m_picking_enabled && m_rectangle_selection.is_dragging()) + m_rectangle_selection.render(*this); + + // draw overlays + _render_overlays(); + + if (wxGetApp().plater()->is_render_statistic_dialog_visible()) { + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + imgui.text("FPS (SwapBuffers() calls per second):"); + ImGui::SameLine(); + imgui.text(std::to_string(m_render_stats.get_fps_and_reset_if_needed())); + ImGui::Separator(); + imgui.text("Compressed textures:"); + ImGui::SameLine(); + imgui.text(OpenGLManager::are_compressed_textures_supported() ? "supported" : "not supported"); + imgui.text("Max texture size:"); + ImGui::SameLine(); + imgui.text(std::to_string(OpenGLManager::get_gl_info().get_max_tex_size())); + imgui.end(); + } + +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + if (wxGetApp().is_editor() && wxGetApp().plater()->is_view3D_shown()) + wxGetApp().plater()->render_project_state_debug_window(); +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + +#if ENABLE_CAMERA_STATISTICS + camera.debug_render(); +#endif // ENABLE_CAMERA_STATISTICS +#if ENABLE_GLMODEL_STATISTICS + GLModel::render_statistics(); +#endif // ENABLE_GLMODEL_STATISTICS + + std::string tooltip; + + // Negative coordinate means out of the window, likely because the window was deactivated. + // In that case the tooltip should be hidden. + if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) { + if (tooltip.empty()) + tooltip = m_layers_editing.get_tooltip(*this); + + if (tooltip.empty()) + tooltip = m_gizmos.get_tooltip(); + + if (tooltip.empty()) + tooltip = m_main_toolbar.get_tooltip(); + + if (tooltip.empty()) + tooltip = m_undoredo_toolbar.get_tooltip(); + + if (tooltip.empty()) + tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip(); + + if (tooltip.empty()) + tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip(); + } + + set_tooltip(tooltip); + + if (m_tooltip_enabled) + m_tooltip.render(m_mouse.position, *this); + + wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); + + wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width()); + + wxGetApp().imgui()->render(); + + m_canvas->SwapBuffers(); + m_render_stats.increment_fps_counter(); +} + +void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type) +{ + render_thumbnail(thumbnail_data, w, h, thumbnail_params, m_volumes, camera_type); +} + +void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) +{ + switch (OpenGLManager::get_framebuffers_type()) + { + case OpenGLManager::EFramebufferType::Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } + case OpenGLManager::EFramebufferType::Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } + default: { _render_thumbnail_legacy(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; } + } +} + +void GLCanvas3D::select_all() +{ + m_selection.add_all(); + m_dirty = true; + wxGetApp().obj_manipul()->set_dirty(); + m_gizmos.reset_all_states(); + m_gizmos.update_data(); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); +} + +void GLCanvas3D::deselect_all() +{ + if (m_selection.is_empty()) + return; + + m_selection.remove_all(); + wxGetApp().obj_manipul()->set_dirty(); + m_gizmos.reset_all_states(); + m_gizmos.update_data(); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); +} + +void GLCanvas3D::delete_selected() +{ + m_selection.erase(); +} + +void GLCanvas3D::ensure_on_bed(unsigned int object_idx, bool allow_negative_z) +{ + if (allow_negative_z) + return; + + typedef std::map, double> InstancesToZMap; + InstancesToZMap instances_min_z; + + for (GLVolume* volume : m_volumes.volumes) { + if (volume->object_idx() == (int)object_idx && !volume->is_modifier) { + double min_z = volume->transformed_convex_hull_bounding_box().min.z(); + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it == instances_min_z.end()) + it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; + + it->second = std::min(it->second, min_z); + } + } + + for (GLVolume* volume : m_volumes.volumes) { + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it != instances_min_z.end()) + volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); + } +} + + +const std::vector& GLCanvas3D::get_gcode_layers_zs() const +{ + return m_gcode_viewer.get_layers_zs(); +} + +std::vector GLCanvas3D::get_volumes_print_zs(bool active_only) const +{ + return m_volumes.get_current_print_zs(active_only); +} + +void GLCanvas3D::set_gcode_options_visibility_from_flags(unsigned int flags) +{ + m_gcode_viewer.set_options_visibility_from_flags(flags); +} + +void GLCanvas3D::set_toolpath_role_visibility_flags(unsigned int flags) +{ + m_gcode_viewer.set_toolpath_role_visibility_flags(flags); +} + +void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) +{ + m_gcode_viewer.set_view_type(type); +} + +void GLCanvas3D::set_volumes_z_range(const std::array& range) +{ + m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6); +} + +void GLCanvas3D::set_toolpaths_z_range(const std::array& range) +{ + if (m_gcode_viewer.has_data()) + m_gcode_viewer.set_layers_z_range(range); +} + +std::vector GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs) +{ + if (instance_idxs.empty()) { + for (unsigned int i = 0; i < model_object.instances.size(); ++i) { + instance_idxs.emplace_back(i); + } + } +#if ENABLE_LEGACY_OPENGL_REMOVAL + return m_volumes.load_object(&model_object, obj_idx, instance_idxs); +#else + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) +{ + if (0 <= obj_idx && obj_idx < (int)model.objects.size()) { + const ModelObject* model_object = model.objects[obj_idx]; + if (model_object != nullptr) + return load_object(*model_object, obj_idx, std::vector()); + } + + return std::vector(); +} + +void GLCanvas3D::mirror_selection(Axis axis) +{ + m_selection.mirror(axis); + do_mirror(L("Mirror Object")); + wxGetApp().obj_manipul()->set_dirty(); +} + +// Reload the 3D scene of +// 1) Model / ModelObjects / ModelInstances / ModelVolumes +// 2) Print bed +// 3) SLA support meshes for their respective ModelObjects / ModelInstances +// 4) Wipe tower preview +// 5) Out of bed collision status & message overlay (texture) +void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_refresh) +{ + if (m_canvas == nullptr || m_config == nullptr || m_model == nullptr) + return; + + if (!m_initialized) + return; + + _set_current(); + + m_hover_volume_idxs.clear(); + + struct ModelVolumeState { + ModelVolumeState(const GLVolume* volume) : + model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} + ModelVolumeState(const ModelVolume* model_volume, const ObjectID& instance_id, const GLVolume::CompositeID& composite_id) : + model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} + ModelVolumeState(const ObjectID& volume_id, const ObjectID& instance_id) : + model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} + bool new_geometry() const { return this->volume_idx == size_t(-1); } + const ModelVolume* model_volume; + // ObjectID of ModelVolume + ObjectID of ModelInstance + // or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance + std::pair geometry_id; + GLVolume::CompositeID composite_id; + // Volume index in the new GLVolume vector. + size_t volume_idx; + }; + std::vector model_volume_state; + std::vector aux_volume_state; + + struct GLVolumeState { + GLVolumeState() : + volume_idx(size_t(-1)) {} + GLVolumeState(const GLVolume* volume, unsigned int volume_idx) : + composite_id(volume->composite_id), volume_idx(volume_idx) {} + GLVolumeState(const GLVolume::CompositeID &composite_id) : + composite_id(composite_id), volume_idx(size_t(-1)) {} + + GLVolume::CompositeID composite_id; + // Volume index in the old GLVolume vector. + size_t volume_idx; + }; + + // SLA steps to pull the preview meshes for. + typedef std::array SLASteps; + SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad }; + struct SLASupportState { + std::array::value> step; + }; + // State of the sla_steps for all SLAPrintObjects. + std::vector sla_support_state; + + std::vector instance_ids_selected; + std::vector map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1)); + std::vector deleted_volumes; + std::vector glvolumes_new; + glvolumes_new.reserve(m_volumes.volumes.size()); + auto model_volume_state_lower = [](const ModelVolumeState& m1, const ModelVolumeState& m2) { return m1.geometry_id < m2.geometry_id; }; + + m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh; + + PrinterTechnology printer_technology = current_printer_technology(); + int volume_idx_wipe_tower_old = -1; + + // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). + // First initialize model_volumes_new_sorted & model_instances_new_sorted. + for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++object_idx) { + const ModelObject* model_object = m_model->objects[object_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++instance_idx) { + const ModelInstance* model_instance = model_object->instances[instance_idx]; + for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++volume_idx) { + const ModelVolume* model_volume = model_object->volumes[volume_idx]; + model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); + } + } + } + if (printer_technology == ptSLA) { + const SLAPrint* sla_print = this->sla_print(); +#ifndef NDEBUG + // Verify that the SLAPrint object is synchronized with m_model. + check_model_ids_equal(*m_model, sla_print->model()); +#endif /* NDEBUG */ + sla_support_state.reserve(sla_print->objects().size()); + for (const SLAPrintObject* print_object : sla_print->objects()) { + SLASupportState state; + for (size_t istep = 0; istep < sla_steps.size(); ++istep) { + state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); + if (state.step[istep].state == PrintStateBase::DONE) { + if (!print_object->has_mesh(sla_steps[istep])) + // Consider the DONE step without a valid mesh as invalid for the purpose + // of mesh visualization. + state.step[istep].state = PrintStateBase::INVALID; + else if (sla_steps[istep] != slaposDrillHoles) + for (const ModelInstance* model_instance : print_object->model_object()->instances) + // Only the instances, which are currently printable, will have the SLA support structures kept. + // The instances outside the print bed will have the GLVolumes of their support structures released. + if (model_instance->is_printable()) + aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); + } + } + sla_support_state.emplace_back(state); + } + } + std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); + std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower); + // Release all ModelVolume based GLVolumes not found in the current Model. Find the GLVolume of a hollowed mesh. + for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++volume_id) { + GLVolume* volume = m_volumes.volumes[volume_id]; + ModelVolumeState key(volume); + ModelVolumeState* mvs = nullptr; + if (volume->volume_idx() < 0) { + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) + // This can be an SLA support structure that should not be rendered (in case someone used undo + // to revert to before it was generated). We only reuse the volume if that's not the case. + if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints) + mvs = &(*it); + } + else { + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) + mvs = &(*it); + } + // Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID. + // The wipe tower has its own wipe_tower_instance_id(). + if (m_selection.contains_volume(volume_id)) + instance_ids_selected.emplace_back(volume->geometry_id.second); + if (mvs == nullptr || force_full_scene_refresh) { + // This GLVolume will be released. + if (volume->is_wipe_tower) { + // There is only one wipe tower. + assert(volume_idx_wipe_tower_old == -1); +#if ENABLE_OPENGL_ES + m_wipe_tower_mesh.clear(); +#endif // ENABLE_OPENGL_ES + volume_idx_wipe_tower_old = (int)volume_id; + } + if (!m_reload_delayed) { + deleted_volumes.emplace_back(volume, volume_id); + delete volume; + } + } + else { + // This GLVolume will be reused. + volume->set_sla_shift_z(0.0); + map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); + mvs->volume_idx = glvolumes_new.size(); + glvolumes_new.emplace_back(volume); + // Update color of the volume based on the current extruder. + if (mvs->model_volume != nullptr) { + int extruder_id = mvs->model_volume->extruder_id(); + if (extruder_id != -1) + volume->extruder_id = extruder_id; + + volume->is_modifier = !mvs->model_volume->is_model_part(); + volume->set_color(color_from_model_volume(*mvs->model_volume)); + // force update of render_color alpha channel + volume->set_render_color(volume->color.is_transparent()); + + // updates volumes transformations + volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); + volume->set_volume_transformation(mvs->model_volume->get_transformation()); + + // updates volumes convex hull + if (mvs->model_volume->is_model_part() && ! volume->convex_hull()) + // Model volume was likely changed from modifier or support blocker / enforcer to a model part. + // Only model parts require convex hulls. + volume->set_convex_hull(mvs->model_volume->get_convex_hull_shared_ptr()); + } + } + } + sort_remove_duplicates(instance_ids_selected); + auto deleted_volumes_lower = [](const GLVolumeState &v1, const GLVolumeState &v2) { return v1.composite_id < v2.composite_id; }; + std::sort(deleted_volumes.begin(), deleted_volumes.end(), deleted_volumes_lower); + + if (m_reload_delayed) + return; + + bool update_object_list = false; + if (m_volumes.volumes != glvolumes_new) + update_object_list = true; + m_volumes.volumes = std::move(glvolumes_new); + for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { + const ModelObject &model_object = *m_model->objects[obj_idx]; + for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { + const ModelVolume &model_volume = *model_object.volumes[volume_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { + const ModelInstance &model_instance = *model_object.instances[instance_idx]; + ModelVolumeState key(model_volume.id(), model_instance.id()); + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); + if (it->new_geometry()) { + // New volume. + auto it_old_volume = std::lower_bound(deleted_volumes.begin(), deleted_volumes.end(), GLVolumeState(it->composite_id), deleted_volumes_lower); + if (it_old_volume != deleted_volumes.end() && it_old_volume->composite_id == it->composite_id) + // If a volume changed its ObjectID, but it reuses a GLVolume's CompositeID, maintain its selection. + map_glvolume_old_to_new[it_old_volume->volume_idx] = m_volumes.volumes.size(); + // Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh + // later in this function. + it->volume_idx = m_volumes.volumes.size(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx); +#else + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + m_volumes.volumes.back()->geometry_id = key.geometry_id; + update_object_list = true; + } else { + // Recycling an old GLVolume. + GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; + assert(existing_volume.geometry_id == key.geometry_id); + // Update the Object/Volume/Instance indices into the current Model. + if (existing_volume.composite_id != it->composite_id) { + existing_volume.composite_id = it->composite_id; + update_object_list = true; + } + } + } + } + } + if (printer_technology == ptSLA) { + size_t idx = 0; + const SLAPrint *sla_print = this->sla_print(); + std::vector shift_zs(m_model->objects.size(), 0); + double relative_correction_z = sla_print->relative_correction().z(); + if (relative_correction_z <= EPSILON) + relative_correction_z = 1.; + for (const SLAPrintObject *print_object : sla_print->objects()) { + SLASupportState &state = sla_support_state[idx ++]; + const ModelObject *model_object = print_object->model_object(); + // Find an index of the ModelObject + int object_idx; + // There may be new SLA volumes added to the scene for this print_object. + // Find the object index of this print_object in the Model::objects list. + auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); + assert(it != sla_print->model().objects.end()); + object_idx = it - sla_print->model().objects.begin(); + // Cache the Z offset to be applied to all volumes with this object_idx. + shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; + // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. + // pairs of + std::vector> instances[std::tuple_size::value]; + for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { + const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; + // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. + auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), + [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); + assert(it != model_object->instances.end()); + int instance_idx = it - model_object->instances.begin(); + for (size_t istep = 0; istep < sla_steps.size(); ++ istep) + if (sla_steps[istep] == slaposDrillHoles) { + // Hollowing is a special case, where the mesh from the backend is being loaded into the 1st volume of an instance, + // not into its own GLVolume. + // There shall always be such a GLVolume allocated. + ModelVolumeState key(model_object->volumes.front()->id(), instance.instance_id); + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); + assert(!it->new_geometry()); + GLVolume &volume = *m_volumes.volumes[it->volume_idx]; + if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) { + // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen. +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.model.reset(); +#else + volume.indexed_vertex_array.release_geometry(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (state.step[istep].state == PrintStateBase::DONE) { + TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); + assert(! mesh.empty()); + + // sla_trafo does not contain volume trafo. To get a mesh to create + // a new volume from, we have to apply vol trafo inverse separately. + const ModelObject& mo = *m_model->objects[volume.object_idx()]; + Transform3d trafo = sla_print->sla_trafo(mo) + * mo.volumes.front()->get_transformation().get_matrix(); + mesh.transform(trafo.inverse()); +#if ENABLE_SMOOTH_NORMALS +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.model.init_from(mesh, true); +#else + volume.indexed_vertex_array.load_mesh(mesh, true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#else +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.model.init_from(mesh); +#if ENABLE_RAYCAST_PICKING + volume.mesh_raycaster = std::make_unique(std::make_shared(mesh)); +#endif // ENABLE_RAYCAST_PICKING +#else + volume.indexed_vertex_array.load_mesh(mesh); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_SMOOTH_NORMALS + } + else { + // Reload the original volume. +#if ENABLE_SMOOTH_NORMALS +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); +#else + volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#else +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_RAYCAST_PICKING + const TriangleMesh& new_mesh = m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(); + volume.model.init_from(new_mesh); + volume.mesh_raycaster = std::make_unique(std::make_shared(new_mesh)); +#else + volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); +#endif // ENABLE_RAYCAST_PICKING +#else + volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_SMOOTH_NORMALS + } +#if !ENABLE_LEGACY_OPENGL_REMOVAL + volume.finalize_geometry(true); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + } + //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable + // to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables + // of various concenrs (model vs. 3D print path). + volume.offsets = { state.step[istep].timestamp }; + } + else if (state.step[istep].state == PrintStateBase::DONE) { + // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created. + ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); + if (it->new_geometry()) { + // This can be an SLA support structure that should not be rendered (in case someone used undo + // to revert to before it was generated). If that's the case, we should not generate anything. + if (model_object->sla_points_status != sla::PointsStatus::NoPoints) + instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); + else + shift_zs[object_idx] = 0.; + } + else { + // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. + m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); + m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); + } + } + } + + for (size_t istep = 0; istep < sla_steps.size(); ++istep) + if (!instances[istep].empty()) +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); +#else + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed + for (GLVolume* volume : m_volumes.volumes) + if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) + volume->set_sla_shift_z(shift_zs[volume->object_idx()]); + } + + if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) { + // Should the wipe tower be visualized ? + unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); + + bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; + bool co = dynamic_cast(m_config->option("complete_objects"))->value; + + if (extruders_count > 1 && wt && !co) { + // Height of a print (Show at least a slab) + double height = std::max(m_model->bounding_box().max(2), 10.0); + + float x = dynamic_cast(m_config->option("wipe_tower_x"))->value; + float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; + float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; + float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; + + const Print *print = m_process->fff_print(); + + float depth = print->wipe_tower_data(extruders_count).depth; + float brim_width = print->wipe_tower_data(extruders_count).brim_width; + +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_OPENGL_ES + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_width, &m_wipe_tower_mesh); +#else + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_width); +#endif // ENABLE_OPENGL_ES +#else + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_width, m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (volume_idx_wipe_tower_old != -1) + map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; + } + } + + update_volumes_colors_by_extruder(); + // Update selection indices based on the old/new GLVolumeCollection. + if (m_selection.get_mode() == Selection::Instance) + m_selection.instances_changed(instance_ids_selected); + else + m_selection.volumes_changed(map_glvolume_old_to_new); + + m_gizmos.update_data(); + m_gizmos.refresh_on_off_state(); + + // Update the toolbar + if (update_object_list) + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + + // checks for geometry outside the print volume to render it accordingly + if (!m_volumes.empty()) { + ModelInstanceEPrintVolumeState state; + const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state); + const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); + const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); + + if (printer_technology != ptSLA) { + _set_warning_notification(EWarning::ObjectClashed, partlyOut); + _set_warning_notification(EWarning::ObjectOutside, fullyOut); + _set_warning_notification(EWarning::SlaSupportsOutside, false); + } + else { + const auto [res, volume] = _is_any_volume_outside(); + const bool is_support = volume != nullptr && volume->is_sla_support(); + if (is_support) { + _set_warning_notification(EWarning::ObjectClashed, false); + _set_warning_notification(EWarning::ObjectOutside, false); + _set_warning_notification(EWarning::SlaSupportsOutside, partlyOut || fullyOut); + } + else { + _set_warning_notification(EWarning::ObjectClashed, partlyOut); + _set_warning_notification(EWarning::ObjectOutside, fullyOut); + _set_warning_notification(EWarning::SlaSupportsOutside, false); + } + } + + post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, + contained_min_one && !m_model->objects.empty() && !partlyOut)); + } + else { + _set_warning_notification(EWarning::ObjectOutside, false); + _set_warning_notification(EWarning::ObjectClashed, false); + _set_warning_notification(EWarning::SlaSupportsOutside, false); + post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); + } + + refresh_camera_scene_box(); + + if (m_selection.is_empty()) { + // If no object is selected, deactivate the active gizmo, if any + // Otherwise it may be shown after cleaning the scene (if it was active while the objects were deleted) + m_gizmos.reset_all_states(); + + // If no object is selected, reset the objects manipulator on the sidebar + // to force a reset of its cache + auto manip = wxGetApp().obj_manipul(); + if (manip != nullptr) + manip->set_dirty(); + } + +#if ENABLE_RAYCAST_PICKING + // refresh volume raycasters for picking + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume); + for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { + const GLVolume* v = m_volumes.volumes[i]; + assert(v->mesh_raycaster != nullptr); + std::shared_ptr raycaster = add_raycaster_for_picking(SceneRaycaster::EType::Volume, i, *v->mesh_raycaster, v->world_matrix()); + raycaster->set_active(v->is_active); + } + + // refresh gizmo elements raycasters for picking + GLGizmoBase* curr_gizmo = m_gizmos.get_current(); + if (curr_gizmo != nullptr) + curr_gizmo->unregister_raycasters_for_picking(); + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo); + if (curr_gizmo != nullptr && !m_selection.is_empty()) + curr_gizmo->register_raycasters_for_picking(); +#endif // ENABLE_RAYCAST_PICKING + + // and force this canvas to be redrawn. + m_dirty = true; +} + +#if !ENABLE_LEGACY_OPENGL_REMOVAL +static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old, bool gl_initialized, size_t prealloc_size = VERTEX_BUFFER_RESERVE_SIZE) +{ + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol_old.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol_old.indexed_vertex_array = vol_new.indexed_vertex_array; + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + // Reserving number of vertices (3x position + 3x color) + vol_new.indexed_vertex_array.reserve(prealloc_size / 6); + // Finalize the old geometry, possibly move data to the graphics card. + vol_old.finalize_geometry(gl_initialized); +} +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_gcode_viewer.load(gcode_result, *this->fff_print()); +#else + m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + if (wxGetApp().is_editor()) { + m_gcode_viewer.update_shells_color_by_extruder(m_config); + _set_warning_notification_if_needed(EWarning::ToolpathOutside); + } + + m_gcode_viewer.refresh(gcode_result, str_tool_colors); + set_as_dirty(); + request_extra_frame(); +} + +void GLCanvas3D::refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) +{ + m_gcode_viewer.refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); + set_as_dirty(); + request_extra_frame(); +} + +void GLCanvas3D::load_sla_preview() +{ + const SLAPrint* print = sla_print(); + if (m_canvas != nullptr && print != nullptr) { + _set_current(); + // Release OpenGL data before generating new data. + reset_volumes(); + _load_sla_shells(); + _update_sla_shells_outside_state(); + _set_warning_notification_if_needed(EWarning::ObjectClashed); + _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); + } +} + +void GLCanvas3D::load_preview(const std::vector& str_tool_colors, const std::vector& color_print_values) +{ + const Print *print = this->fff_print(); + if (print == nullptr) + return; + + _set_current(); + + // Release OpenGL data before generating new data. + this->reset_volumes(); + + const BuildVolume &build_volume = m_bed.build_volume(); + _load_print_toolpaths(build_volume); + _load_wipe_tower_toolpaths(build_volume, str_tool_colors); + for (const PrintObject* object : print->objects()) + _load_print_object_toolpaths(*object, build_volume, str_tool_colors, color_print_values); + + _set_warning_notification_if_needed(EWarning::ToolpathOutside); +} + +void GLCanvas3D::bind_event_handlers() +{ + if (m_canvas != nullptr) { + m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this); + m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); + m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this); + m_canvas->Bind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this); + m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); + m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); + m_canvas->Bind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this); + m_toolbar_highlighter.set_timer_owner(m_canvas, 0); + m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_toolbar_highlighter.blink(); }); + m_gizmo_highlighter.set_timer_owner(m_canvas, 0); + m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_gizmo_highlighter.blink(); }); + m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); + m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this); + + m_event_handlers_bound = true; + } +} + +void GLCanvas3D::unbind_event_handlers() +{ + if (m_canvas != nullptr && m_event_handlers_bound) { + m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this); + m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); + m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this); + m_canvas->Unbind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this); + m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); + m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); + m_canvas->Unbind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this); + m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); + m_canvas->Unbind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this); + + m_event_handlers_bound = false; + } +} + +void GLCanvas3D::on_size(wxSizeEvent& evt) +{ + m_dirty = true; +} + +void GLCanvas3D::on_idle(wxIdleEvent& evt) +{ + if (!m_initialized) + return; + + m_dirty |= m_main_toolbar.update_items_state(); + m_dirty |= m_undoredo_toolbar.update_items_state(); + m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); + m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); + bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); + m_dirty |= mouse3d_controller_applied; + m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); + auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current(); + if (gizmo != nullptr) m_dirty |= gizmo->update_items_state(); + // ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism + bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame(); + m_dirty |= imgui_requires_extra_frame; + + if (!m_dirty) + return; + + // this needs to be done here. + // during the render launched by the refresh the value may be set again + wxGetApp().imgui()->reset_requires_extra_frame(); + + _refresh_if_shown_on_screen(); + + if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) { + m_extra_frame_requested = false; + evt.RequestMore(); + } + else + m_dirty = false; +} + +void GLCanvas3D::on_char(wxKeyEvent& evt) +{ + if (!m_initialized) + return; + + // see include/wx/defs.h enum wxKeyCode + int keyCode = evt.GetKeyCode(); + int ctrlMask = wxMOD_CONTROL; + int shiftMask = wxMOD_SHIFT; + + auto imgui = wxGetApp().imgui(); + if (imgui->update_key_data(evt)) { + render(); + return; + } + + if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu())) + return; + + if (m_gizmos.on_char(evt)) + return; + + if ((evt.GetModifiers() & ctrlMask) != 0) { + // CTRL is pressed + switch (keyCode) { +#ifdef __APPLE__ + case 'a': + case 'A': +#else /* __APPLE__ */ + case WXK_CONTROL_A: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); + break; +#ifdef __APPLE__ + case 'c': + case 'C': +#else /* __APPLE__ */ + case WXK_CONTROL_C: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLTOOLBAR_COPY)); + break; +#ifdef __APPLE__ + case 'm': + case 'M': +#else /* __APPLE__ */ + case WXK_CONTROL_M: +#endif /* __APPLE__ */ + { +#ifdef _WIN32 + if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "1") { +#endif //_WIN32 +#ifdef __APPLE__ + // On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog" + if ((evt.GetModifiers() & shiftMask) != 0) { +#endif // __APPLE__ + Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller(); + controller.show_settings_dialog(!controller.is_settings_dialog_shown()); + m_dirty = true; +#ifdef __APPLE__ + } + else + // and Cmd+M to minimize application + wxGetApp().mainframe->Iconize(); +#endif // __APPLE__ +#ifdef _WIN32 + } +#endif //_WIN32 + break; + } +#ifdef __APPLE__ + case 'v': + case 'V': +#else /* __APPLE__ */ + case WXK_CONTROL_V: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE)); + break; + + +#ifdef __APPLE__ + case 'f': + case 'F': +#else /* __APPLE__ */ + case WXK_CONTROL_F: +#endif /* __APPLE__ */ + _activate_search_toolbar_item(); + break; + + +#ifdef __APPLE__ + case 'y': + case 'Y': +#else /* __APPLE__ */ + case WXK_CONTROL_Y: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLCANVAS_REDO)); + break; +#ifdef __APPLE__ + case 'z': + case 'Z': +#else /* __APPLE__ */ + case WXK_CONTROL_Z: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); + break; + + case WXK_BACK: + case WXK_DELETE: + post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; + default: evt.Skip(); + } + } else { + switch (keyCode) + { + case WXK_BACK: + case WXK_DELETE: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; } + case WXK_ESCAPE: { deselect_all(); break; } + case WXK_F5: { + if ((wxGetApp().is_editor() && !wxGetApp().plater()->model().objects.empty()) || + (wxGetApp().is_gcode_viewer() && !wxGetApp().plater()->get_last_loaded_gcode().empty())) + post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK)); + break; + } + case '0': { select_view("iso"); break; } + case '1': { select_view("top"); break; } + case '2': { select_view("bottom"); break; } + case '3': { select_view("front"); break; } + case '4': { select_view("rear"); break; } + case '5': { select_view("left"); break; } + case '6': { select_view("right"); break; } + case '+': { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); + else + post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); + break; + } + case '-': { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); + else + post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); + break; + } + case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } + case 'A': + case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } + case 'B': + case 'b': { zoom_to_bed(); break; } + case 'C': + case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; } + case 'E': + case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; } + case 'G': + case 'g': { + if ((evt.GetModifiers() & shiftMask) != 0) { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt)); + } + break; + } + case 'I': + case 'i': { _update_camera_zoom(1.0); break; } + case 'K': + case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; } + case 'L': + case 'l': { + if (!m_main_toolbar.is_enabled()) + show_legend(!is_legend_shown()); + break; + } + case 'O': + case 'o': { _update_camera_zoom(-1.0); break; } + case 'Z': + case 'z': { + if (!m_selection.is_empty()) + zoom_to_selection(); + else { + if (!m_volumes.empty()) + zoom_to_volumes(); + else + _zoom_to_box(m_gcode_viewer.get_paths_bounding_box()); + } + break; + } + default: { evt.Skip(); break; } + } + } +} + +class TranslationProcessor +{ + using UpAction = std::function; + using DownAction = std::function; + + UpAction m_up_action{ nullptr }; + DownAction m_down_action{ nullptr }; + + bool m_running{ false }; + Vec3d m_direction{ Vec3d::UnitX() }; + +public: + TranslationProcessor(UpAction up_action, DownAction down_action) + : m_up_action(up_action), m_down_action(down_action) + { + } + + void process(wxKeyEvent& evt) + { + const int keyCode = evt.GetKeyCode(); + wxEventType type = evt.GetEventType(); + if (type == wxEVT_KEY_UP) { + switch (keyCode) + { + case WXK_NUMPAD_LEFT: case WXK_LEFT: + case WXK_NUMPAD_RIGHT: case WXK_RIGHT: + case WXK_NUMPAD_UP: case WXK_UP: + case WXK_NUMPAD_DOWN: case WXK_DOWN: + { + m_running = false; + m_up_action(); + break; + } + default: { break; } + } + } + else if (type == wxEVT_KEY_DOWN) { + bool apply = false; + + switch (keyCode) + { + case WXK_SHIFT: + { + if (m_running) + apply = true; + + break; + } + case WXK_NUMPAD_LEFT: + case WXK_LEFT: + { + m_direction = -Vec3d::UnitX(); + apply = true; + break; + } + case WXK_NUMPAD_RIGHT: + case WXK_RIGHT: + { + m_direction = Vec3d::UnitX(); + apply = true; + break; + } + case WXK_NUMPAD_UP: + case WXK_UP: + { + m_direction = Vec3d::UnitY(); + apply = true; + break; + } + case WXK_NUMPAD_DOWN: + case WXK_DOWN: + { + m_direction = -Vec3d::UnitY(); + apply = true; + break; + } + default: { break; } + } + + if (apply) { + m_running = true; + m_down_action(m_direction, evt.ShiftDown(), evt.CmdDown()); + } + } + } +}; + +void GLCanvas3D::on_key(wxKeyEvent& evt) +{ + static TranslationProcessor translationProcessor( + [this]() { + do_move(L("Gizmo-Move")); + m_gizmos.update_data(); + + wxGetApp().obj_manipul()->set_dirty(); + // Let the plater know that the dragging finished, so a delayed refresh + // of the scene with the background processing data should be performed. + post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + // updates camera target constraints + refresh_camera_scene_box(); + m_dirty = true; + }, + [this](const Vec3d& direction, bool slow, bool camera_space) { + m_selection.setup_cache(); + double multiplier = slow ? 1.0 : 10.0; + + Vec3d displacement; + if (camera_space) { + Eigen::Matrix inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3); + displacement = multiplier * (inv_view_3x3 * direction); + displacement.z() = 0.0; + } + else + displacement = multiplier * direction; + +#if ENABLE_WORLD_COORDINATE + TransformationType trafo_type; + trafo_type.set_relative(); + m_selection.translate(displacement, trafo_type); +#else + m_selection.translate(displacement); +#endif // ENABLE_WORLD_COORDINATE + m_dirty = true; + } + ); + + const int keyCode = evt.GetKeyCode(); + + auto imgui = wxGetApp().imgui(); + if (imgui->update_key_data(evt)) { + render(); + } + else { + if (!m_gizmos.on_key(evt)) { + if (evt.GetEventType() == wxEVT_KEY_UP) { + if (evt.ShiftDown() && evt.ControlDown() && keyCode == WXK_SPACE) { + wxGetApp().plater()->toggle_render_statistic_dialog(); + m_dirty = true; + } + if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { + // Enable switching between 3D and Preview with Tab + // m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux + post_event(SimpleEvent(EVT_GLCANVAS_TAB)); + } + else if (keyCode == WXK_TAB && evt.ShiftDown() && ! wxGetApp().is_gcode_viewer()) { + // Collapse side-panel with Shift+Tab + post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR)); + } + else if (keyCode == WXK_SHIFT) { + translationProcessor.process(evt); + + if (m_picking_enabled && m_rectangle_selection.is_dragging()) { + _update_selection_from_hover(); + m_rectangle_selection.stop_dragging(); + m_mouse.ignore_left_up = true; + } + m_shift_kar_filter.reset_count(); + m_dirty = true; +// set_cursor(Standard); + } + else if (keyCode == WXK_ALT) { + if (m_picking_enabled && m_rectangle_selection.is_dragging()) { + _update_selection_from_hover(); + m_rectangle_selection.stop_dragging(); + m_mouse.ignore_left_up = true; + m_dirty = true; + } +// set_cursor(Standard); + } + else if (keyCode == WXK_CONTROL) { +#if ENABLE_RAYCAST_PICKING + if (m_mouse.dragging && !m_moving) { +#else + if (m_mouse.dragging) { +#endif // ENABLE_RAYCAST_PICKING + // if the user releases CTRL while rotating the 3D scene + // prevent from moving the selected volume + m_mouse.drag.move_volume_idx = -1; + m_mouse.set_start_position_3D_as_invalid(); + } + m_ctrl_kar_filter.reset_count(); + m_dirty = true; + } + else if (m_gizmos.is_enabled() && !m_selection.is_empty()) { + translationProcessor.process(evt); + + switch (keyCode) + { + case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: + case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: + { + do_rotate(L("Gizmo-Rotate")); + m_gizmos.update_data(); + + wxGetApp().obj_manipul()->set_dirty(); + // Let the plater know that the dragging finished, so a delayed refresh + // of the scene with the background processing data should be performed. + post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + // updates camera target constraints + refresh_camera_scene_box(); + m_dirty = true; + + break; + } + default: { break; } + } + } + } + else if (evt.GetEventType() == wxEVT_KEY_DOWN) { + m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers(); + if (keyCode == WXK_SHIFT) { + translationProcessor.process(evt); + + if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) { + m_mouse.ignore_left_up = false; +// set_cursor(Cross); + } + if (m_shift_kar_filter.is_first()) + m_dirty = true; + + m_shift_kar_filter.increase_count(); + } + else if (keyCode == WXK_ALT) { + if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) { + m_mouse.ignore_left_up = false; +// set_cursor(Cross); + } + } + else if (keyCode == WXK_CONTROL) { + if (m_ctrl_kar_filter.is_first()) + m_dirty = true; + + m_ctrl_kar_filter.increase_count(); + } + else if (m_gizmos.is_enabled() && !m_selection.is_empty()) { + auto do_rotate = [this](double angle_z_rad) { + m_selection.setup_cache(); + m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint)); + m_dirty = true; +// wxGetApp().obj_manipul()->set_dirty(); + }; + + translationProcessor.process(evt); + + switch (keyCode) + { + case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: { do_rotate(0.25 * M_PI); break; } + case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: { do_rotate(-0.25 * M_PI); break; } + default: { break; } + } + } + else if (!m_gizmos.is_enabled()) { + // DoubleSlider navigation in Preview + if (keyCode == WXK_LEFT || + keyCode == WXK_RIGHT || + keyCode == WXK_UP || + keyCode == WXK_DOWN) { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_SLIDERS, evt)); + } + } + } + } + } + + if (keyCode != WXK_TAB + && keyCode != WXK_LEFT + && keyCode != WXK_UP + && keyCode != WXK_RIGHT + && keyCode != WXK_DOWN) { + evt.Skip(); // Needed to have EVT_CHAR generated as well + } +} + +void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) +{ +#ifdef WIN32 + // Try to filter out spurious mouse wheel events comming from 3D mouse. + if (wxGetApp().plater()->get_mouse3d_controller().process_mouse_wheel()) + return; +#endif + + if (!m_initialized) + return; + + // Ignore the wheel events if the middle button is pressed. + if (evt.MiddleIsDown()) + return; + +#if ENABLE_RETINA_GL + const float scale = m_retina_helper->get_scale_factor(); + evt.SetX(evt.GetX() * scale); + evt.SetY(evt.GetY() * scale); +#endif + + if (wxGetApp().imgui()->update_mouse_data(evt)) { + m_dirty = true; + return; + } + +#ifdef __WXMSW__ + // For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad, + // if the event is not allowed to be passed further. + // https://github.com/prusa3d/PrusaSlicer/issues/2750 + // evt.Skip() used to trigger the needed screen refresh, but it does no more. wxWakeUpIdle() seem to work now. + wxWakeUpIdle(); +#endif /* __WXMSW__ */ + + // Performs layers editing updates, if enabled + if (is_layers_editing_enabled()) { + int object_idx_selected = m_selection.get_object_idx(); + if (object_idx_selected != -1) { + // A volume is selected. Test, whether hovering over a layer thickness bar. + if (m_layers_editing.bar_rect_contains(*this, (float)evt.GetX(), (float)evt.GetY())) { + // Adjust the width of the selection. + m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f); + if (m_canvas != nullptr) + m_canvas->Refresh(); + + return; + } + } + } + + // If the Search window or Undo/Redo list is opened, + // update them according to the event + if (m_main_toolbar.is_item_pressed("search") || + m_undoredo_toolbar.is_item_pressed("undo") || + m_undoredo_toolbar.is_item_pressed("redo")) { + m_mouse_wheel = int((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); + return; + } + + // Inform gizmos about the event so they have the opportunity to react. + if (m_gizmos.on_mouse_wheel(evt)) + return; + + // Calculate the zoom delta and apply it to the current zoom factor + double direction_factor = (wxGetApp().app_config->get("reverse_mouse_wheel_zoom") == "1") ? -1.0 : 1.0; + _update_camera_zoom(direction_factor * (double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); +} + +void GLCanvas3D::on_timer(wxTimerEvent& evt) +{ + if (m_layers_editing.state == LayersEditing::Editing) + _perform_layer_editing_action(); +} + +void GLCanvas3D::on_render_timer(wxTimerEvent& evt) +{ + // no need to wake up idle + // right after this event, idle event is fired + // m_dirty = true; + // wxWakeUpIdle(); +} + + +void GLCanvas3D::schedule_extra_frame(int miliseconds) +{ + // Schedule idle event right now + if (miliseconds == 0) + { + // We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait + if (m_in_render) + miliseconds = 33; + else { + m_dirty = true; + wxWakeUpIdle(); + return; + } + } + int remaining_time = m_render_timer.GetInterval(); + // Timer is not running + if (!m_render_timer.IsRunning()) { + m_render_timer.StartOnce(miliseconds); + // Timer is running - restart only if new period is shorter than remaning period + } else { + if (miliseconds + 20 < remaining_time) { + m_render_timer.Stop(); + m_render_timer.StartOnce(miliseconds); + } + } +} + +#ifndef NDEBUG +// #define SLIC3R_DEBUG_MOUSE_EVENTS +#endif + +#ifdef SLIC3R_DEBUG_MOUSE_EVENTS +std::string format_mouse_event_debug_message(const wxMouseEvent &evt) +{ + static int idx = 0; + char buf[2048]; + std::string out; + sprintf(buf, "Mouse Event %d - ", idx ++); + out = buf; + + if (evt.Entering()) + out += "Entering "; + if (evt.Leaving()) + out += "Leaving "; + if (evt.Dragging()) + out += "Dragging "; + if (evt.Moving()) + out += "Moving "; + if (evt.Magnify()) + out += "Magnify "; + if (evt.LeftDown()) + out += "LeftDown "; + if (evt.LeftUp()) + out += "LeftUp "; + if (evt.LeftDClick()) + out += "LeftDClick "; + if (evt.MiddleDown()) + out += "MiddleDown "; + if (evt.MiddleUp()) + out += "MiddleUp "; + if (evt.MiddleDClick()) + out += "MiddleDClick "; + if (evt.RightDown()) + out += "RightDown "; + if (evt.RightUp()) + out += "RightUp "; + if (evt.RightDClick()) + out += "RightDClick "; + + sprintf(buf, "(%d, %d)", evt.GetX(), evt.GetY()); + out += buf; + return out; +} +#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ + +void GLCanvas3D::on_mouse(wxMouseEvent& evt) +{ + if (!m_initialized || !_set_current()) + return; + +#if ENABLE_RETINA_GL + const float scale = m_retina_helper->get_scale_factor(); + evt.SetX(evt.GetX() * scale); + evt.SetY(evt.GetY() * scale); +#endif + + Point pos(evt.GetX(), evt.GetY()); + + ImGuiWrapper* imgui = wxGetApp().imgui(); + if (m_tooltip.is_in_imgui() && evt.LeftUp()) + // ignore left up events coming from imgui windows and not processed by them + m_mouse.ignore_left_up = true; + m_tooltip.set_in_imgui(false); + if (imgui->update_mouse_data(evt)) { + m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast(); + m_tooltip.set_in_imgui(true); + render(); +#ifdef SLIC3R_DEBUG_MOUSE_EVENTS + printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); +#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ + m_dirty = true; + // do not return if dragging or tooltip not empty to allow for tooltip update + // also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection + if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving())) + return; + } + +#ifdef __WXMSW__ + bool on_enter_workaround = false; + if (! evt.Entering() && ! evt.Leaving() && m_mouse.position.x() == -1.0) { + // Workaround for SPE-832: There seems to be a mouse event sent to the window before evt.Entering() + m_mouse.position = pos.cast(); + render(); +#ifdef SLIC3R_DEBUG_MOUSE_EVENTS + printf((format_mouse_event_debug_message(evt) + " - OnEnter workaround\n").c_str()); +#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ + on_enter_workaround = true; + } else +#endif /* __WXMSW__ */ + { +#ifdef SLIC3R_DEBUG_MOUSE_EVENTS + printf((format_mouse_event_debug_message(evt) + " - other\n").c_str()); +#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ + } + + if (m_main_toolbar.on_mouse(evt, *this)) { + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + m_mouse.set_start_position_3D_as_invalid(); + return; + } + + if (m_undoredo_toolbar.on_mouse(evt, *this)) { + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + m_mouse.set_start_position_3D_as_invalid(); + return; + } + + if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) { + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + m_mouse.set_start_position_3D_as_invalid(); + return; + } + + if (wxGetApp().plater()->get_view_toolbar().on_mouse(evt, *this)) { + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + m_mouse.set_start_position_3D_as_invalid(); + return; + } + + for (GLVolume* volume : m_volumes.volumes) { + volume->force_sinking_contours = false; + } + + auto show_sinking_contours = [this]() { + const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); + for (unsigned int idx : idxs) { + m_volumes.volumes[idx]->force_sinking_contours = true; + } + m_dirty = true; + }; + + if (m_gizmos.on_mouse(evt)) { + if (wxWindow::FindFocus() != m_canvas) + // Grab keyboard focus for input in gizmo dialogs. + m_canvas->SetFocus(); + + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + + m_mouse.set_start_position_3D_as_invalid(); + m_mouse.position = pos.cast(); + + // It should be detection of volume change + // Not only detection of some modifiers !!! + if (evt.Dragging()) { + GLGizmosManager::EType c = m_gizmos.get_current_type(); + if (current_printer_technology() == ptFFF && + fff_print()->config().complete_objects){ + if (c == GLGizmosManager::EType::Move || + c == GLGizmosManager::EType::Scale || + c == GLGizmosManager::EType::Rotate ) + update_sequential_clearance(); + } else { + if (c == GLGizmosManager::EType::Move || + c == GLGizmosManager::EType::Scale || + c == GLGizmosManager::EType::Rotate) + show_sinking_contours(); + } + } + else if (evt.LeftUp() && + m_gizmos.get_current_type() == GLGizmosManager::EType::Scale && + m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) { + wxGetApp().obj_list()->selection_changed(); + } + + return; + } + + bool any_gizmo_active = m_gizmos.get_current() != nullptr; + + int selected_object_idx = m_selection.get_object_idx(); + int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; + m_layers_editing.select_object(*m_model, layer_editing_object_idx); + + if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) { + m_mouse.drag.move_requires_threshold = false; + m_mouse.set_move_start_threshold_position_2D_as_invalid(); + } + + if (evt.ButtonDown() && wxWindow::FindFocus() != m_canvas) + // Grab keyboard focus on any mouse click event. + m_canvas->SetFocus(); + + if (evt.Entering()) { +//#if defined(__WXMSW__) || defined(__linux__) +// // On Windows and Linux needs focus in order to catch key events + // Set focus in order to remove it from object list + if (m_canvas != nullptr) { + // Only set focus, if the top level window of this canvas is active + // and ObjectList has a focus + auto p = dynamic_cast(evt.GetEventObject()); + while (p->GetParent()) + p = p->GetParent(); +#ifdef __WIN32__ + wxWindow* const obj_list = wxGetApp().obj_list()->GetMainWindow(); +#else + wxWindow* const obj_list = wxGetApp().obj_list(); +#endif + if (obj_list == p->FindFocus()) + m_canvas->SetFocus(); + m_mouse.position = pos.cast(); + m_tooltip_enabled = false; + // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while + // the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to + // change the volume hover state if any is under the mouse + // 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible, + // so forces a resize to avoid multiple renders with different sizes (seen as flickering) + _refresh_if_shown_on_screen(); + m_tooltip_enabled = true; + } + m_mouse.set_start_position_2D_as_invalid(); +//#endif + } + else if (evt.Leaving()) { + _deactivate_undo_redo_toolbar_items(); + + // to remove hover on objects when the mouse goes out of this canvas + m_mouse.position = Vec2d(-1.0, -1.0); + m_dirty = true; + } + else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) { + if (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu()) + return; + + // If user pressed left or right button we first check whether this happened + // on a volume or not. + m_layers_editing.state = LayersEditing::Unknown; + if (layer_editing_object_idx != -1 && m_layers_editing.bar_rect_contains(*this, pos(0), pos(1))) { + // A volume is selected and the mouse is inside the layer thickness bar. + // Start editing the layer height. + m_layers_editing.state = LayersEditing::Editing; + _perform_layer_editing_action(&evt); + } + else { + const bool rectangle_selection_dragging = m_rectangle_selection.is_dragging(); + if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) { + if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && + m_gizmos.get_current_type() != GLGizmosManager::FdmSupports && + m_gizmos.get_current_type() != GLGizmosManager::Seam && + m_gizmos.get_current_type() != GLGizmosManager::Cut && + m_gizmos.get_current_type() != GLGizmosManager::Measure && + m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { + m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); + m_dirty = true; + } + } + + // Select volume in this 3D canvas. + // Don't deselect a volume if layer editing is enabled or any gizmo is active. We want the object to stay selected + // during the scene manipulation. + + if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) && !rectangle_selection_dragging) { + if (evt.LeftDown() && !m_hover_volume_idxs.empty()) { + int volume_idx = get_first_hover_volume_idx(); + bool already_selected = m_selection.contains_volume(volume_idx); + bool shift_down = evt.ShiftDown(); + + Selection::IndicesList curr_idxs = m_selection.get_volume_idxs(); + + if (already_selected && shift_down) + m_selection.remove(volume_idx); + else { + m_selection.add(volume_idx, !shift_down, true); + m_mouse.drag.move_requires_threshold = !already_selected; + if (already_selected) + m_mouse.set_move_start_threshold_position_2D_as_invalid(); + else + m_mouse.drag.move_start_threshold_position_2D = pos; + } + + // propagate event through callback + if (curr_idxs != m_selection.get_volume_idxs()) { + if (m_selection.is_empty()) + m_gizmos.reset_all_states(); + else + m_gizmos.refresh_on_off_state(); + + m_gizmos.update_data(); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + m_dirty = true; + } + } + } + + if (!m_hover_volume_idxs.empty() && !m_rectangle_selection.is_dragging()) { + if (evt.LeftDown() && m_moving_enabled && m_mouse.drag.move_volume_idx == -1) { + // Only accept the initial position, if it is inside the volume bounding box. + const int volume_idx = get_first_hover_volume_idx(); + BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); + volume_bbox.offset(1.0); + const bool is_cut_connector_selected = m_selection.is_any_connector(); + if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position) && !is_cut_connector_selected) { + m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None; + // The dragging operation is initiated. + m_mouse.drag.move_volume_idx = volume_idx; + m_selection.setup_cache(); + if (!evt.CmdDown()) + m_mouse.drag.start_position_3D = m_mouse.scene_position; + m_sequential_print_clearance_first_displacement = true; +#if !ENABLE_RAYCAST_PICKING + m_moving = true; +#endif // !ENABLE_RAYCAST_PICKING + } + } + } + } + } + else if (evt.Dragging() && evt.LeftIsDown() && !evt.CmdDown() && m_layers_editing.state == LayersEditing::Unknown && + m_mouse.drag.move_volume_idx != -1 && m_mouse.is_start_position_3D_defined()) { + if (!m_mouse.drag.move_requires_threshold) { + m_mouse.dragging = true; + Vec3d cur_pos = m_mouse.drag.start_position_3D; + // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag + if (m_selection.contains_volume(get_first_hover_volume_idx())) { + const Camera& camera = wxGetApp().plater()->get_camera(); + if (std::abs(camera.get_dir_forward().z()) < EPSILON) { + // side view -> move selected volumes orthogonally to camera view direction + const Linef3 ray = mouse_ray(pos); + const Vec3d dir = ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + const Vec3d inters = ray.a + (m_mouse.drag.start_position_3D - ray.a).dot(dir) / dir.squaredNorm() * dir; + // vector from the starting position to the found intersection + const Vec3d inters_vec = inters - m_mouse.drag.start_position_3D; + + const Vec3d camera_right = camera.get_dir_right(); + const Vec3d camera_up = camera.get_dir_up(); + + // finds projection of the vector along the camera axes + const double projection_x = inters_vec.dot(camera_right); + const double projection_z = inters_vec.dot(camera_up); + + // apply offset + cur_pos = m_mouse.drag.start_position_3D + projection_x * camera_right + projection_z * camera_up; + } + else { + // Generic view + // Get new position at the same Z of the initial click point. + cur_pos = mouse_ray(pos).intersect_plane(m_mouse.drag.start_position_3D.z()); + } + } + +#if ENABLE_WORLD_COORDINATE +#if ENABLE_RAYCAST_PICKING + m_moving = true; +#endif // ENABLE_RAYCAST_PICKING + TransformationType trafo_type; + trafo_type.set_relative(); + m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type); +#else + m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); +#endif // ENABLE_WORLD_COORDINATE + if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) + update_sequential_clearance(); + wxGetApp().obj_manipul()->set_dirty(); + m_dirty = true; + } + } + else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) { + // keeps the mouse position updated while dragging the selection rectangle + m_mouse.position = pos.cast(); + m_rectangle_selection.dragging(m_mouse.position); + m_dirty = true; + } + else if (evt.Dragging()) { + m_mouse.dragging = true; + + if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) { + if (m_layers_editing.state == LayersEditing::Editing) { + _perform_layer_editing_action(&evt); + m_mouse.position = pos.cast(); + } + } + // do not process the dragging if the left mouse was set down in another canvas + else if (evt.LeftIsDown()) { + // if dragging over blank area with left button, rotate +#if ENABLE_RAYCAST_PICKING + if (!m_moving) { +#endif // ENABLE_RAYCAST_PICKING + if ((any_gizmo_active || evt.CmdDown() || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) { + const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.0) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.0); + if (wxGetApp().app_config->get("use_free_camera") == "1") + // Virtual track ball (similar to the 3DConnexion mouse). + wxGetApp().plater()->get_camera().rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.0)); + else { + // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation. + // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(), + // which checks an atomics (flushes CPU caches). + // See GH issue #3816. + Camera& camera = wxGetApp().plater()->get_camera(); + camera.recover_from_free_camera(); + camera.rotate_on_sphere(rot.x(), rot.y(), current_printer_technology() != ptSLA); + } + + m_dirty = true; + } + m_mouse.drag.start_position_3D = Vec3d((double)pos.x(), (double)pos.y(), 0.0); +#if ENABLE_RAYCAST_PICKING + } +#endif // ENABLE_RAYCAST_PICKING + } + else if (evt.MiddleIsDown() || evt.RightIsDown()) { + // If dragging over blank area with right/middle button, pan. + if (m_mouse.is_start_position_2D_defined()) { + // get point in model space at Z = 0 + float z = 0.0f; + const Vec3d cur_pos = _mouse_to_3d(pos, &z); + const Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z); + Camera& camera = wxGetApp().plater()->get_camera(); + if (wxGetApp().app_config->get("use_free_camera") != "1") + // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation. + // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(), + // which checks an atomics (flushes CPU caches). + // See GH issue #3816. + camera.recover_from_free_camera(); + + camera.set_target(camera.get_target() + orig - cur_pos); + m_dirty = true; + } + + m_mouse.drag.start_position_2D = pos; + } + } + else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) { +#if ENABLE_RAYCAST_PICKING + m_mouse.position = pos.cast(); +#endif // ENABLE_RAYCAST_PICKING + + if (m_layers_editing.state != LayersEditing::Unknown) { + m_layers_editing.state = LayersEditing::Unknown; + _stop_timer(); + m_layers_editing.accept_changes(*this); + } + else if (m_mouse.drag.move_volume_idx != -1 && m_mouse.dragging) { + do_move(L("Move Object")); + wxGetApp().obj_manipul()->set_dirty(); + // Let the plater know that the dragging finished, so a delayed refresh + // of the scene with the background processing data should be performed. + post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + } + else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging()) { + if (evt.ShiftDown() || evt.AltDown()) + _update_selection_from_hover(); + + m_rectangle_selection.stop_dragging(); + } + else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && !is_layers_editing_enabled()) { + // deselect and propagate event through callback + if (!evt.ShiftDown() && (!any_gizmo_active || !evt.CmdDown()) && m_picking_enabled) + deselect_all(); + } + else if (evt.RightUp()) { +#if !ENABLE_RAYCAST_PICKING + m_mouse.position = pos.cast(); +#endif // !ENABLE_RAYCAST_PICKING + // forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while + // the context menu is already shown + render(); + if (!m_hover_volume_idxs.empty()) { + // if right clicking on volume, propagate event through callback (shows context menu) + int volume_idx = get_first_hover_volume_idx(); + if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower + && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::Measure)) // disable context menu when the gizmo is open + { + // forces the selection of the volume + /* m_selection.add(volume_idx); // #et_FIXME_if_needed + * To avoid extra "Add-Selection" snapshots, + * call add() with check_for_already_contained=true + * */ + m_selection.add(volume_idx, true, true); + m_gizmos.refresh_on_off_state(); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + m_gizmos.update_data(); + wxGetApp().obj_manipul()->set_dirty(); + // forces a frame render to update the view before the context menu is shown + render(); + } + } + Vec2d logical_pos = pos.cast(); +#if ENABLE_RETINA_GL + const float factor = m_retina_helper->get_scale_factor(); + logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); +#endif // ENABLE_RETINA_GL + if (!m_mouse.dragging) { + // do not post the event if the user is panning the scene + // or if right click was done over the wipe tower + const bool post_right_click_event = (m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower) && + m_gizmos.get_current_type() != GLGizmosManager::Measure; + if (post_right_click_event) + post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() })); + } + } + + mouse_up_cleanup(); + } + else if (evt.Moving()) { + m_mouse.position = pos.cast(); + + // updates gizmos overlay + if (m_selection.is_empty()) + m_gizmos.reset_all_states(); + + m_dirty = true; + } + else + evt.Skip(); + + // Detection of doubleclick on text to open emboss edit window + if (evt.LeftDClick() && m_gizmos.get_current() == nullptr && !m_hover_volume_idxs.empty()) { + for (int hover_volume_id : m_hover_volume_idxs) { + const GLVolume &hover_gl_volume = *m_volumes.volumes[hover_volume_id]; + const ModelObject* hover_object = m_model->objects[hover_gl_volume.object_idx()]; + int hover_volume_idx = hover_gl_volume.volume_idx(); + const ModelVolume* hover_volume = hover_object->volumes[hover_volume_idx]; + if (hover_volume->text_configuration.has_value()) { + //m_selection.set_mode(Selection::EMode::Volume); + //m_selection.add(hover_volume_id); // add whole instance + m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id}); + m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss); + wxGetApp().obj_list()->update_selections(); + return; + } + } + } + + if (m_moving) + show_sinking_contours(); + +#ifdef __WXMSW__ + if (on_enter_workaround) + m_mouse.position = Vec2d(-1., -1.); +#endif /* __WXMSW__ */ +} + +void GLCanvas3D::on_paint(wxPaintEvent& evt) +{ + if (m_initialized) + m_dirty = true; + else + // Call render directly, so it gets initialized immediately, not from On Idle handler. + this->render(); +} + +void GLCanvas3D::on_set_focus(wxFocusEvent& evt) +{ + m_tooltip_enabled = false; + _refresh_if_shown_on_screen(); + m_tooltip_enabled = true; +} + +Size GLCanvas3D::get_canvas_size() const +{ + int w = 0; + int h = 0; + + if (m_canvas != nullptr) + m_canvas->GetSize(&w, &h); + +#if ENABLE_RETINA_GL + const float factor = m_retina_helper->get_scale_factor(); + w *= factor; + h *= factor; +#else + const float factor = 1.0f; +#endif + + return Size(w, h, factor); +} + +Vec2d GLCanvas3D::get_local_mouse_position() const +{ + if (m_canvas == nullptr) + return Vec2d::Zero(); + + wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition()); + const double factor = +#if ENABLE_RETINA_GL + m_retina_helper->get_scale_factor(); +#else + 1.0; +#endif + return Vec2d(factor * mouse_pos.x, factor * mouse_pos.y); +} + +void GLCanvas3D::set_tooltip(const std::string& tooltip) +{ + if (m_canvas != nullptr) + m_tooltip.set_text(tooltip); +} + +void GLCanvas3D::do_move(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + std::set> done; // keeps track of modified instances + bool object_moved = false; + Vec3d wipe_tower_origin = Vec3d::Zero(); + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) { + int object_idx = v->object_idx(); + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + std::pair done_id(object_idx, instance_idx); + + if (0 <= object_idx && object_idx < (int)m_model->objects.size()) { + done.insert(done_id); + + // Move instances/volumes + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) +#if ENABLE_WORLD_COORDINATE + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); +#endif // ENABLE_WORLD_COORDINATE + else if (selection_mode == Selection::Volume) +#if ENABLE_WORLD_COORDINATE + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else + model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); +#endif // ENABLE_WORLD_COORDINATE + + object_moved = true; + model_object->invalidate_bounding_box(); + } + } + else if (v->is_wipe_tower) + // Move a wipe tower proxy. + wipe_tower_origin = v->get_volume_offset(); + } + + // Fixes flying instances + for (const std::pair& i : done) { + ModelObject* m = m_model->objects[i.first]; + const double shift_z = m->get_instance_min_z(i.second); + if (current_printer_technology() == ptSLA || shift_z > SINKING_Z_THRESHOLD) { + const Vec3d shift(0.0, 0.0, -shift_z); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + } + + // if the selection is not valid to allow for layer editing after the move, we need to turn off the tool if it is running + // similar to void Plater::priv::selection_changed() + if (!wxGetApp().plater()->can_layers_editing() && is_layers_editing_enabled()) + post_event(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); + + if (object_moved) + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED)); + + if (wipe_tower_origin != Vec3d::Zero()) + post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin))); + + reset_sequential_print_clearance(); + + m_dirty = true; +} + +void GLCanvas3D::do_rotate(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + // stores current min_z of instances + std::map, double> min_zs; + for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { + const ModelObject* obj = m_model->objects[i]; + for (int j = 0; j < static_cast(obj->instances.size()); ++j) { + if (snapshot_type == L("Gizmo-Place on Face") && m_selection.get_object_idx() == i) { + // This means we are flattening this object. In that case pretend + // that it is not sinking (even if it is), so it is placed on bed + // later on (whatever is sinking will be left sinking). + min_zs[{ i, j }] = SINKING_Z_THRESHOLD; + } + else + min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); + + } + } + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) { + if (v->is_wipe_tower) { + const Vec3d offset = v->get_volume_offset(); + post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset.x(), offset.y(), v->get_volume_rotation().z()))); + } + const int object_idx = v->object_idx(); + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + continue; + + const int instance_idx = v->instance_idx(); + const int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + // Rotate instances/volumes. + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) { +#if ENABLE_WORLD_COORDINATE + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else + model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); +#endif // ENABLE_WORLD_COORDINATE + } + else if (selection_mode == Selection::Volume) { +#if ENABLE_WORLD_COORDINATE + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else + model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); + model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); +#endif // ENABLE_WORLD_COORDINATE + } + model_object->invalidate_bounding_box(); + } + } + + // Fixes sinking/flying instances + for (const std::pair& i : done) { + ModelObject* m = m_model->objects[i.first]; + const double shift_z = m->get_instance_min_z(i.second); + // leave sinking instances as sinking + if (min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { + const Vec3d shift(0.0, 0.0, -shift_z); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + + wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + } + + if (!done.empty()) + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + + m_dirty = true; +} + +void GLCanvas3D::do_scale(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + // stores current min_z of instances + std::map, double> min_zs; + if (!snapshot_type.empty()) { + for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { + const ModelObject* obj = m_model->objects[i]; + for (int j = 0; j < static_cast(obj->instances.size()); ++j) { + min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); + } + } + } + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) { + const int object_idx = v->object_idx(); + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + continue; + + const int instance_idx = v->instance_idx(); + const int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + // Rotate instances/volumes + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) { +#if ENABLE_WORLD_COORDINATE + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else + model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); +#endif // ENABLE_WORLD_COORDINATE + } + else if (selection_mode == Selection::Volume) { +#if ENABLE_WORLD_COORDINATE + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); + model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); +#endif // ENABLE_WORLD_COORDINATE + } + model_object->invalidate_bounding_box(); + } + } + + // Fixes sinking/flying instances + for (const std::pair& i : done) { + ModelObject* m = m_model->objects[i.first]; + const double shift_z = m->get_instance_min_z(i.second); + // leave sinking instances as sinking + if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { + const Vec3d shift(0.0, 0.0, -shift_z); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + } + + if (!done.empty()) + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED)); + + m_dirty = true; +} + +void GLCanvas3D::do_mirror(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + // stores current min_z of instances + std::map, double> min_zs; + if (!snapshot_type.empty()) { + for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { + const ModelObject* obj = m_model->objects[i]; + for (int j = 0; j < static_cast(obj->instances.size()); ++j) { + min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); + } + } + } + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) { + int object_idx = v->object_idx(); + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + continue; + + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + // Mirror instances/volumes + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) +#if ENABLE_WORLD_COORDINATE + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else + model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror()); +#endif // ENABLE_WORLD_COORDINATE + else if (selection_mode == Selection::Volume) +#if ENABLE_WORLD_COORDINATE + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else + model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror()); +#endif // ENABLE_WORLD_COORDINATE + + model_object->invalidate_bounding_box(); + } + } + + // Fixes sinking/flying instances + for (const std::pair& i : done) { + ModelObject* m = m_model->objects[i.first]; + double shift_z = m->get_instance_min_z(i.second); + // leave sinking instances as sinking + if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { + Vec3d shift(0.0, 0.0, -shift_z); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + wxGetApp().obj_list()->update_info_items(static_cast(i.first)); + } + + post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + + m_dirty = true; +} + +#if ENABLE_WORLD_COORDINATE +void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + std::set> done; // keeps track of modified instances + + const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); + + for (unsigned int id : idxs) { + const GLVolume* v = m_volumes.volumes[id]; + int object_idx = v->object_idx(); + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + continue; + + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); + model_object->invalidate_bounding_box(); + } + } + + post_event(SimpleEvent(EVT_GLCANVAS_RESET_SKEW)); + + m_dirty = true; +} +#endif // ENABLE_WORLD_COORDINATE + +void GLCanvas3D::update_gizmos_on_off_state() +{ + set_as_dirty(); + m_gizmos.update_data(); + m_gizmos.refresh_on_off_state(); +} + +void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on) +{ + m_sidebar_field = focus_on ? opt_key : ""; + if (!m_sidebar_field.empty()) + m_gizmos.reset_all_states(); + + m_dirty = true; +} + +void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) +{ + std::string field = "layer_" + std::to_string(type) + "_" + float_to_string_decimal_point(range.first) + "_" + float_to_string_decimal_point(range.second); + handle_sidebar_focus_event(field, true); +} + +void GLCanvas3D::update_ui_from_settings() +{ + m_dirty = true; + +#if __APPLE__ + // Update OpenGL scaling on OSX after the user toggled the "use_retina_opengl" settings in Preferences dialog. + const float orig_scaling = m_retina_helper->get_scale_factor(); + + const bool use_retina = wxGetApp().app_config->get("use_retina_opengl") == "1"; + BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Use Retina OpenGL: " << use_retina; + m_retina_helper->set_use_retina(use_retina); + const float new_scaling = m_retina_helper->get_scale_factor(); + + if (new_scaling != orig_scaling) { + BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Scaling factor: " << new_scaling; + + Camera& camera = wxGetApp().plater()->get_camera(); + camera.set_zoom(camera.get_zoom() * new_scaling / orig_scaling); + _refresh_if_shown_on_screen(); + } +#endif // ENABLE_RETINA_GL + + if (wxGetApp().is_editor()) + wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get("show_collapse_button") == "1"); +} + +GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const +{ + WipeTowerInfo wti; + + for (const GLVolume* vol : m_volumes.volumes) { + if (vol->is_wipe_tower) { + wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), + m_config->opt_float("wipe_tower_y")); + wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); + const BoundingBoxf3& bb = vol->bounding_box(); + wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)}; + break; + } + } + + return wti; +} + +Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) +{ + float z0 = 0.0f; + float z1 = 1.0f; + return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)); +} + +double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const +{ + const BoundingBoxf& bbox = m_bed.build_volume().bounding_volume2d(); + return factor * std::max(bbox.size()[0], bbox.size()[1]); +} + +void GLCanvas3D::set_cursor(ECursorType type) +{ + if ((m_canvas != nullptr) && (m_cursor_type != type)) + { + switch (type) + { + case Standard: { m_canvas->SetCursor(*wxSTANDARD_CURSOR); break; } + case Cross: { m_canvas->SetCursor(*wxCROSS_CURSOR); break; } + } + + m_cursor_type = type; + } +} + +void GLCanvas3D::msw_rescale() +{ + m_gcode_viewer.invalidate_legend(); +} + +void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() +{ + std::string new_tooltip = _u8L("Switch to Settings") + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; + + m_main_toolbar.set_tooltip(get_main_toolbar_item_id("settings"), new_tooltip); +} + +bool GLCanvas3D::has_toolpaths_to_export() const +{ + return m_gcode_viewer.can_export_toolpaths(); +} + +void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const +{ + m_gcode_viewer.export_toolpaths_to_obj(filename); +} + +void GLCanvas3D::mouse_up_cleanup() +{ + m_moving = false; + m_mouse.drag.move_volume_idx = -1; + m_mouse.set_start_position_3D_as_invalid(); + m_mouse.set_start_position_2D_as_invalid(); + m_mouse.dragging = false; + m_mouse.ignore_left_up = false; + m_dirty = true; + + if (m_canvas->HasCapture()) + m_canvas->ReleaseMouse(); +} + +void GLCanvas3D::update_sequential_clearance() +{ + if (current_printer_technology() != ptFFF || !fff_print()->config().complete_objects) + return; + + if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) + return; + + // collects instance transformations from volumes + // first define temporary cache + unsigned int instances_count = 0; + std::vector>> instance_transforms; + for (size_t obj = 0; obj < m_model->objects.size(); ++obj) { + instance_transforms.emplace_back(std::vector>()); + const ModelObject* model_object = m_model->objects[obj]; + for (size_t i = 0; i < model_object->instances.size(); ++i) { + instance_transforms[obj].emplace_back(std::optional()); + ++instances_count; + } + } + + if (instances_count == 1) + return; + + // second fill temporary cache with data from volumes + for (const GLVolume* v : m_volumes.volumes) { + if (v->is_modifier || v->is_wipe_tower) + continue; + + auto& transform = instance_transforms[v->object_idx()][v->instance_idx()]; + if (!transform.has_value()) + transform = v->get_instance_transformation(); + } + + // calculates objects 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) + // this is done only the first time this method is called while moving the mouse, + // the results are then cached for following displacements + if (m_sequential_print_clearance_first_displacement) { + m_sequential_print_clearance.m_hull_2d_cache.clear(); + float shrink_factor = static_cast(scale_(0.5 * fff_print()->config().extruder_clearance_radius.value - EPSILON)); + double mitter_limit = scale_(0.1); + m_sequential_print_clearance.m_hull_2d_cache.reserve(m_model->objects.size()); + for (size_t i = 0; i < m_model->objects.size(); ++i) { + ModelObject* model_object = m_model->objects[i]; + ModelInstance* model_instance0 = model_object->instances.front(); +#if ENABLE_WORLD_COORDINATE + Geometry::Transformation trafo = model_instance0->get_transformation(); + trafo.set_offset({ 0.0, 0.0, model_instance0->get_offset().z() }); + Polygon hull_2d = offset(model_object->convex_hull_2d(trafo.get_matrix()), + // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects + // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. + shrink_factor, + jtRound, mitter_limit).front(); +#else + Polygon hull_2d = offset(model_object->convex_hull_2d(Geometry::assemble_transform({ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), + model_instance0->get_scaling_factor(), model_instance0->get_mirror())), + // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects + // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. + shrink_factor, + jtRound, mitter_limit).front(); +#endif // ENABLE_WORLD_COORDINATE + + Pointf3s& cache_hull_2d = m_sequential_print_clearance.m_hull_2d_cache.emplace_back(Pointf3s()); + cache_hull_2d.reserve(hull_2d.points.size()); + for (const Point& p : hull_2d.points) { + cache_hull_2d.emplace_back(unscale(p.x()), unscale(p.y()), 0.0); + } + } + m_sequential_print_clearance_first_displacement = false; + } + + // calculates instances 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) + Polygons polygons; + polygons.reserve(instances_count); + for (size_t i = 0; i < instance_transforms.size(); ++i) { + const auto& instances = instance_transforms[i]; + double rotation_z0 = instances.front()->get_rotation().z(); + for (const auto& instance : instances) { + Geometry::Transformation transformation; + const Vec3d& offset = instance->get_offset(); + transformation.set_offset({ offset.x(), offset.y(), 0.0 }); + transformation.set_rotation(Z, instance->get_rotation().z() - rotation_z0); + const Transform3d& trafo = transformation.get_matrix(); + const Pointf3s& hull_2d = m_sequential_print_clearance.m_hull_2d_cache[i]; + Points inst_pts; + inst_pts.reserve(hull_2d.size()); + for (size_t j = 0; j < hull_2d.size(); ++j) { + const Vec3d p = trafo * hull_2d[j]; + inst_pts.emplace_back(scaled(p.x()), scaled(p.y())); + } + polygons.emplace_back(Geometry::convex_hull(std::move(inst_pts))); + } + } + + // sends instances 2d hulls to be rendered + set_sequential_print_clearance_visible(true); + set_sequential_print_clearance_render_fill(false); + set_sequential_print_clearance_polygons(polygons); +} + +bool GLCanvas3D::is_object_sinking(int object_idx) const +{ + for (const GLVolume* v : m_volumes.volumes) { + if (v->object_idx() == object_idx && (v->is_sinking() || (!v->is_modifier && v->is_below_printbed()))) + return true; + } + return false; +} + +void GLCanvas3D::apply_retina_scale(Vec2d &screen_coordinate) const +{ +#if ENABLE_RETINA_GL + double scale = static_cast(m_retina_helper->get_scale_factor()); + screen_coordinate *= scale; +#endif // ENABLE_RETINA_GL +} + +bool GLCanvas3D::_is_shown_on_screen() const +{ + return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; +} + +// Getter for the const char*[] +static bool string_getter(const bool is_undo, int idx, const char** out_text) +{ + return wxGetApp().plater()->undo_redo_string_getter(is_undo, idx, out_text); +} + +bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) +{ + bool action_taken = false; + + ImGuiWrapper* imgui = wxGetApp().imgui(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + imgui->set_next_window_pos(pos_x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#else + const float x = pos_x * (float)wxGetApp().plater()->get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); + imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + std::string title = is_undo ? L("Undo History") : L("Redo History"); + imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + int hovered = m_imgui_undo_redo_hovered_pos; + int selected = -1; + float em = static_cast(wxGetApp().em_unit()); +#if ENABLE_RETINA_GL + em *= m_retina_helper->get_scale_factor(); +#endif + + if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected, m_mouse_wheel)) + m_imgui_undo_redo_hovered_pos = hovered; + else + m_imgui_undo_redo_hovered_pos = -1; + + if (selected >= 0) { + is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); + action_taken = true; + } + + imgui->text(wxString::Format(is_undo ? _L_PLURAL("Undo %1$d Action", "Undo %1$d Actions", hovered + 1) : _L_PLURAL("Redo %1$d Action", "Redo %1$d Actions", hovered + 1), hovered + 1)); + + imgui->end(); + + return action_taken; +} + +// Getter for the const char*[] for the search list +static bool search_string_getter(int idx, const char** label, const char** tooltip) +{ + return wxGetApp().plater()->search_string_getter(idx, label, tooltip); +} + +bool GLCanvas3D::_render_search_list(float pos_x) +{ + bool action_taken = false; + ImGuiWrapper* imgui = wxGetApp().imgui(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#else + const float x = /*pos_x * (float)wxGetApp().plater()->get_camera().get_zoom() + */0.5f * (float)get_canvas_size().get_width(); + imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + std::string title = L("Search"); + imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + int selected = -1; + bool edited = false; + float em = static_cast(wxGetApp().em_unit()); +#if ENABLE_RETINA_GL + em *= m_retina_helper->get_scale_factor(); +#endif // ENABLE_RETINA_GL + + Sidebar& sidebar = wxGetApp().sidebar(); + + std::string& search_line = sidebar.get_search_line(); + char *s = new char[255]; + strcpy(s, search_line.empty() ? _u8L("Enter a search term").c_str() : search_line.c_str()); + + imgui->search_list(ImVec2(45 * em, 30 * em), &search_string_getter, s, + sidebar.get_searcher().view_params, + selected, edited, m_mouse_wheel, wxGetApp().is_localized()); + + search_line = s; + delete [] s; + if (search_line == _u8L("Enter a search term")) + search_line.clear(); + + if (edited) + sidebar.search(); + + if (selected >= 0) { + // selected == 9999 means that Esc kye was pressed + /*// revert commit https://github.com/prusa3d/PrusaSlicer/commit/91897589928789b261ca0dc735ffd46f2b0b99f2 + if (selected == 9999) + action_taken = true; + else + sidebar.jump_to_option(selected);*/ + if (selected != 9999) { + imgui->end(); // end imgui before the jump to option + sidebar.jump_to_option(selected); + return true; + } + action_taken = true; + } + + imgui->end(); + + return action_taken; +} + +bool GLCanvas3D::_render_arrange_menu(float pos_x) +{ + ImGuiWrapper *imgui = wxGetApp().imgui(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#else + auto canvas_w = float(get_canvas_size().get_width()); + const float x = pos_x * float(wxGetApp().plater()->get_camera().get_zoom()) + 0.5f * canvas_w; + imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + ArrangeSettings settings = get_arrange_settings(); + ArrangeSettings &settings_out = get_arrange_settings(); + + auto &appcfg = wxGetApp().app_config; + PrinterTechnology ptech = current_printer_technology(); + + bool settings_changed = false; + float dist_min = 0.f; + float dist_bed_min = 0.f; + std::string dist_key = "min_object_distance"; + std::string dist_bed_key = "min_bed_distance"; + std::string rot_key = "enable_rotation"; + std::string postfix; + + if (ptech == ptSLA) { + postfix = "_sla"; + } else if (ptech == ptFFF) { + auto co_opt = m_config->option("complete_objects"); + if (co_opt && co_opt->value) { + dist_min = float(min_object_distance(*m_config)); + postfix = "_fff_seq_print"; + } else { + dist_min = 0.f; + postfix = "_fff"; + } + } + + dist_key += postfix; + dist_bed_key += postfix; + rot_key += postfix; + + imgui->text(GUI::format_wxstr(_L("Press %1%left mouse button to enter the exact value"), shortkey_ctrl_prefix())); + + if (imgui->slider_float(_L("Spacing"), &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance) { + settings.distance = std::max(dist_min, settings.distance); + settings_out.distance = settings.distance; + appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); + settings_changed = true; + } + + if (imgui->slider_float(_L("Spacing from bed"), &settings.distance_from_bed, dist_bed_min, 100.0f, "%5.2f") || dist_bed_min > settings.distance_from_bed) { + settings.distance_from_bed = std::max(dist_bed_min, settings.distance_from_bed); + settings_out.distance_from_bed = settings.distance_from_bed; + appcfg->set("arrange", dist_bed_key.c_str(), float_to_string_decimal_point(settings_out.distance_from_bed)); + settings_changed = true; + } + + if (imgui->checkbox(_L("Enable rotations (slow)"), settings.enable_rotation)) { + settings_out.enable_rotation = settings.enable_rotation; + appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); + settings_changed = true; + } + + ImGui::Separator(); + + if (imgui->button(_L("Reset"))) { + settings_out = ArrangeSettings{}; + settings_out.distance = std::max(dist_min, settings_out.distance); + appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); + appcfg->set("arrange", dist_bed_key.c_str(), float_to_string_decimal_point(settings_out.distance_from_bed)); + appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); + settings_changed = true; + } + + ImGui::SameLine(); + + if (imgui->button(_L("Arrange"))) { + wxGetApp().plater()->arrange(); + } + + imgui->end(); + + return settings_changed; +} + +#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT +static void debug_output_thumbnail(const ThumbnailData& thumbnail_data) +{ + // debug export of generated image + wxImage image(thumbnail_data.width, thumbnail_data.height); + image.InitAlpha(); + + for (unsigned int r = 0; r < thumbnail_data.height; ++r) + { + unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width; + for (unsigned int c = 0; c < thumbnail_data.width; ++c) + { + unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c); + image.SetRGB((int)c, (int)r, px[0], px[1], px[2]); + image.SetAlpha((int)c, (int)r, px[3]); + } + } + + image.SaveFile("C:/prusa/test/test.png", wxBITMAP_TYPE_PNG); +} +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + +void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) +{ + auto is_visible = [](const GLVolume& v) { + bool ret = v.printable; + ret &= (!v.shader_outside_printer_detection_enabled || !v.is_outside); + return ret; + }; + + GLVolumePtrs visible_volumes; + + for (GLVolume* vol : volumes.volumes) { + if (!vol->is_modifier && !vol->is_wipe_tower && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) { + if (!thumbnail_params.printable_only || is_visible(*vol)) + visible_volumes.emplace_back(vol); + } + } + + BoundingBoxf3 volumes_box; + if (!visible_volumes.empty()) { + for (const GLVolume* vol : visible_volumes) { + volumes_box.merge(vol->transformed_bounding_box()); + } + } + else + // This happens for empty projects + volumes_box = m_bed.extended_bounding_box(); + + Camera camera; + camera.set_type(camera_type); + camera.set_scene_box(scene_bounding_box()); +#if ENABLE_RAYCAST_PICKING + camera.set_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); + camera.apply_viewport(); +#else + camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); +#endif // ENABLE_RAYCAST_PICKING + camera.zoom_to_box(volumes_box); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Transform3d& view_matrix = camera.get_view_matrix(); +#else + camera.apply_view_matrix(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + double near_z = -1.0; + double far_z = -1.0; + + if (thumbnail_params.show_bed) { + // extends the near and far z of the frustrum to avoid the bed being clipped + + // box in eye space +#if ENABLE_LEGACY_OPENGL_REMOVAL + const BoundingBoxf3 t_bed_box = m_bed.extended_bounding_box().transformed(view_matrix); +#else + const BoundingBoxf3 t_bed_box = m_bed.extended_bounding_box().transformed(camera.get_view_matrix()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + near_z = -t_bed_box.max.z(); + far_z = -t_bed_box.min.z(); + } + + camera.apply_projection(volumes_box, near_z, far_z); + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + if (thumbnail_params.transparent_background) + glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); + + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + shader->start_using(); + shader->set_uniform("emission_factor", 0.0f); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Transform3d& projection_matrix = camera.get_projection_matrix(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + for (GLVolume* vol : visible_volumes) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + vol->model.set_color((vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); +#else + shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + // the volume may have been deactivated by an active gizmo + const bool is_active = vol->is_active; + vol->is_active = true; +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Transform3d model_matrix = vol->world_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + vol->render(); + vol->is_active = is_active; + } + + shader->stop_using(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + + if (thumbnail_params.show_bed) +#if ENABLE_LEGACY_OPENGL_REMOVAL + _render_bed(view_matrix, projection_matrix, !camera.is_looking_downward(), false); +#else + _render_bed(!camera.is_looking_downward(), false); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // restore background color + if (thumbnail_params.transparent_background) + glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); +} + +void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) +{ + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + bool multisample = m_multisample_allowed; + if (multisample) + glsafe(::glEnable(GL_MULTISAMPLE)); + + GLint max_samples; + glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples)); + GLsizei num_samples = max_samples / 2; + + GLuint render_fbo; + glsafe(::glGenFramebuffers(1, &render_fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)); + + GLuint render_tex = 0; + GLuint render_tex_buffer = 0; + if (multisample) { + // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 + glsafe(::glGenRenderbuffers(1, &render_tex_buffer)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer)); + glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h)); + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer)); + } + else { + glsafe(::glGenTextures(1, &render_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); + } + + GLuint render_depth; + glsafe(::glGenRenderbuffers(1, &render_depth)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); + if (multisample) + glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h)); + else + glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h)); + + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); + + GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { + _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); + + if (multisample) { + GLuint resolve_fbo; + glsafe(::glGenFramebuffers(1, &resolve_fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo)); + + GLuint resolve_tex; + glsafe(::glGenTextures(1, &resolve_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0)); + + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { + glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo)); + glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo)); + glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); + + glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo)); + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + } + + glsafe(::glDeleteTextures(1, &resolve_tex)); + glsafe(::glDeleteFramebuffers(1, &resolve_fbo)); + } + else + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + debug_output_thumbnail(thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + } + + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); + glsafe(::glDeleteRenderbuffers(1, &render_depth)); + if (render_tex_buffer != 0) + glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer)); + if (render_tex != 0) + glsafe(::glDeleteTextures(1, &render_tex)); + glsafe(::glDeleteFramebuffers(1, &render_fbo)); + + if (multisample) + glsafe(::glDisable(GL_MULTISAMPLE)); +} + +void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) +{ + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + bool multisample = m_multisample_allowed; + if (multisample) + glsafe(::glEnable(GL_MULTISAMPLE)); + + GLint max_samples; + glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples)); + GLsizei num_samples = max_samples / 2; + + GLuint render_fbo; + glsafe(::glGenFramebuffersEXT(1, &render_fbo)); + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo)); + + GLuint render_tex = 0; + GLuint render_tex_buffer = 0; + if (multisample) { + // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 + glsafe(::glGenRenderbuffersEXT(1, &render_tex_buffer)); + glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_tex_buffer)); + glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_RGBA8, w, h)); + glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, render_tex_buffer)); + } + else { + glsafe(::glGenTextures(1, &render_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0)); + } + + GLuint render_depth; + glsafe(::glGenRenderbuffersEXT(1, &render_depth)); + glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth)); + if (multisample) + glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h)); + else + glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h)); + + glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth)); + + GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) { + _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); + + if (multisample) { + GLuint resolve_fbo; + glsafe(::glGenFramebuffersEXT(1, &resolve_fbo)); + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo)); + + GLuint resolve_tex; + glsafe(::glGenTextures(1, &resolve_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, resolve_tex, 0)); + + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) { + glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, render_fbo)); + glsafe(::glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, resolve_fbo)); + glsafe(::glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); + + glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, resolve_fbo)); + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + } + + glsafe(::glDeleteTextures(1, &resolve_tex)); + glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo)); + } + else + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + debug_output_thumbnail(thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + } + + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); + glsafe(::glDeleteRenderbuffersEXT(1, &render_depth)); + if (render_tex_buffer != 0) + glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer)); + if (render_tex != 0) + glsafe(::glDeleteTextures(1, &render_tex)); + glsafe(::glDeleteFramebuffersEXT(1, &render_fbo)); + + if (multisample) + glsafe(::glDisable(GL_MULTISAMPLE)); +} + +void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) +{ + // check that thumbnail size does not exceed the default framebuffer size + const Size& cnv_size = get_canvas_size(); + unsigned int cnv_w = (unsigned int)cnv_size.get_width(); + unsigned int cnv_h = (unsigned int)cnv_size.get_height(); + if (w > cnv_w || h > cnv_h) { + float ratio = std::min((float)cnv_w / (float)w, (float)cnv_h / (float)h); + w = (unsigned int)(ratio * (float)w); + h = (unsigned int)(ratio * (float)h); + } + + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); + + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + debug_output_thumbnail(thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + + // restore the default framebuffer size to avoid flickering on the 3D scene +#if ENABLE_RAYCAST_PICKING + wxGetApp().plater()->get_camera().apply_viewport(); +#else + wxGetApp().plater()->get_camera().apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height()); +#endif // ENABLE_RAYCAST_PICKING +} + +bool GLCanvas3D::_init_toolbars() +{ + if (!_init_main_toolbar()) + return false; + + if (!_init_undoredo_toolbar()) + return false; + + if (!_init_view_toolbar()) + return false; + + if (!_init_collapse_toolbar()) + return false; + + return true; +} + +bool GLCanvas3D::_init_main_toolbar() +{ + if (!m_main_toolbar.is_enabled()) + return true; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!m_main_toolbar.init(background_data)) { + // unable to init the toolbar texture, disable it + m_main_toolbar.set_enabled(false); + return true; + } + // init arrow +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!m_main_toolbar.init_arrow("toolbar_arrow_2.svg")) +#else + BackgroundTexture::Metadata arrow_data; + arrow_data.filename = "toolbar_arrow.svg"; + arrow_data.left = 0; + arrow_data.top = 0; + arrow_data.right = 0; + arrow_data.bottom = 0; + if (!m_main_toolbar.init_arrow(arrow_data)) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + BOOST_LOG_TRIVIAL(error) << "Main toolbar failed to load arrow texture."; + + // m_gizmos is created at constructor, thus we can init arrow here. +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!m_gizmos.init_arrow("toolbar_arrow_2.svg")) +#else + if (!m_gizmos.init_arrow(arrow_data)) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + BOOST_LOG_TRIVIAL(error) << "Gizmos manager failed to load arrow texture."; + +// m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); + m_main_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); + m_main_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + m_main_toolbar.set_border(5.0f); + m_main_toolbar.set_separator_size(5); + m_main_toolbar.set_gap_size(4); + + GLToolbarItem::Data item; + + item.name = "add"; + item.icon_filename = "add.svg"; + item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; + item.sprite_id = 0; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "delete"; + item.icon_filename = "remove.svg"; + item.tooltip = _utf8(L("Delete")) + " [Del]"; + item.sprite_id = 1; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "deleteall"; + item.icon_filename = "delete_all.svg"; + item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; + item.sprite_id = 2; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "arrange"; + item.icon_filename = "arrange.svg"; + item.tooltip = _utf8(L("Arrange")) + " [A]\n" + _utf8(L("Arrange selection")) + " [Shift+A]\n" + _utf8(L("Click right mouse button to show arrangement options")); + item.sprite_id = 3; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; + item.right.toggable = true; + item.right.render_callback = [this](float left, float right, float, float) { + if (m_canvas != nullptr) + _render_arrange_menu(0.5f * (left + right)); + }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.right.toggable = false; + item.right.render_callback = GLToolbarItem::Default_Render_Callback; + + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "copy"; + item.icon_filename = "copy.svg"; + item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; + item.sprite_id = 4; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "paste"; + item.icon_filename = "paste.svg"; + item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; + item.sprite_id = 5; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "more"; + item.icon_filename = "instance_add.svg"; + item.tooltip = _utf8(L("Add instance")) + " [+]"; + item.sprite_id = 6; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; + item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; + + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "fewer"; + item.icon_filename = "instance_remove.svg"; + item.tooltip = _utf8(L("Remove instance")) + " [-]"; + item.sprite_id = 7; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; + item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "splitobjects"; + item.icon_filename = "split_objects.svg"; + item.tooltip = _utf8(L("Split to objects")); + item.sprite_id = 8; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; + item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "splitvolumes"; + item.icon_filename = "split_parts.svg"; + item.tooltip = _utf8(L("Split to parts")); + item.sprite_id = 9; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; + item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "settings"; + item.icon_filename = "settings.svg"; + item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; + item.sprite_id = 10; + item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; + item.visibility_callback = []() { return (wxGetApp().app_config->get("new_settings_layout_mode") == "1" || + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1"); }; + item.left.action_callback = []() { wxGetApp().mainframe->select_tab(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + /* + if (!m_main_toolbar.add_separator()) + return false; + */ + + item.name = "search"; + item.icon_filename = "search_.svg"; + item.tooltip = _utf8(L("Search")) + " [" + GUI::shortkey_ctrl_prefix() + "F]"; + item.sprite_id = 11; + item.left.toggable = true; + item.left.render_callback = [this](float left, float right, float, float) { + if (m_canvas != nullptr) { + if (!m_canvas->HasFocus()) + m_canvas->SetFocus(); + if (_render_search_list(0.5f * (left + right))) + _deactivate_search_toolbar_item(); + } + }; + item.left.action_callback = GLToolbarItem::Default_Action_Callback; + item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; + item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; + if (!m_main_toolbar.add_item(item)) + return false; + + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "layersediting"; + item.icon_filename = "layers_white.svg"; + item.tooltip = _utf8(L("Variable layer height")); + item.sprite_id = 12; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.visibility_callback = [this]()->bool { + bool res = current_printer_technology() == ptFFF; + // turns off if changing printer technology + if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) + force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); + + return res; + }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; + item.left.render_callback = GLToolbarItem::Default_Render_Callback; + if (!m_main_toolbar.add_item(item)) + return false; + + return true; +} + +bool GLCanvas3D::_init_undoredo_toolbar() +{ + if (!m_undoredo_toolbar.is_enabled()) + return true; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!m_undoredo_toolbar.init(background_data)) { + // unable to init the toolbar texture, disable it + m_undoredo_toolbar.set_enabled(false); + return true; + } + + // init arrow +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!m_undoredo_toolbar.init_arrow("toolbar_arrow_2.svg")) +#else + BackgroundTexture::Metadata arrow_data; + arrow_data.filename = "toolbar_arrow.svg"; + arrow_data.left = 0; + arrow_data.top = 0; + arrow_data.right = 0; + arrow_data.bottom = 0; + if (!m_undoredo_toolbar.init_arrow(arrow_data)) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + BOOST_LOG_TRIVIAL(error) << "Undo/Redo toolbar failed to load arrow texture."; + +// m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); + m_undoredo_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); + m_undoredo_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + m_undoredo_toolbar.set_border(5.0f); + m_undoredo_toolbar.set_separator_size(5); + m_undoredo_toolbar.set_gap_size(4); + + GLToolbarItem::Data item; + + item.name = "undo"; + item.icon_filename = "undo_toolbar.svg"; + item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]\n" + _utf8(L("Click right mouse button to open/close History")); + item.sprite_id = 0; + item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; + item.right.toggable = true; + item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.right.render_callback = [this](float left, float right, float, float) { + if (m_canvas != nullptr) { + if (_render_undo_redo_stack(true, 0.5f * (left + right))) + _deactivate_undo_redo_toolbar_items(); + } + }; + item.enabling_callback = [this]()->bool { + bool can_undo = wxGetApp().plater()->can_undo(); + int id = m_undoredo_toolbar.get_item_id("undo"); + + std::string curr_additional_tooltip; + m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); + + std::string new_additional_tooltip; + if (can_undo) { + std::string action; + wxGetApp().plater()->undo_redo_topmost_string_getter(true, action); + new_additional_tooltip = (boost::format(_utf8(L("Next Undo action: %1%"))) % action).str(); + } + + if (new_additional_tooltip != curr_additional_tooltip) { + m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); + set_tooltip(""); + } + return can_undo; + }; + + if (!m_undoredo_toolbar.add_item(item)) + return false; + + item.name = "redo"; + item.icon_filename = "redo_toolbar.svg"; + item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]\n" + _utf8(L("Click right mouse button to open/close History")); + item.sprite_id = 1; + item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; + item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.right.render_callback = [this](float left, float right, float, float) { + if (m_canvas != nullptr) { + if (_render_undo_redo_stack(false, 0.5f * (left + right))) + _deactivate_undo_redo_toolbar_items(); + } + }; + item.enabling_callback = [this]()->bool { + bool can_redo = wxGetApp().plater()->can_redo(); + int id = m_undoredo_toolbar.get_item_id("redo"); + + std::string curr_additional_tooltip; + m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); + + std::string new_additional_tooltip; + if (can_redo) { + std::string action; + wxGetApp().plater()->undo_redo_topmost_string_getter(false, action); + new_additional_tooltip = (boost::format(_utf8(L("Next Redo action: %1%"))) % action).str(); + } + + if (new_additional_tooltip != curr_additional_tooltip) { + m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); + set_tooltip(""); + } + return can_redo; + }; + + if (!m_undoredo_toolbar.add_item(item)) + return false; + /* + if (!m_undoredo_toolbar.add_separator()) + return false; + */ + return true; +} + +bool GLCanvas3D::_init_view_toolbar() +{ + return wxGetApp().plater()->init_view_toolbar(); +} + +bool GLCanvas3D::_init_collapse_toolbar() +{ + return wxGetApp().plater()->init_collapse_toolbar(); +} + +bool GLCanvas3D::_set_current() +{ + return m_context != nullptr && m_canvas->SetCurrent(*m_context); +} + +void GLCanvas3D::_resize(unsigned int w, unsigned int h) +{ + if (m_canvas == nullptr && m_context == nullptr) + return; + + const std::array new_size = { w, h }; + if (m_old_size == new_size) + return; + + m_old_size = new_size; + + auto *imgui = wxGetApp().imgui(); + imgui->set_display_size(static_cast(w), static_cast(h)); + const float font_size = 1.5f * wxGetApp().em_unit(); +#if ENABLE_RETINA_GL + imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); +#else + imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f); +#endif + + this->request_extra_frame(); + + // ensures that this canvas is current + _set_current(); +} + +BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const +{ + BoundingBoxf3 bb = volumes_bounding_box(); + + // The following is a workaround for gizmos not being taken in account when calculating the tight camera frustrum + // A better solution would ask the gizmo manager for the bounding box of the current active gizmo, if any + if (include_gizmos && m_gizmos.is_running()) { + const BoundingBoxf3 sel_bb = m_selection.get_bounding_box(); + const Vec3d sel_bb_center = sel_bb.center(); + const Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones(); + bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by)); + } + + const BoundingBoxf3 bed_bb = include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume(); + bb.merge(bed_bb); + + if (!m_main_toolbar.is_enabled()) + bb.merge(m_gcode_viewer.get_max_bounding_box()); + + // clamp max bb size with respect to bed bb size + if (!m_picking_enabled) { + static const double max_scale_factor = 2.0; + const Vec3d bb_size = bb.size(); + const Vec3d bed_bb_size = m_bed.build_volume().bounding_volume().size(); + + if ((bed_bb_size.x() > 0.0 && bb_size.x() > max_scale_factor * bed_bb_size.x()) || + (bed_bb_size.y() > 0.0 && bb_size.y() > max_scale_factor * bed_bb_size.y()) || + (bed_bb_size.z() > 0.0 && bb_size.z() > max_scale_factor * bed_bb_size.z())) { + const Vec3d bed_bb_center = bed_bb.center(); + const Vec3d extend_by = max_scale_factor * bed_bb_size; + bb = BoundingBoxf3(bed_bb_center - extend_by, bed_bb_center + extend_by); + } + } + + return bb; +} + +void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor) +{ + wxGetApp().plater()->get_camera().zoom_to_box(box, margin_factor); + m_dirty = true; +} + +void GLCanvas3D::_update_camera_zoom(double zoom) +{ + wxGetApp().plater()->get_camera().update_zoom(zoom); + m_dirty = true; +} + +void GLCanvas3D::_refresh_if_shown_on_screen() +{ + if (_is_shown_on_screen()) { + const Size& cnv_size = get_canvas_size(); + _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + + // Because of performance problems on macOS, where PaintEvents are not delivered + // frequently enough, we call render() here directly when we can. + render(); + } +} + +#if ENABLE_RAYCAST_PICKING +void GLCanvas3D::_picking_pass() +{ + if (!m_picking_enabled || m_mouse.dragging || m_mouse.position == Vec2d(DBL_MAX, DBL_MAX) || m_gizmos.is_dragging()) { +#if ENABLE_RAYCAST_PICKING_DEBUG + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); + imgui.text("Picking disabled"); + imgui.end(); +#endif // ENABLE_RAYCAST_PICKING_DEBUG + return; + } + + m_hover_volume_idxs.clear(); + + const ClippingPlane clipping_plane = m_gizmos.get_clipping_plane().inverted_normal(); + const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(m_mouse.position, wxGetApp().plater()->get_camera(), &clipping_plane); + if (hit.is_valid()) { + switch (hit.type) + { + case SceneRaycaster::EType::Volume: + { + if (0 <= hit.raycaster_id && hit.raycaster_id < (int)m_volumes.volumes.size()) { + const GLVolume* volume = m_volumes.volumes[hit.raycaster_id]; + if (volume->is_active && !volume->disabled && (volume->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) { + // do not add the volume id if any gizmo is active and CTRL is pressed + if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) + m_hover_volume_idxs.emplace_back(hit.raycaster_id); + m_gizmos.set_hover_id(-1); + } + } + else + assert(false); + + break; + } + case SceneRaycaster::EType::Gizmo: + { + const Size& cnv_size = get_canvas_size(); + const bool inside = 0 <= m_mouse.position.x() && m_mouse.position.x() < cnv_size.get_width() && + 0 <= m_mouse.position.y() && m_mouse.position.y() < cnv_size.get_height(); + m_gizmos.set_hover_id(inside ? hit.raycaster_id : -1); + break; + } + case SceneRaycaster::EType::Bed: + { + m_gizmos.set_hover_id(-1); + break; + } + default: + { + assert(false); + break; + } + } + } + else + m_gizmos.set_hover_id(-1); + + _update_volumes_hover_state(); + +#if ENABLE_RAYCAST_PICKING_DEBUG + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); + std::string object_type = "None"; + switch (hit.type) + { + case SceneRaycaster::EType::Bed: { object_type = "Bed"; break; } + case SceneRaycaster::EType::Gizmo: { object_type = "Gizmo element"; break; } + case SceneRaycaster::EType::Volume: + { + if (m_volumes.volumes[hit.raycaster_id]->is_wipe_tower) + object_type = "Volume (Wipe tower)"; + else if (m_volumes.volumes[hit.raycaster_id]->volume_idx() == -int(slaposPad)) + object_type = "Volume (SLA pad)"; + else if (m_volumes.volumes[hit.raycaster_id]->volume_idx() == -int(slaposSupportTree)) + object_type = "Volume (SLA supports)"; + else if (m_volumes.volumes[hit.raycaster_id]->is_modifier) + object_type = "Volume (Modifier)"; + else + object_type = "Volume (Part)"; + break; + } + default: { break; } + } + + auto add_strings_row_to_table = [&imgui](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(col_1_color, col_1.c_str()); + ImGui::TableSetColumnIndex(1); + imgui.text_colored(col_2_color, col_2.c_str()); + }; + + char buf[1024]; + if (hit.type != SceneRaycaster::EType::None) { + if (ImGui::BeginTable("Hit", 2)) { + add_strings_row_to_table("Object ID", ImGuiWrapper::COL_ORANGE_LIGHT, std::to_string(hit.raycaster_id), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Type", ImGuiWrapper::COL_ORANGE_LIGHT, object_type, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%.3f, %.3f, %.3f", hit.position.x(), hit.position.y(), hit.position.z()); + add_strings_row_to_table("Position", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%.3f, %.3f, %.3f", hit.normal.x(), hit.normal.y(), hit.normal.z()); + add_strings_row_to_table("Normal", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ImGui::EndTable(); + } + } + else + imgui.text("NO HIT"); + + ImGui::Separator(); + imgui.text("Registered for picking:"); + if (ImGui::BeginTable("Raycasters", 2)) { + sprintf(buf, "%d (%d)", (int)m_scene_raycaster.beds_count(), (int)m_scene_raycaster.active_beds_count()); + add_strings_row_to_table("Beds", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%d (%d)", (int)m_scene_raycaster.volumes_count(), (int)m_scene_raycaster.active_volumes_count()); + add_strings_row_to_table("Volumes", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%d (%d)", (int)m_scene_raycaster.gizmos_count(), (int)m_scene_raycaster.active_gizmos_count()); + add_strings_row_to_table("Gizmo elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ImGui::EndTable(); + } + imgui.end(); +#endif // ENABLE_RAYCAST_PICKING_DEBUG +} +#else +void GLCanvas3D::_picking_pass() +{ + if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX) && !m_gizmos.is_dragging()) { + m_hover_volume_idxs.clear(); + + // Render the object for picking. + // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. + // Better to use software ray - casting on a bounding - box hierarchy. + + if (m_multisample_allowed) + // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. + glsafe(::glDisable(GL_MULTISAMPLE)); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + m_camera_clipping_plane = m_gizmos.get_clipping_plane(); + if (m_camera_clipping_plane.is_active()) { + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data().data()); + ::glEnable(GL_CLIP_PLANE0); + } +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + _render_volumes_for_picking(); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + if (m_camera_clipping_plane.is_active()) + ::glDisable(GL_CLIP_PLANE0); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Camera& camera = wxGetApp().plater()->get_camera(); + _render_bed_for_picking(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward()); +#else + _render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + m_gizmos.render_current_gizmo_for_picking_pass(); + + if (m_multisample_allowed) + glsafe(::glEnable(GL_MULTISAMPLE)); + + int volume_id = -1; + int gizmo_id = -1; + + std::array color = { 0, 0, 0, 0 }; + const Size& cnv_size = get_canvas_size(); + bool inside = 0 <= m_mouse.position(0) && m_mouse.position(0) < cnv_size.get_width() && 0 <= m_mouse.position(1) && m_mouse.position(1) < cnv_size.get_height(); + if (inside) { + glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position.y() - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color.data())); + if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3]) { + // Only non-interpolated colors are valid, those have their lowest three bits zeroed. + // we reserve color = (0,0,0) for occluders (as the printbed) + // volumes' id are shifted by 1 + // see: _render_volumes_for_picking() + unsigned int id = picking_encode(color[0], color[1], color[2]); + volume_id = id - 1; + // gizmos' id are instead properly encoded by the color + gizmo_id = id; + } + } + if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { + // do not add the volume id if any gizmo is active and CTRL is pressed + if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) + m_hover_volume_idxs.emplace_back(volume_id); + m_gizmos.set_hover_id(-1); + } + else + m_gizmos.set_hover_id(inside && (unsigned int)gizmo_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - gizmo_id) : -1); + + _update_volumes_hover_state(); + } +} +#endif // ENABLE_RAYCAST_PICKING + +void GLCanvas3D::_rectangular_selection_picking_pass() +{ + m_gizmos.set_hover_id(-1); + + std::set idxs; + + if (m_picking_enabled) { +#if ENABLE_RAYCAST_PICKING + const size_t width = std::max(m_rectangle_selection.get_width(), 1); + const size_t height = std::max(m_rectangle_selection.get_height(), 1); + + const OpenGLManager::EFramebufferType framebuffers_type = OpenGLManager::get_framebuffers_type(); + bool use_framebuffer = framebuffers_type != OpenGLManager::EFramebufferType::Unknown; + + GLuint render_fbo = 0; + GLuint render_tex = 0; + GLuint render_depth = 0; + if (use_framebuffer) { + // setup a framebuffer which covers only the selection rectangle + if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { + glsafe(::glGenFramebuffers(1, &render_fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)); + } + else { + glsafe(::glGenFramebuffersEXT(1, &render_fbo)); + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo)); + } + glsafe(::glGenTextures(1, &render_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); + if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); + glsafe(::glGenRenderbuffers(1, &render_depth)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); +#if ENABLE_OPENGL_ES + glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height)); +#else + glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height)); +#endif // ENABLE_OPENGL_ES + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); + } + else { + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0)); + glsafe(::glGenRenderbuffersEXT(1, &render_depth)); + glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth)); + glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height)); + glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth)); + } + const GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; + glsafe(::glDrawBuffers(1, drawBufs)); + if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + use_framebuffer = false; + } + else { + if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) + use_framebuffer = false; + } + } +#endif // ENABLE_RAYCAST_PICKING + + if (m_multisample_allowed) + // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. + glsafe(::glDisable(GL_MULTISAMPLE)); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + +#if ENABLE_RAYCAST_PICKING + const Camera& main_camera = wxGetApp().plater()->get_camera(); + Camera framebuffer_camera; + framebuffer_camera.set_type(main_camera.get_type()); + const Camera* camera = &main_camera; + if (use_framebuffer) { + // setup a camera which covers only the selection rectangle + const std::array& viewport = camera->get_viewport(); + const double near_left = camera->get_near_left(); + const double near_bottom = camera->get_near_bottom(); + const double near_width = camera->get_near_width(); + const double near_height = camera->get_near_height(); + + const double ratio_x = near_width / double(viewport[2]); + const double ratio_y = near_height / double(viewport[3]); + + const double rect_near_left = near_left + double(m_rectangle_selection.get_left()) * ratio_x; + const double rect_near_bottom = near_bottom + (double(viewport[3]) - double(m_rectangle_selection.get_bottom())) * ratio_y; + double rect_near_right = near_left + double(m_rectangle_selection.get_right()) * ratio_x; + double rect_near_top = near_bottom + (double(viewport[3]) - double(m_rectangle_selection.get_top())) * ratio_y; + + if (rect_near_left == rect_near_right) + rect_near_right = rect_near_left + ratio_x; + if (rect_near_bottom == rect_near_top) + rect_near_top = rect_near_bottom + ratio_y; + + framebuffer_camera.look_at(camera->get_position(), camera->get_target(), camera->get_dir_up()); + framebuffer_camera.apply_projection(rect_near_left, rect_near_right, rect_near_bottom, rect_near_top, camera->get_near_z(), camera->get_far_z()); + framebuffer_camera.set_viewport(0, 0, width, height); + framebuffer_camera.apply_viewport(); + camera = &framebuffer_camera; + } + + _render_volumes_for_picking(*camera); +#else + _render_volumes_for_picking(); +#endif // ENABLE_RAYCAST_PICKING +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_RAYCAST_PICKING + _render_bed_for_picking(camera->get_view_matrix(), camera->get_projection_matrix(), !camera->is_looking_downward()); +#else + const Camera& camera = wxGetApp().plater()->get_camera(); + _render_bed_for_picking(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward()); +#endif // ENABLE_RAYCAST_PICKING +#else + _render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + if (m_multisample_allowed) + glsafe(::glEnable(GL_MULTISAMPLE)); + +#if ENABLE_RAYCAST_PICKING + const size_t px_count = width * height; + + const size_t left = use_framebuffer ? 0 : (size_t)m_rectangle_selection.get_left(); + const size_t top = use_framebuffer ? 0 : (size_t)get_canvas_size().get_height() - (size_t)m_rectangle_selection.get_top(); +#else + int width = std::max((int)m_rectangle_selection.get_width(), 1); + int height = std::max((int)m_rectangle_selection.get_height(), 1); + int px_count = width * height; + + int left = (int)m_rectangle_selection.get_left(); + int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top(); + if (left >= 0 && top >= 0) { +#endif // ENABLE_RAYCAST_PICKING +#define USE_PARALLEL 1 +#if USE_PARALLEL + struct Pixel + { + std::array data; + // Only non-interpolated colors are valid, those have their lowest three bits zeroed. + bool valid() const { return picking_checksum_alpha_channel(data[0], data[1], data[2]) == data[3]; } + // we reserve color = (0,0,0) for occluders (as the printbed) + // volumes' id are shifted by 1 + // see: _render_volumes_for_picking() + int id() const { return data[0] + (data[1] << 8) + (data[2] << 16) - 1; } + }; + + std::vector frame(px_count); + glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); + + tbb::spin_mutex mutex; + tbb::parallel_for(tbb::blocked_range(0, frame.size(), (size_t)width), + [this, &frame, &idxs, &mutex](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); ++i) + if (frame[i].valid()) { + int volume_id = frame[i].id(); + if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { + mutex.lock(); + idxs.insert(volume_id); + mutex.unlock(); + } + } + }); +#else + std::vector frame(4 * px_count); + glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); + + for (int i = 0; i < px_count; ++i) + { + int px_id = 4 * i; + int volume_id = frame[px_id] + (frame[px_id + 1] << 8) + (frame[px_id + 2] << 16); + if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) + idxs.insert(volume_id); + } +#endif // USE_PARALLEL +#if ENABLE_RAYCAST_PICKING + if (camera != &main_camera) + main_camera.apply_viewport(); + + if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); + if (render_depth != 0) + glsafe(::glDeleteRenderbuffers(1, &render_depth)); + if (render_fbo != 0) + glsafe(::glDeleteFramebuffers(1, &render_fbo)); + } + else if (framebuffers_type == OpenGLManager::EFramebufferType::Ext) { + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); + if (render_depth != 0) + glsafe(::glDeleteRenderbuffersEXT(1, &render_depth)); + if (render_fbo != 0) + glsafe(::glDeleteFramebuffersEXT(1, &render_fbo)); + } + + if (render_tex != 0) + glsafe(::glDeleteTextures(1, &render_tex)); +#else + } +#endif // ENABLE_RAYCAST_PICKING + } + + m_hover_volume_idxs.assign(idxs.begin(), idxs.end()); + _update_volumes_hover_state(); +} + +void GLCanvas3D::_render_background() +{ + bool use_error_color = false; + if (wxGetApp().is_editor()) { + use_error_color = m_dynamic_background_enabled && + (current_printer_technology() != ptSLA || !m_volumes.empty()); + + if (!m_volumes.empty()) + use_error_color &= _is_any_volume_outside().first; + else + use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); + } + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); + glsafe(::glMatrixMode(GL_PROJECTION)); + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + // Draws a bottom to top gradient over the complete screen. + glsafe(::glDisable(GL_DEPTH_TEST)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const ColorRGBA bottom_color = use_error_color ? ERROR_BG_DARK_COLOR : DEFAULT_BG_DARK_COLOR; + + if (!m_background.is_initialized()) { + m_background.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec2f(-1.0f, -1.0f), Vec2f(0.0f, 0.0f)); + init_data.add_vertex(Vec2f(1.0f, -1.0f), Vec2f(1.0f, 0.0f)); + init_data.add_vertex(Vec2f(1.0f, 1.0f), Vec2f(1.0f, 1.0f)); + init_data.add_vertex(Vec2f(-1.0f, 1.0f), Vec2f(0.0f, 1.0f)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_background.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = wxGetApp().get_shader("background"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("top_color", use_error_color ? ERROR_BG_LIGHT_COLOR : DEFAULT_BG_LIGHT_COLOR); + shader->set_uniform("bottom_color", bottom_color); + m_background.render(); + shader->stop_using(); + } +#else + ::glBegin(GL_QUADS); + ::glColor3fv(use_error_color ? ERROR_BG_DARK_COLOR.data(): DEFAULT_BG_DARK_COLOR.data()); + ::glVertex2f(-1.0f, -1.0f); + ::glVertex2f(1.0f, -1.0f); + + ::glColor3fv(use_error_color ? ERROR_BG_LIGHT_COLOR.data() : DEFAULT_BG_LIGHT_COLOR.data()); + ::glVertex2f(1.0f, 1.0f); + ::glVertex2f(-1.0f, 1.0f); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_DEPTH_TEST)); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPopMatrix()); + glsafe(::glMatrixMode(GL_MODELVIEW)); + glsafe(::glPopMatrix()); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL +} + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes) +#else +void GLCanvas3D::_render_bed(bool bottom, bool show_axes) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +{ + float scale_factor = 1.0; +#if ENABLE_RETINA_GL + scale_factor = m_retina_helper->get_scale_factor(); +#endif // ENABLE_RETINA_GL + + bool show_texture = ! bottom || + (m_gizmos.get_current_type() != GLGizmosManager::FdmSupports + && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports + && m_gizmos.get_current_type() != GLGizmosManager::Hollow + && m_gizmos.get_current_type() != GLGizmosManager::Seam + && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes, show_texture); +#else + m_bed.render(*this, bottom, scale_factor, show_axes, show_texture); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void GLCanvas3D::_render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) +#else +void GLCanvas3D::_render_bed_for_picking(bool bottom) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +{ + float scale_factor = 1.0; +#if ENABLE_RETINA_GL + scale_factor = m_retina_helper->get_scale_factor(); +#endif // ENABLE_RETINA_GL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_bed.render_for_picking(*this, view_matrix, projection_matrix, bottom, scale_factor); +#else + m_bed.render_for_picking(*this, bottom, scale_factor); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) +{ + if (m_volumes.empty()) + return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + + m_camera_clipping_plane = m_gizmos.get_clipping_plane(); + + if (m_picking_enabled) + // Update the layer editing selection to the first object selected, update the current object maximum Z. + m_layers_editing.select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); + + if (const BuildVolume &build_volume = m_bed.build_volume(); build_volume.valid()) { + switch (build_volume.type()) { + case BuildVolume::Type::Rectangle: { + const BoundingBox3Base bed_bb = build_volume.bounding_volume().inflated(BuildVolume::SceneEpsilon); + m_volumes.set_print_volume({ 0, // circle + { float(bed_bb.min.x()), float(bed_bb.min.y()), float(bed_bb.max.x()), float(bed_bb.max.y()) }, + { 0.0f, float(build_volume.max_print_height()) } }); + break; + } + case BuildVolume::Type::Circle: { + m_volumes.set_print_volume({ 1, // rectangle + { unscaled(build_volume.circle().center.x()), unscaled(build_volume.circle().center.y()), unscaled(build_volume.circle().radius + BuildVolume::SceneEpsilon), 0.0f }, + { 0.0f, float(build_volume.max_print_height() + BuildVolume::SceneEpsilon) } }); + break; + } + default: + case BuildVolume::Type::Convex: + case BuildVolume::Type::Custom: { + m_volumes.set_print_volume({ static_cast(type), + { -FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX }, + { -FLT_MAX, FLT_MAX } } + ); + } + } + if (m_requires_check_outside_state) { + m_volumes.check_outside_state(build_volume, nullptr); + m_requires_check_outside_state = false; + } + } + + if (m_use_clipping_planes) + m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); + else + m_volumes.set_z_range(-FLT_MAX, FLT_MAX); + + m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); + m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); + m_volumes.set_show_non_manifold_edges(!m_gizmos.is_hiding_instances() && m_gizmos.get_current_type() != GLGizmosManager::Simplify); + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); + if (shader != nullptr) { + shader->start_using(); + + switch (type) + { + default: + case GLVolumeCollection::ERenderType::Opaque: + { + if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { + int object_id = m_layers_editing.last_object_id; +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Camera& camera = wxGetApp().plater()->get_camera(); + m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix(), [object_id](const GLVolume& volume) { + // Which volume to paint without the layer height profile shader? + return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); + }); +#else + m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { + // Which volume to paint without the layer height profile shader? + return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); + }); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + // Let LayersEditing handle rendering of the active object using the layer height profile shader. + m_layers_editing.render_volumes(*this, m_volumes); + } + else { + // do not cull backfaces to show broken geometry, if any +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Camera& camera = wxGetApp().plater()->get_camera(); + m_volumes.render(type, m_picking_enabled, camera.get_view_matrix(), camera.get_projection_matrix(), [this](const GLVolume& volume) { + return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); + }); +#else + m_volumes.render(type, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) { + return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); + }); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + // In case a painting gizmo is open, it should render the painted triangles + // before transparent objects are rendered. Otherwise they would not be + // visible when inside modifier meshes etc. + { + GLGizmosManager& gm = get_gizmos_manager(); +// GLGizmosManager::EType type = gm.get_current_type(); + if (dynamic_cast(gm.get_current())) { + shader->stop_using(); + gm.render_painter_gizmo(); + shader->start_using(); + } + } + break; + } + case GLVolumeCollection::ERenderType::Transparent: + { +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Camera& camera = wxGetApp().plater()->get_camera(); + m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix()); +#else + m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + break; + } + } + shader->stop_using(); + } + + m_camera_clipping_plane = ClippingPlane::ClipsNothing(); +} + +void GLCanvas3D::_render_gcode() +{ + m_gcode_viewer.render(); +} + +void GLCanvas3D::_render_gcode_cog() +{ + m_gcode_viewer.render_cog(); +} + +void GLCanvas3D::_render_selection() +{ + float scale_factor = 1.0; +#if ENABLE_RETINA_GL + scale_factor = m_retina_helper->get_scale_factor(); +#endif // ENABLE_RETINA_GL + + if (!m_gizmos.is_running()) + m_selection.render(scale_factor); +} + +void GLCanvas3D::_render_sequential_clearance() +{ + if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) + return; + + switch (m_gizmos.get_current_type()) + { + case GLGizmosManager::EType::Flatten: + case GLGizmosManager::EType::Cut: + case GLGizmosManager::EType::Hollow: + case GLGizmosManager::EType::SlaSupports: + case GLGizmosManager::EType::FdmSupports: + case GLGizmosManager::EType::Seam: { return; } + default: { break; } + } + + m_sequential_print_clearance.render(); +} + +#if ENABLE_RENDER_SELECTION_CENTER +void GLCanvas3D::_render_selection_center() +{ + m_selection.render_center(m_gizmos.is_dragging()); +} +#endif // ENABLE_RENDER_SELECTION_CENTER + +void GLCanvas3D::_check_and_update_toolbar_icon_scale() +{ + // Don't update a toolbar scale, when we are on a Preview + if (wxGetApp().plater()->is_preview_shown()) + return; + + float scale = wxGetApp().toolbar_icon_scale(); + Size cnv_size = get_canvas_size(); + + float size = GLToolbar::Default_Icons_Size * scale; + + // Set current size for all top toolbars. It will be used for next calculations + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); +#if ENABLE_RETINA_GL + const float sc = m_retina_helper->get_scale_factor() * scale; + m_main_toolbar.set_scale(sc); + m_undoredo_toolbar.set_scale(sc); + collapse_toolbar.set_scale(sc); + size *= m_retina_helper->get_scale_factor(); +#else + m_main_toolbar.set_icons_size(size); + m_undoredo_toolbar.set_icons_size(size); + collapse_toolbar.set_icons_size(size); +#endif // ENABLE_RETINA_GL + + float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); + int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt(); + float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars + + // calculate scale needed for items in all top toolbars + // the std::max() is there because on some Linux dialects/virtual machines this code is called when the canvas has not been properly initialized yet, + // leading to negative values for the scale. + // See: https://github.com/prusa3d/PrusaSlicer/issues/8563 + // https://github.com/supermerill/SuperSlicer/issues/854 + float new_h_scale = std::max((cnv_size.get_width() - noitems_width), 1.0f) / (items_cnt * GLToolbar::Default_Icons_Size); + + items_cnt = m_gizmos.get_selectable_icons_cnt() + 3; // +3 means a place for top and view toolbars and separators in gizmos toolbar + + // calculate scale needed for items in the gizmos toolbar + float new_v_scale = cnv_size.get_height() / (items_cnt * GLGizmosManager::Default_Icons_Size); + + // set minimum scale as a auto scale for the toolbars + float new_scale = std::min(new_h_scale, new_v_scale); +#if ENABLE_RETINA_GL + new_scale /= m_retina_helper->get_scale_factor(); +#endif + if (fabs(new_scale - scale) > 0.01) // scale is changed by 1% and more + wxGetApp().set_auto_toolbar_icon_scale(new_scale); +} + +void GLCanvas3D::_render_overlays() +{ + glsafe(::glDisable(GL_DEPTH_TEST)); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); + // ensure that the textures are renderered inside the frustrum + const Camera& camera = wxGetApp().plater()->get_camera(); + glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.005))); + // ensure that the overlay fits the frustrum near z plane + double gui_scale = camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + _check_and_update_toolbar_icon_scale(); + + _render_gizmos_overlay(); + + // main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed + // to correctly place them +#if ENABLE_RETINA_GL + const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); + m_main_toolbar.set_scale(scale); + m_undoredo_toolbar.set_scale(scale); + wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); +#else + const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); + m_main_toolbar.set_icons_size(size); + m_undoredo_toolbar.set_icons_size(size); + wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); +#endif // ENABLE_RETINA_GL + + _render_main_toolbar(); + _render_undoredo_toolbar(); + _render_collapse_toolbar(); + _render_view_toolbar(); + + if (m_layers_editing.last_object_id >= 0 && m_layers_editing.object_max_z() > 0.0f) + m_layers_editing.render_overlay(*this); + + const ConfigOptionBool* opt = dynamic_cast(m_config->option("complete_objects")); + bool sequential_print = opt != nullptr && opt->value; + std::vector sorted_instances; + if (sequential_print) { + for (ModelObject* model_object : m_model->objects) + for (ModelInstance* model_instance : model_object->instances) { + sorted_instances.emplace_back(model_instance); + } + } + m_labels.render(sorted_instances); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPopMatrix()); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL +} + +#if ENABLE_RAYCAST_PICKING +void GLCanvas3D::_render_volumes_for_picking(const Camera& camera) const +#else +void GLCanvas3D::_render_volumes_for_picking() const +#endif // ENABLE_RAYCAST_PICKING +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat_clip"); + if (shader == nullptr) + return; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // do not cull backfaces to show broken geometry, if any + glsafe(::glDisable(GL_CULL_FACE)); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_RAYCAST_PICKING + const Transform3d& view_matrix = camera.get_view_matrix(); +#else + const Transform3d& view_matrix = wxGetApp().plater()->get_camera().get_view_matrix(); +#endif // ENABLE_RAYCAST_PICKING + for (size_t type = 0; type < 2; ++ type) { + GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::ERenderType::Opaque : GLVolumeCollection::ERenderType::Transparent, view_matrix); + for (const GLVolumeWithIdAndZ& volume : to_render) + if (!volume.first->disabled && (volume.first->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) { + // Object picking mode. Render the object with a color encoding the object index. + // we reserve color = (0,0,0) for occluders (as the printbed) + // so we shift volumes' id by 1 to get the proper color + const unsigned int id = 1 + volume.second.first; +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume.first->model.set_color(picking_decode(id)); + shader->start_using(); +#if ENABLE_RAYCAST_PICKING + shader->set_uniform("view_model_matrix", view_matrix * volume.first->world_matrix()); +#else + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * volume.first->world_matrix()); +#endif // ENABLE_RAYCAST_PICKING + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("volume_world_matrix", volume.first->world_matrix()); + shader->set_uniform("z_range", m_volumes.get_z_range()); + shader->set_uniform("clipping_plane", m_volumes.get_clipping_plane()); +#else + glsafe(::glColor4fv(picking_decode(id).data())); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + volume.first->render(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_CULL_FACE)); +} + +void GLCanvas3D::_render_current_gizmo() const +{ + m_gizmos.render_current_gizmo(); +} + +void GLCanvas3D::_render_gizmos_overlay() +{ +#if ENABLE_RETINA_GL +// m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor()); + const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); + m_gizmos.set_overlay_scale(scale); //! #ys_FIXME_experiment +#else +// m_gizmos.set_overlay_scale(m_canvas->GetContentScaleFactor()); +// m_gizmos.set_overlay_scale(wxGetApp().em_unit()*0.1f); + const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); + m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment +#endif /* __WXMSW__ */ + + m_gizmos.render_overlay(); + + if (m_gizmo_highlighter.m_render_arrow) + m_gizmos.render_arrow(*this, m_gizmo_highlighter.m_gizmo_type); +} + +void GLCanvas3D::_render_main_toolbar() +{ + if (!m_main_toolbar.is_enabled()) + return; + + const Size cnv_size = get_canvas_size(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + const float top = 0.5f * (float)cnv_size.get_height(); +#else + const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; +#if ENABLE_LEGACY_OPENGL_REMOVAL + const float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width); +#else + const float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + m_main_toolbar.set_position(top, left); + m_main_toolbar.render(*this); + if (m_toolbar_highlighter.m_render_arrow) + m_main_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); +} + +void GLCanvas3D::_render_undoredo_toolbar() +{ + if (!m_undoredo_toolbar.is_enabled()) + return; + + const Size cnv_size = get_canvas_size(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + const float top = 0.5f * (float)cnv_size.get_height(); +#else + float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + + const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; +#if ENABLE_LEGACY_OPENGL_REMOVAL + const float left = m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width); +#else + const float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + m_undoredo_toolbar.set_position(top, left); + m_undoredo_toolbar.render(*this); + if (m_toolbar_highlighter.m_render_arrow) + m_undoredo_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); +} + +void GLCanvas3D::_render_collapse_toolbar() const +{ + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + + const Size cnv_size = get_canvas_size(); + const float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0; +#if ENABLE_LEGACY_OPENGL_REMOVAL + const float top = 0.5f * (float)cnv_size.get_height(); + const float left = 0.5f * (float)cnv_size.get_width() - collapse_toolbar.get_width() - band; +#else + const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + + const float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + const float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + collapse_toolbar.set_position(top, left); + collapse_toolbar.render(*this); +} + +void GLCanvas3D::_render_view_toolbar() const +{ + GLToolbar& view_toolbar = wxGetApp().plater()->get_view_toolbar(); + +#if ENABLE_RETINA_GL + const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(); +#if __APPLE__ + view_toolbar.set_scale(scale); +#else // if GTK3 + const float size = int(GLGizmosManager::Default_Icons_Size * scale); + view_toolbar.set_icons_size(size); +#endif // __APPLE__ +#else + const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); + view_toolbar.set_icons_size(size); +#endif // ENABLE_RETINA_GL + + const Size cnv_size = get_canvas_size(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + // places the toolbar on the bottom-left corner of the 3d scene + const float top = -0.5f * (float)cnv_size.get_height() + view_toolbar.get_height(); + const float left = -0.5f * (float)cnv_size.get_width(); +#else + float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + + // places the toolbar on the bottom-left corner of the 3d scene + float top = (-0.5f * (float)cnv_size.get_height() + view_toolbar.get_height()) * inv_zoom; + float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + view_toolbar.set_position(top, left); + view_toolbar.render(*this); +} + +#if ENABLE_SHOW_CAMERA_TARGET +void GLCanvas3D::_render_camera_target() +{ + static const float half_length = 5.0f; + + glsafe(::glDisable(GL_DEPTH_TEST)); +#if ENABLE_GL_CORE_PROFILE + if (!OpenGLManager::get_gl_info().is_core_profile()) +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(2.0f)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Vec3f& target = wxGetApp().plater()->get_camera().get_target().cast(); + m_camera_target.target = target.cast(); + + for (int i = 0; i < 3; ++i) { + if (!m_camera_target.axis[i].is_initialized()) { + m_camera_target.axis[i].reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = (i == X) ? ColorRGBA::X() : ((i == Y) ? ColorRGBA::Y() : ColorRGBA::Z()); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + if (i == X) { + init_data.add_vertex(Vec3f(-half_length, 0.0f, 0.0f)); + init_data.add_vertex(Vec3f(+half_length, 0.0f, 0.0f)); + } + else if (i == Y) { + init_data.add_vertex(Vec3f(0.0f, -half_length, 0.0f)); + init_data.add_vertex(Vec3f(0.0f, +half_length, 0.0f)); + } + else { + init_data.add_vertex(Vec3f(0.0f, 0.0f, -half_length)); + init_data.add_vertex(Vec3f(0.0f, 0.0f, +half_length)); + } + + // indices + init_data.add_line(0, 1); + + m_camera_target.axis[i].init_from(std::move(init_data)); + } + } + +#if ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_CORE_PROFILE + if (shader != nullptr) { + shader->start_using(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(m_camera_target.target)); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#if ENABLE_GL_CORE_PROFILE + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.5f); + shader->set_uniform("gap_size", 0.0f); +#endif // ENABLE_GL_CORE_PROFILE +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + for (int i = 0; i < 3; ++i) { + m_camera_target.axis[i].render(); + } + shader->stop_using(); + } +#else + ::glBegin(GL_LINES); + const Vec3d& target = wxGetApp().plater()->get_camera().get_target(); + // draw line for x axis + ::glColor3f(1.0f, 0.0f, 0.0f); + ::glVertex3d(target.x() - half_length, target.y(), target.z()); + ::glVertex3d(target.x() + half_length, target.y(), target.z()); + // draw line for y axis + ::glColor3f(0.0f, 1.0f, 0.0f); + ::glVertex3d(target.x(), target.y() - half_length, target.z()); + ::glVertex3d(target.x(), target.y() + half_length, target.z()); + // draw line for z axis + ::glColor3f(0.0f, 0.0f, 1.0f); + ::glVertex3d(target.x(), target.y(), target.z() - half_length); + ::glVertex3d(target.x(), target.y(), target.z() + half_length); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} +#endif // ENABLE_SHOW_CAMERA_TARGET + +void GLCanvas3D::_render_sla_slices() +{ + if (!m_use_clipping_planes || current_printer_technology() != ptSLA) + return; + + const SLAPrint* print = this->sla_print(); + const PrintObjects& print_objects = print->objects(); + if (print_objects.empty()) + // nothing to render, return + return; + + double clip_min_z = -m_clipping_planes[0].get_data()[3]; + double clip_max_z = m_clipping_planes[1].get_data()[3]; + for (unsigned int i = 0; i < (unsigned int)print_objects.size(); ++i) { + const SLAPrintObject* obj = print_objects[i]; + + if (!obj->is_step_done(slaposSliceSupports)) + continue; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + SlaCap::ObjectIdToModelsMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); + SlaCap::ObjectIdToModelsMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); +#else + SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); + SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + { + if (it_caps_bottom == m_sla_caps[0].triangles.end()) + it_caps_bottom = m_sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; + if (!m_sla_caps[0].matches(clip_min_z)) { + m_sla_caps[0].z = clip_min_z; +#if ENABLE_LEGACY_OPENGL_REMOVAL + it_caps_bottom->second.object.reset(); + it_caps_bottom->second.supports.reset(); +#else + it_caps_bottom->second.object.clear(); + it_caps_bottom->second.supports.clear(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + if (it_caps_top == m_sla_caps[1].triangles.end()) + it_caps_top = m_sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; + if (!m_sla_caps[1].matches(clip_max_z)) { + m_sla_caps[1].z = clip_max_z; +#if ENABLE_LEGACY_OPENGL_REMOVAL + it_caps_top->second.object.reset(); + it_caps_top->second.supports.reset(); +#else + it_caps_top->second.object.clear(); + it_caps_top->second.supports.clear(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLModel& bottom_obj_triangles = it_caps_bottom->second.object; + GLModel& bottom_sup_triangles = it_caps_bottom->second.supports; + GLModel& top_obj_triangles = it_caps_top->second.object; + GLModel& top_sup_triangles = it_caps_top->second.supports; +#else + Pointf3s &bottom_obj_triangles = it_caps_bottom->second.object; + Pointf3s &bottom_sup_triangles = it_caps_bottom->second.supports; + Pointf3s &top_obj_triangles = it_caps_top->second.object; + Pointf3s &top_sup_triangles = it_caps_top->second.supports; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + auto init_model = [](GLModel& model, const Pointf3s& triangles, const ColorRGBA& color) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(triangles.size()); + init_data.reserve_indices(triangles.size() / 3); + init_data.color = color; + + unsigned int vertices_count = 0; + for (const Vec3d& v : triangles) { + init_data.add_vertex((Vec3f)v.cast()); + ++vertices_count; + if (vertices_count % 3 == 0) + init_data.add_triangle(vertices_count - 3, vertices_count - 2, vertices_count - 1); + } + + if (!init_data.is_empty()) + model.init_from(std::move(init_data)); + }; + + if ((!bottom_obj_triangles.is_initialized() || !bottom_sup_triangles.is_initialized() || + !top_obj_triangles.is_initialized() || !top_sup_triangles.is_initialized()) && !obj->get_slice_index().empty()) { +#else + if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && + !obj->get_slice_index().empty()) { +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + double layer_height = print->default_object_config().layer_height.value; + double initial_layer_height = print->material_config().initial_layer_height.value; + bool left_handed = obj->is_left_handed(); + + coord_t key_zero = obj->get_slice_index().front().print_level(); + // Slice at the center of the slab starting at clip_min_z will be rendered for the lower plane. + coord_t key_low = coord_t((clip_min_z - initial_layer_height + layer_height) / SCALING_FACTOR) + key_zero; + // Slice at the center of the slab ending at clip_max_z will be rendered for the upper plane. + coord_t key_high = coord_t((clip_max_z - initial_layer_height) / SCALING_FACTOR) + key_zero; + + const SliceRecord& slice_low = obj->closest_slice_to_print_level(key_low, coord_t(SCALED_EPSILON)); + const SliceRecord& slice_high = obj->closest_slice_to_print_level(key_high, coord_t(SCALED_EPSILON)); + + // Offset to avoid OpenGL Z fighting between the object's horizontal surfaces and the triangluated surfaces of the cuts. + const double plane_shift_z = 0.002; + + if (slice_low.is_valid()) { + const ExPolygons& obj_bottom = slice_low.get_slice(soModel); + const ExPolygons& sup_bottom = slice_low.get_slice(soSupport); +#if ENABLE_LEGACY_OPENGL_REMOVAL + // calculate model bottom cap + if (!bottom_obj_triangles.is_initialized() && !obj_bottom.empty()) + init_model(bottom_obj_triangles, triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); + // calculate support bottom cap + if (!bottom_sup_triangles.is_initialized() && !sup_bottom.empty()) + init_model(bottom_sup_triangles, triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); +#else + // calculate model bottom cap + if (bottom_obj_triangles.empty() && !obj_bottom.empty()) + bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, ! left_handed); + // calculate support bottom cap + if (bottom_sup_triangles.empty() && !sup_bottom.empty()) + bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + if (slice_high.is_valid()) { + const ExPolygons& obj_top = slice_high.get_slice(soModel); + const ExPolygons& sup_top = slice_high.get_slice(soSupport); +#if ENABLE_LEGACY_OPENGL_REMOVAL + // calculate model top cap + if (!top_obj_triangles.is_initialized() && !obj_top.empty()) + init_model(top_obj_triangles, triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); + // calculate support top cap + if (!top_sup_triangles.is_initialized() && !sup_top.empty()) + init_model(top_sup_triangles, triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); +#else + // calculate model top cap + if (top_obj_triangles.empty() && !obj_top.empty()) + top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed); + // calculate support top cap + if (top_sup_triangles.empty() && !sup_top.empty()) + top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + for (const SLAPrintObject::Instance& inst : obj->instances()) { + const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_WORLD_COORDINATE + Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::translation_transform({ unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0 }) * + Geometry::rotation_transform(inst.rotation * Vec3d::UnitZ()); + if (obj->is_left_handed()) + view_model_matrix = view_model_matrix * Geometry::scale_transform({ -1.0f, 1.0f, 1.0f }); +#else + const Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::assemble_transform(Vec3d(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0), + inst.rotation * Vec3d::UnitZ(), Vec3d::Ones(), + obj->is_left_handed() ? /* The polygons are mirrored by X */ Vec3d(-1.0f, 1.0f, 1.0f) : Vec3d::Ones()); +#endif // ENABLE_WORLD_COORDINATE + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + bottom_obj_triangles.render(); + top_obj_triangles.render(); + bottom_sup_triangles.render(); + top_sup_triangles.render(); + } + + shader->stop_using(); + } +#else + if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) { + for (const SLAPrintObject::Instance& inst : obj->instances()) { + glsafe(::glPushMatrix()); + glsafe(::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0)); + glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0f, 0.0f, 1.0f)); + if (obj->is_left_handed()) + // The polygons are mirrored by X. + glsafe(::glScalef(-1.0f, 1.0f, 1.0f)); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); + if (!bottom_obj_triangles.empty()) { + glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_obj_triangles.front().data())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_obj_triangles.size())); + } + if (! top_obj_triangles.empty()) { + glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_obj_triangles.front().data())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_obj_triangles.size())); + } + glsafe(::glColor3f(1.0f, 0.0f, 0.37f)); + if (! bottom_sup_triangles.empty()) { + glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_sup_triangles.front().data())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_sup_triangles.size())); + } + if (! top_sup_triangles.empty()) { + glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_sup_triangles.front().data())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_sup_triangles.size())); + } + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glPopMatrix()); + } + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } +} + +void GLCanvas3D::_render_selection_sidebar_hints() +{ + m_selection.render_sidebar_hints(m_sidebar_field); +} + +void GLCanvas3D::_update_volumes_hover_state() +{ + // skip update if the Gizmo Measure is active + if (m_gizmos.get_current_type() == GLGizmosManager::Measure) + return; + + for (GLVolume* v : m_volumes.volumes) { + v->hover = GLVolume::HS_None; + } + + if (m_hover_volume_idxs.empty()) + return; + + const bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); + const bool shift_pressed = wxGetKeyState(WXK_SHIFT); + const bool alt_pressed = wxGetKeyState(WXK_ALT); + + if (alt_pressed && (shift_pressed || ctrl_pressed)) { + // illegal combinations of keys + m_hover_volume_idxs.clear(); + return; + } + + bool hover_modifiers_only = true; + for (int i : m_hover_volume_idxs) { + if (!m_volumes.volumes[i]->is_modifier) { + hover_modifiers_only = false; + break; + } + } + + std::set> hover_instances; + for (int i : m_hover_volume_idxs) { + const GLVolume& v = *m_volumes.volumes[i]; + hover_instances.insert(std::make_pair(v.object_idx(), v.instance_idx())); + } + + bool hover_from_single_instance = hover_instances.size() == 1; + + if (hover_modifiers_only && !hover_from_single_instance) { + // do not allow to select volumes from different instances + m_hover_volume_idxs.clear(); + return; + } + + for (int i : m_hover_volume_idxs) { + GLVolume& volume = *m_volumes.volumes[i]; + if (volume.hover != GLVolume::HS_None) + continue; + + const bool deselect = volume.selected && ((shift_pressed && m_rectangle_selection.is_empty()) || (alt_pressed && !m_rectangle_selection.is_empty())); + const bool select = !volume.selected && (m_rectangle_selection.is_empty() || (shift_pressed && !m_rectangle_selection.is_empty())); + + if (select || deselect) { + const bool as_volume = + volume.is_modifier && hover_from_single_instance && !ctrl_pressed && + ( + !deselect || + (deselect && !m_selection.is_single_full_instance() && volume.object_idx() == m_selection.get_object_idx() && volume.instance_idx() == m_selection.get_instance_idx()) + ); + + if (as_volume) + volume.hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; + else { + const int object_idx = volume.object_idx(); + const int instance_idx = volume.instance_idx(); + + for (GLVolume* v : m_volumes.volumes) { + if (v->object_idx() == object_idx && v->instance_idx() == instance_idx) + v->hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select; + } + } + } + else if (volume.selected) + volume.hover = GLVolume::HS_Hover; + } +} + +void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt) +{ + int object_idx_selected = m_layers_editing.last_object_id; + if (object_idx_selected == -1) + return; + + // A volume is selected. Test, whether hovering over a layer thickness bar. + if (evt != nullptr) { + const Rect& rect = LayersEditing::get_bar_rect_screen(*this); + float b = rect.get_bottom(); + m_layers_editing.last_z = m_layers_editing.object_max_z() * (b - evt->GetY() - 1.0f) / (b - rect.get_top()); + m_layers_editing.last_action = + evt->ShiftDown() ? (evt->RightIsDown() ? LAYER_HEIGHT_EDIT_ACTION_SMOOTH : LAYER_HEIGHT_EDIT_ACTION_REDUCE) : + (evt->RightIsDown() ? LAYER_HEIGHT_EDIT_ACTION_INCREASE : LAYER_HEIGHT_EDIT_ACTION_DECREASE); + } + + m_layers_editing.adjust_layer_height_profile(); + _refresh_if_shown_on_screen(); + + // Automatic action on mouse down with the same coordinate. + _start_timer(); +} + +Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z) +{ + if (m_canvas == nullptr) + return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); + +#if ENABLE_RAYCAST_PICKING + if (z == nullptr) { + const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(mouse_pos.cast(), wxGetApp().plater()->get_camera(), nullptr); + return hit.is_valid() ? hit.position.cast() : _mouse_to_bed_3d(mouse_pos); + } + else { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Vec4i viewport(camera.get_viewport().data()); + Vec3d out; + igl::unproject(Vec3d(mouse_pos.x(), viewport[3] - mouse_pos.y(), *z), camera.get_view_matrix().matrix(), camera.get_projection_matrix().matrix(), viewport, out); + return out; + } +#else + const Camera& camera = wxGetApp().plater()->get_camera(); + const Matrix4d modelview = camera.get_view_matrix().matrix(); + const Matrix4d projection = camera.get_projection_matrix().matrix(); + const Vec4i viewport(camera.get_viewport().data()); + + const int y = viewport[3] - mouse_pos.y(); + float mouse_z; + if (z == nullptr) + glsafe(::glReadPixels(mouse_pos.x(), y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z)); + else + mouse_z = *z; + + Vec3d out; + igl::unproject(Vec3d(mouse_pos.x(), y, mouse_z), modelview, projection, viewport, out); + return out; +#endif // ENABLE_RAYCAST_PICKING +} + +Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos) +{ + return mouse_ray(mouse_pos).intersect_plane(0.0); +} + +void GLCanvas3D::_start_timer() +{ + m_timer.Start(100, wxTIMER_CONTINUOUS); +} + +void GLCanvas3D::_stop_timer() +{ + m_timer.Stop(); +} + +void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) +{ + const Print *print = this->fff_print(); + if (print == nullptr) + return; + + if (! print->is_step_done(psSkirtBrim)) + return; + + if (!print->has_skirt() && !print->has_brim()) + return; + + const ColorRGBA color = ColorRGBA::GREENISH(); + + // number of skirt layers + size_t total_layer_count = 0; + for (const PrintObject* print_object : print->objects()) { + total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); + } + size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min(print->config().skirt_height.value, total_layer_count); + if (skirt_height == 0 && print->has_brim()) + skirt_height = 1; + + // Get first skirt_height layers. + //FIXME This code is fishy. It may not work for multiple objects with different layering due to variable layer height feature. + // This is not critical as this is just an initial preview. + const PrintObject* highest_object = *std::max_element(print->objects().begin(), print->objects().end(), [](auto l, auto r){ return l->layers().size() < r->layers().size(); }); + std::vector print_zs; + print_zs.reserve(skirt_height * 2); + for (size_t i = 0; i < std::min(skirt_height, highest_object->layers().size()); ++ i) + print_zs.emplace_back(float(highest_object->layers()[i]->print_z)); + // Only add skirt for the raft layers. + for (size_t i = 0; i < std::min(skirt_height, std::min(highest_object->slicing_parameters().raft_layers(), highest_object->support_layers().size())); ++ i) + print_zs.emplace_back(float(highest_object->support_layers()[i]->print_z)); + sort_remove_duplicates(print_zs); + skirt_height = std::min(skirt_height, print_zs.size()); + print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLVolume* volume = m_volumes.new_toolpath_volume(color); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; +#else + GLVolume *volume = m_volumes.new_toolpath_volume(color, VERTEX_BUFFER_RESERVE_SIZE); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t i = 0; i < skirt_height; ++ i) { + volume->print_zs.emplace_back(print_zs[i]); +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume->offsets.emplace_back(init_data.indices_count()); + if (i == 0) + _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), init_data); + _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), init_data); +#else + volume->offsets.emplace_back(volume->indexed_vertex_array.quad_indices.size()); + volume->offsets.emplace_back(volume->indexed_vertex_array.triangle_indices.size()); + if (i == 0) + _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), *volume); + _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), *volume); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (init_data.vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + volume->model.init_from(std::move(init_data)); +#else + if (volume->indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + GLVolume &vol = *volume; + volume = m_volumes.new_toolpath_volume(vol.color); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + } + } +#if ENABLE_LEGACY_OPENGL_REMOVAL + volume->model.init_from(std::move(init_data)); + volume->is_outside = !contains(build_volume, volume->model); +#else + volume->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(volume->indexed_vertex_array.vertices_and_normals_interleaved, volume->indexed_vertex_array.bounding_box()); + volume->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) +{ + std::vector tool_colors; + decode_colors(str_tool_colors, tool_colors); + + struct Ctxt + { + const PrintInstances *shifted_copies; + std::vector layers; + bool has_perimeters; + bool has_infill; + bool has_support; + const std::vector* tool_colors; + bool is_single_material_print; + int extruders_cnt; + const std::vector* color_print_values; + + static ColorRGBA color_perimeters() { return ColorRGBA::YELLOW(); } + static ColorRGBA color_infill() { return ColorRGBA::REDISH(); } + static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } + static ColorRGBA color_pause_or_custom_code() { return ColorRGBA::GRAY(); } + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return color_by_tool() ? tool_colors->size() : 0; } + const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } + + // For coloring by a color_print(M600), return a parsed color. + bool color_by_color_print() const { return color_print_values!=nullptr; } + const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const { + const CustomGCode::Item value{layers[layer_idx]->print_z + EPSILON, CustomGCode::Custom, 0, ""}; + auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); + return (it - color_print_values->begin()) % number_tools(); + } + + const size_t color_print_color_idx_by_layer_idx_and_extruder(const size_t layer_idx, const int extruder) const + { + const coordf_t print_z = layers[layer_idx]->print_z; + + auto it = std::find_if(color_print_values->begin(), color_print_values->end(), + [print_z](const CustomGCode::Item& code) + { return fabs(code.print_z - print_z) < EPSILON; }); + if (it != color_print_values->end()) { + CustomGCode::Type type = it->type; + // pause print or custom Gcode + if (type == CustomGCode::PausePrint || + (type != CustomGCode::ColorChange && type != CustomGCode::ToolChange)) + return number_tools()-1; // last color item is a gray color for pause print or custom G-code + + // change tool (extruder) + if (type == CustomGCode::ToolChange) + return get_color_idx_for_tool_change(it, extruder); + // change color for current extruder + if (type == CustomGCode::ColorChange) { + int color_idx = get_color_idx_for_color_change(it, extruder); + if (color_idx >= 0) + return color_idx; + } + } + + const CustomGCode::Item value{print_z + EPSILON, CustomGCode::Custom, 0, ""}; + it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); + while (it != color_print_values->begin()) { + --it; + // change color for current extruder + if (it->type == CustomGCode::ColorChange) { + int color_idx = get_color_idx_for_color_change(it, extruder); + if (color_idx >= 0) + return color_idx; + } + // change tool (extruder) + if (it->type == CustomGCode::ToolChange) + return get_color_idx_for_tool_change(it, extruder); + } + + return std::min(extruders_cnt - 1, std::max(extruder - 1, 0));; + } + + private: + int get_m600_color_idx(std::vector::const_iterator it) const + { + int shift = 0; + while (it != color_print_values->begin()) { + --it; + if (it->type == CustomGCode::ColorChange) + shift++; + } + return extruders_cnt + shift; + } + + int get_color_idx_for_tool_change(std::vector::const_iterator it, const int extruder) const + { + const int current_extruder = it->extruder == 0 ? extruder : it->extruder; + if (number_tools() == size_t(extruders_cnt + 1)) // there is no one "M600" + return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); + + auto it_n = it; + while (it_n != color_print_values->begin()) { + --it_n; + if (it_n->type == CustomGCode::ColorChange && it_n->extruder == current_extruder) + return get_m600_color_idx(it_n); + } + + return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); + } + + int get_color_idx_for_color_change(std::vector::const_iterator it, const int extruder) const + { + if (extruders_cnt == 1) + return get_m600_color_idx(it); + + auto it_n = it; + bool is_tool_change = false; + while (it_n != color_print_values->begin()) { + --it_n; + if (it_n->type == CustomGCode::ToolChange) { + is_tool_change = true; + if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder)) + return get_m600_color_idx(it); + break; + } + } + if (!is_tool_change && it->extruder == extruder) + return get_m600_color_idx(it); + + return -1; + } + + } ctxt; + + ctxt.has_perimeters = print_object.is_step_done(posPerimeters); + ctxt.has_infill = print_object.is_step_done(posInfill); + ctxt.has_support = print_object.is_step_done(posSupportMaterial); + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + ctxt.color_print_values = color_print_values.empty() ? nullptr : &color_print_values; + ctxt.is_single_material_print = this->fff_print()->extruders().size()==1; + ctxt.extruders_cnt = wxGetApp().extruders_edited_cnt(); + + ctxt.shifted_copies = &print_object.instances(); + + // order layers by print_z + { + size_t nlayers = 0; + if (ctxt.has_perimeters || ctxt.has_infill) + nlayers = print_object.layers().size(); + if (ctxt.has_support) + nlayers += print_object.support_layers().size(); + ctxt.layers.reserve(nlayers); + } + if (ctxt.has_perimeters || ctxt.has_infill) + for (const Layer *layer : print_object.layers()) + ctxt.layers.emplace_back(layer); + if (ctxt.has_support) + for (const Layer *layer : print_object.support_layers()) + ctxt.layers.emplace_back(layer); + std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); + + // Maximum size of an allocation block: 32MB / sizeof(float) + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); + + const bool is_selected_separate_extruder = m_selected_extruder > 0 && ctxt.color_by_color_print(); + + //FIXME Improve the heuristics for a grain size. + size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { + // Allocate the volume before locking. + GLVolume *volume = new GLVolume(color); + volume->is_extrusion_path = true; +#if ENABLE_LEGACY_OPENGL_REMOVAL + // to prevent sending data to gpu (in the main thread) while + // editing the model geometry + volume->model.disable_render(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + tbb::spin_mutex::scoped_lock lock; + // Lock by ROII, so if the emplace_back() fails, the lock will be released. + lock.acquire(new_volume_mutex); + m_volumes.volumes.emplace_back(volume); + lock.release(); + return volume; + }; + const size_t volumes_cnt_initial = m_volumes.volumes.size(); + // Limit the number of threads as the code below does not scale well due to memory pressure. + // (most of the time is spent in malloc / free / memmove) + // Not using all the threads leaves some of the threads to G-code generator. + tbb::task_arena limited_arena(std::min(tbb::this_task_arena::max_concurrency(), 4)); + limited_arena.execute([&ctxt, grain_size, &new_volume, is_selected_separate_extruder, this]{ + tbb::parallel_for( + tbb::blocked_range(0, ctxt.layers.size(), grain_size), + [&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range& range) { + GLVolumePtrs vols; +#if ENABLE_LEGACY_OPENGL_REMOVAL + std::vector geometries; + auto select_geometry = [&ctxt, &geometries](size_t layer_idx, int extruder, int feature) -> GLModel::Geometry& { + return geometries[ctxt.color_by_color_print() ? + ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : + ctxt.color_by_tool() ? + std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : + feature + ]; + }; +#else + auto volume = [&ctxt, &vols](size_t layer_idx, int extruder, int feature) -> GLVolume& { + return *vols[ctxt.color_by_color_print() ? + ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : + ctxt.color_by_tool() ? + std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : + feature + ]; + }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (ctxt.color_by_color_print() || ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++i) { + vols.emplace_back(new_volume(ctxt.color_tool(i))); +#if ENABLE_LEGACY_OPENGL_REMOVAL + geometries.emplace_back(GLModel::Geometry()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + else { + vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; +#if ENABLE_LEGACY_OPENGL_REMOVAL + geometries = { GLModel::Geometry(), GLModel::Geometry(), GLModel::Geometry() }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + assert(vols.size() == geometries.size()); + for (GLModel::Geometry& g : geometries) { + g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + } +#else + for (GLVolume *vol : vols) + // Reserving number of vertices (3x position + 3x color) + vol->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + const Layer *layer = ctxt.layers[idx_layer]; + + if (is_selected_separate_extruder) { + bool at_least_one_has_correct_extruder = false; + for (const LayerRegion* layerm : layer->regions()) { + if (layerm->slices().empty()) + continue; + const PrintRegionConfig& cfg = layerm->region().config(); + if (cfg.perimeter_extruder.value == m_selected_extruder || + cfg.infill_extruder.value == m_selected_extruder || + cfg.solid_infill_extruder.value == m_selected_extruder ) { + at_least_one_has_correct_extruder = true; + break; + } + } + if (!at_least_one_has_correct_extruder) + continue; + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume* vol = vols[i]; + if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { + vol->print_zs.emplace_back(layer->print_z); + vol->offsets.emplace_back(geometries[i].indices_count()); + } + } +#else + for (GLVolume* vol : vols) + if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { + vol->print_zs.emplace_back(layer->print_z); + vol->offsets.emplace_back(vol->indexed_vertex_array.quad_indices.size()); + vol->offsets.emplace_back(vol->indexed_vertex_array.triangle_indices.size()); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + for (const PrintInstance &instance : *ctxt.shifted_copies) { + const Point © = instance.shift; + for (const LayerRegion *layerm : layer->regions()) { + if (is_selected_separate_extruder) { + const PrintRegionConfig& cfg = layerm->region().config(); + if (cfg.perimeter_extruder.value != m_selected_extruder || + cfg.infill_extruder.value != m_selected_extruder || + cfg.solid_infill_extruder.value != m_selected_extruder) + continue; + } + if (ctxt.has_perimeters) +#if ENABLE_LEGACY_OPENGL_REMOVAL + _3DScene::extrusionentity_to_verts(layerm->perimeters(), float(layer->print_z), copy, + select_geometry(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); +#else + _3DScene::extrusionentity_to_verts(layerm->perimeters(), float(layer->print_z), copy, + volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (ctxt.has_infill) { + for (const ExtrusionEntity *ee : layerm->fills()) { + // fill represents infill extrusions of a single island. + const auto *fill = dynamic_cast(ee); + if (! fill->entities.empty()) +#if ENABLE_LEGACY_OPENGL_REMOVAL + _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, + select_geometry(idx_layer, is_solid_infill(fill->entities.front()->role()) ? + layerm->region().config().solid_infill_extruder : + layerm->region().config().infill_extruder, 1)); +#else + _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, + volume(idx_layer, + is_solid_infill(fill->entities.front()->role()) ? + layerm->region().config().solid_infill_extruder : + layerm->region().config().infill_extruder, + 1)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + } + if (ctxt.has_support) { + const SupportLayer *support_layer = dynamic_cast(layer); + if (support_layer) { + for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) +#if ENABLE_LEGACY_OPENGL_REMOVAL + _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, + select_geometry(idx_layer, (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config().support_material_extruder : + support_layer->object()->config().support_material_interface_extruder, 2)); +#else + _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, + volume(idx_layer, + (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config().support_material_extruder : + support_layer->object()->config().support_material_interface_extruder, + 2)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + } + // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + vol.model.init_from(std::move(geometries[i])); +#else + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + vols[i] = new_volume(vol.color); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + reserve_new_volume_finalize_old_volume(*vols[i], vol, false); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + } + } + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t i = 0; i < vols.size(); ++i) { + if (!geometries[i].is_empty()) + vols[i]->model.init_from(std::move(geometries[i])); + } +#else + for (GLVolume *vol : vols) + // Ideally one would call vol->indexed_vertex_array.finalize() here to move the buffers to the OpenGL driver, + // but this code runs in parallel and the OpenGL driver is not thread safe. + vol->indexed_vertex_array.shrink_to_fit(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + }); + }); // task arena + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); + // Remove empty volumes from the newly added volumes. + { + for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) + if ((*ptr_it)->empty()) { + delete *ptr_it; + *ptr_it = nullptr; + } + m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); + } + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { + GLVolume* v = m_volumes.volumes[i]; +#if ENABLE_LEGACY_OPENGL_REMOVAL + v->is_outside = !contains(build_volume, v->model); + // We are done editinig the model, now it can be sent to gpu + v->model.enable_render(); +#else + v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); + v->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); +} + +void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, const std::vector& str_tool_colors) +{ + const Print *print = this->fff_print(); + if (print == nullptr || print->wipe_tower_data().tool_changes.empty()) + return; + + if (!print->is_step_done(psWipeTower)) + return; + + std::vector tool_colors; + decode_colors(str_tool_colors, tool_colors); + + struct Ctxt + { + const Print *print; + const std::vector *tool_colors; + Vec2f wipe_tower_pos; + float wipe_tower_angle; + + static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() : 0; } + const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } + int volume_idx(int tool, int feature) const { + return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; + } + + const std::vector& tool_change(size_t idx) { + const auto &tool_changes = print->wipe_tower_data().tool_changes; + return priming.empty() ? + ((idx == tool_changes.size()) ? final : tool_changes[idx]) : + ((idx == 0) ? priming : (idx == tool_changes.size() + 1) ? final : tool_changes[idx - 1]); + } + std::vector priming; + std::vector final; + } ctxt; + + ctxt.print = print; + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + if (print->wipe_tower_data().priming && print->config().single_extruder_multi_material_priming) + for (int i=0; i<(int)print->wipe_tower_data().priming.get()->size(); ++i) + ctxt.priming.emplace_back(print->wipe_tower_data().priming.get()->at(i)); + if (print->wipe_tower_data().final_purge) + ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get()); + + ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI; + ctxt.wipe_tower_pos = Vec2f(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); + + //FIXME Improve the heuristics for a grain size. + size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); + size_t grain_size = std::max(n_items / 128, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { + auto *volume = new GLVolume(color); + volume->is_extrusion_path = true; +#if ENABLE_LEGACY_OPENGL_REMOVAL + // to prevent sending data to gpu (in the main thread) while + // editing the model geometry + volume->model.disable_render(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + tbb::spin_mutex::scoped_lock lock; + lock.acquire(new_volume_mutex); + m_volumes.volumes.emplace_back(volume); + lock.release(); + return volume; + }; + const size_t volumes_cnt_initial = m_volumes.volumes.size(); + std::vector volumes_per_thread(n_items); + tbb::parallel_for( + tbb::blocked_range(0, n_items, grain_size), + [&ctxt, &new_volume](const tbb::blocked_range& range) { + // Bounding box of this slab of a wipe tower. + GLVolumePtrs vols; +#if ENABLE_LEGACY_OPENGL_REMOVAL + std::vector geometries; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++i) { + vols.emplace_back(new_volume(ctxt.color_tool(i))); +#if ENABLE_LEGACY_OPENGL_REMOVAL + geometries.emplace_back(GLModel::Geometry()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + else { + vols = { new_volume(ctxt.color_support()) }; +#if ENABLE_LEGACY_OPENGL_REMOVAL + geometries = { GLModel::Geometry() }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + assert(vols.size() == geometries.size()); + for (GLModel::Geometry& g : geometries) { + g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + } +#else + for (GLVolume *volume : vols) + // Reserving number of vertices (3x position + 3x color) + volume->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + const std::vector &layer = ctxt.tool_change(idx_layer); + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { + vol.print_zs.emplace_back(layer.front().print_z); +#if ENABLE_LEGACY_OPENGL_REMOVAL + vol.offsets.emplace_back(geometries[i].indices_count()); +#else + vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size()); + vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + for (const WipeTower::ToolChangeResult &extrusions : layer) { + for (size_t i = 1; i < extrusions.extrusions.size();) { + const WipeTower::Extrusion &e = extrusions.extrusions[i]; + if (e.width == 0.) { + ++i; + continue; + } + size_t j = i + 1; + if (ctxt.color_by_tool()) + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j); + else + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j); + size_t n_lines = j - i; + Lines lines; + std::vector widths; + std::vector heights; + lines.reserve(n_lines); + widths.reserve(n_lines); + heights.assign(n_lines, extrusions.layer_height); + WipeTower::Extrusion e_prev = extrusions.extrusions[i-1]; + + if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation + e_prev.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e_prev.pos; + e_prev.pos += ctxt.wipe_tower_pos; + } + + for (; i < j; ++i) { + WipeTower::Extrusion e = extrusions.extrusions[i]; + assert(e.width > 0.f); + if (!extrusions.priming) { + e.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e.pos; + e.pos += ctxt.wipe_tower_pos; + } + + lines.emplace_back(Point::new_scale(e_prev.pos.x(), e_prev.pos.y()), Point::new_scale(e.pos.x(), e.pos.y())); + widths.emplace_back(e.width); + + e_prev = e; + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, + geometries[ctxt.volume_idx(e.tool, 0)]); +#else + _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, + *vols[ctxt.volume_idx(e.tool, 0)]); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + } + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + vol.model.init_from(std::move(geometries[i])); +#else + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + vols[i] = new_volume(vol.color); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + reserve_new_volume_finalize_old_volume(*vols[i], vol, false); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + } + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (size_t i = 0; i < vols.size(); ++i) { + if (!geometries[i].is_empty()) + vols[i]->model.init_from(std::move(geometries[i])); + } +#else + for (GLVolume *vol : vols) + vol->indexed_vertex_array.shrink_to_fit(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + }); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); + // Remove empty volumes from the newly added volumes. + { + for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) + if ((*ptr_it)->empty()) { + delete *ptr_it; + *ptr_it = nullptr; + } + m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); + } + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { + GLVolume* v = m_volumes.volumes[i]; +#if ENABLE_LEGACY_OPENGL_REMOVAL + v->is_outside = !contains(build_volume, v->model); + // We are done editinig the model, now it can be sent to gpu + v->model.enable_render(); +#else + v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); + v->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); +} + +// While it looks like we can call +// this->reload_scene(true, true) +// the two functions are quite different: +// 1) This function only loads objects, for which the step slaposSliceSupports already finished. Therefore objects outside of the print bed never load. +// 2) This function loads object mesh with the relative scaling correction (the "relative_correction" parameter) was applied, +// therefore the mesh may be slightly larger or smaller than the mesh shown in the 3D scene. +void GLCanvas3D::_load_sla_shells() +{ + const SLAPrint* print = this->sla_print(); + if (print->objects().empty()) + // nothing to render, return + return; + + auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance, + const TriangleMesh& mesh, const ColorRGBA& color, bool outside_printer_detection_enabled) { + m_volumes.volumes.emplace_back(new GLVolume(color)); + GLVolume& v = *m_volumes.volumes.back(); +#if ENABLE_SMOOTH_NORMALS +#if ENABLE_LEGACY_OPENGL_REMOVAL + v.model.init_from(mesh, true); +#else + v.indexed_vertex_array.load_mesh(mesh, true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#else +#if ENABLE_LEGACY_OPENGL_REMOVAL + v.model.init_from(mesh); +#else + v.indexed_vertex_array.load_mesh(mesh); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#endif // ENABLE_SMOOTH_NORMALS +#if !ENABLE_LEGACY_OPENGL_REMOVAL + v.indexed_vertex_array.finalize_geometry(m_initialized); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; + v.composite_id.volume_id = volume_id; + v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0.0)); + v.set_instance_rotation({ 0.0, 0.0, (double)instance.rotation }); + v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); + v.set_convex_hull(mesh.convex_hull_3d()); + }; + + // adds objects' volumes + for (const SLAPrintObject* obj : print->objects()) + if (obj->is_step_done(slaposSliceSupports)) { + unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); + for (const SLAPrintObject::Instance& instance : obj->instances()) { + add_volume(*obj, 0, instance, obj->get_mesh_to_print(), GLVolume::MODEL_COLOR[0], true); + // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when + // through the update_volumes_colors_by_extruder() call. + m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); + if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) + add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true); + if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad)) + add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); + } + double shift_z = obj->get_current_elevation(); + for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { + // apply shift z + m_volumes.volumes[i]->set_sla_shift_z(shift_z); + } + } + + update_volumes_colors_by_extruder(); +} + +void GLCanvas3D::_update_sla_shells_outside_state() +{ + check_volumes_outside_state(); +} + +void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) +{ + _set_current(); + bool show = false; + if (!m_volumes.empty()) { + if (current_printer_technology() == ptSLA) { + const auto [res, volume] = _is_any_volume_outside(); + if (res) { + if (warning == EWarning::ObjectClashed) + show = !volume->is_sla_support(); + else if (warning == EWarning::SlaSupportsOutside) + show = volume->is_sla_support(); + } + } + else + show = _is_any_volume_outside().first; + } + else { + if (wxGetApp().is_editor()) { + if (current_printer_technology() != ptSLA) + show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); + } + } + + _set_warning_notification(warning, show); +} + +void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) +{ + enum ErrorType{ + PLATER_WARNING, + PLATER_ERROR, + SLICING_ERROR + }; + std::string text; + ErrorType error = ErrorType::PLATER_WARNING; + switch (warning) { + case EWarning::ObjectOutside: text = _u8L("An object outside the print area was detected."); break; + case EWarning::ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = ErrorType::SLICING_ERROR; break; + case EWarning::SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break; + case EWarning::SomethingNotShown: text = _u8L("Some objects are not visible during editing."); break; + case EWarning::ObjectClashed: + text = _u8L("An object outside the print area was detected.\n" + "Resolve the current problem to continue slicing."); + error = ErrorType::PLATER_ERROR; + break; + } + auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); + switch (error) + { + case PLATER_WARNING: + if (state) + notification_manager.push_plater_warning_notification(text); + else + notification_manager.close_plater_warning_notification(text); + break; + case PLATER_ERROR: + if (state) + notification_manager.push_plater_error_notification(text); + else + notification_manager.close_plater_error_notification(text); + break; + case SLICING_ERROR: + if (state) + notification_manager.push_slicing_error_notification(text); + else + notification_manager.close_slicing_error_notification(text); + break; + default: + break; + } +} + +std::pair GLCanvas3D::_is_any_volume_outside() const +{ + for (const GLVolume* volume : m_volumes.volumes) { + if (volume != nullptr && volume->is_outside) + return std::make_pair(true, volume); + } + + return std::make_pair(false, nullptr); +} + +void GLCanvas3D::_update_selection_from_hover() +{ + bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); + bool selection_changed = false; + + if (m_hover_volume_idxs.empty()) { + if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) { + selection_changed = ! m_selection.is_empty(); + m_selection.remove_all(); + } + } + + GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); + + bool hover_modifiers_only = true; + for (int i : m_hover_volume_idxs) { + if (!m_volumes.volumes[i]->is_modifier) { + hover_modifiers_only = false; + break; + } + } + + if (!m_rectangle_selection.is_empty()) { + if (state == GLSelectionRectangle::EState::Select) { + bool contains_all = true; + for (int i : m_hover_volume_idxs) { + if (!m_selection.contains_volume((unsigned int)i)) { + contains_all = false; + break; + } + } + + // the selection is going to be modified (Add) + if (!contains_all) { + wxGetApp().plater()->take_snapshot(_L("Selection-Add from rectangle"), UndoRedo::SnapshotType::Selection); + selection_changed = true; + } + } + else { + bool contains_any = false; + for (int i : m_hover_volume_idxs) { + if (m_selection.contains_volume((unsigned int)i)) { + contains_any = true; + break; + } + } + + // the selection is going to be modified (Remove) + if (contains_any) { + wxGetApp().plater()->take_snapshot(_L("Selection-Remove from rectangle"), UndoRedo::SnapshotType::Selection); + selection_changed = true; + } + } + } + + if (!selection_changed) + return; + + Plater::SuppressSnapshots suppress(wxGetApp().plater()); + + if (state == GLSelectionRectangle::EState::Select && !ctrl_pressed) + m_selection.clear(); + + for (int i : m_hover_volume_idxs) { + if (state == GLSelectionRectangle::EState::Select) { + if (hover_modifiers_only) { + const GLVolume& v = *m_volumes.volumes[i]; + m_selection.add_volume(v.object_idx(), v.volume_idx(), v.instance_idx(), false); + } + else + m_selection.add(i, false); + } + else + m_selection.remove(i); + } + + if (m_selection.is_empty()) + m_gizmos.reset_all_states(); + else + m_gizmos.refresh_on_off_state(); + + m_gizmos.update_data(); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + m_dirty = true; +} + +bool GLCanvas3D::_deactivate_undo_redo_toolbar_items() +{ + if (m_undoredo_toolbar.is_item_pressed("undo")) { + m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("undo"), *this); + return true; + } + else if (m_undoredo_toolbar.is_item_pressed("redo")) { + m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("redo"), *this); + return true; + } + + return false; +} + +bool GLCanvas3D::is_search_pressed() const +{ + return m_main_toolbar.is_item_pressed("search"); +} + +bool GLCanvas3D::_deactivate_arrange_menu() +{ + if (m_main_toolbar.is_item_pressed("arrange")) { + m_main_toolbar.force_right_action(m_main_toolbar.get_item_id("arrange"), *this); + return true; + } + + return false; +} + +bool GLCanvas3D::_deactivate_search_toolbar_item() +{ + if (is_search_pressed()) { + m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this); + return true; + } + + return false; +} + +bool GLCanvas3D::_activate_search_toolbar_item() +{ + if (!m_main_toolbar.is_item_pressed("search")) { + m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this); + return true; + } + + return false; +} + +bool GLCanvas3D::_deactivate_collapse_toolbar_items() +{ + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + if (collapse_toolbar.is_item_pressed("print")) { + collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this); + return true; + } + + return false; +} + +void GLCanvas3D::highlight_toolbar_item(const std::string& item_name) +{ + GLToolbarItem* item = m_main_toolbar.get_item(item_name); + if (!item) + item = m_undoredo_toolbar.get_item(item_name); + if (!item || !item->is_visible()) + return; + m_toolbar_highlighter.init(item, this); +} + +void GLCanvas3D::highlight_gizmo(const std::string& gizmo_name) +{ + GLGizmosManager::EType gizmo = m_gizmos.get_gizmo_from_name(gizmo_name); + if(gizmo == GLGizmosManager::EType::Undefined) + return; + m_gizmo_highlighter.init(&m_gizmos, gizmo, this); +} + +const Print* GLCanvas3D::fff_print() const +{ + return (m_process == nullptr) ? nullptr : m_process->fff_print(); +} + +const SLAPrint* GLCanvas3D::sla_print() const +{ + return (m_process == nullptr) ? nullptr : m_process->sla_print(); +} + +void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const +{ + DynamicPrintConfig cfg; + cfg.opt("wipe_tower_x", true)->value = m_pos(X); + cfg.opt("wipe_tower_y", true)->value = m_pos(Y); + cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; + wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); +} + +void GLCanvas3D::RenderTimer::Notify() +{ + wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this)); +} + +void GLCanvas3D::ToolbarHighlighterTimer::Notify() +{ + wxPostEvent((wxEvtHandler*)GetOwner(), ToolbarHighlighterTimerEvent(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, *this)); +} + +void GLCanvas3D::GizmoHighlighterTimer::Notify() +{ + wxPostEvent((wxEvtHandler*)GetOwner(), GizmoHighlighterTimerEvent(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, *this)); +} + +void GLCanvas3D::ToolbarHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) +{ + m_timer.SetOwner(owner, timerid); +} + +void GLCanvas3D::ToolbarHighlighter::init(GLToolbarItem* toolbar_item, GLCanvas3D* canvas) +{ + if (m_timer.IsRunning()) + invalidate(); + if (!toolbar_item || !canvas) + return; + + m_timer.Start(300, false); + + m_toolbar_item = toolbar_item; + m_canvas = canvas; +} + +void GLCanvas3D::ToolbarHighlighter::invalidate() +{ + m_timer.Stop(); + + if (m_toolbar_item) { + m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::NotHighlighted); + } + m_toolbar_item = nullptr; + m_blink_counter = 0; + m_render_arrow = false; +} + +void GLCanvas3D::ToolbarHighlighter::blink() +{ + if (m_toolbar_item) { + char state = m_toolbar_item->get_highlight(); + if (state != (char)GLToolbarItem::EHighlightState::HighlightedShown) + m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedShown); + else + m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedHidden); + + m_render_arrow = !m_render_arrow; + m_canvas->set_as_dirty(); + } + else + invalidate(); + + if ((++m_blink_counter) >= 11) + invalidate(); +} + +void GLCanvas3D::GizmoHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) +{ + m_timer.SetOwner(owner, timerid); +} + +void GLCanvas3D::GizmoHighlighter::init(GLGizmosManager* manager, GLGizmosManager::EType gizmo, GLCanvas3D* canvas) +{ + if (m_timer.IsRunning()) + invalidate(); + if (!gizmo || !canvas) + return; + + m_timer.Start(300, false); + + m_gizmo_manager = manager; + m_gizmo_type = gizmo; + m_canvas = canvas; +} + +void GLCanvas3D::GizmoHighlighter::invalidate() +{ + m_timer.Stop(); + + if (m_gizmo_manager) { + m_gizmo_manager->set_highlight(GLGizmosManager::EType::Undefined, false); + } + m_gizmo_manager = nullptr; + m_gizmo_type = GLGizmosManager::EType::Undefined; + m_blink_counter = 0; + m_render_arrow = false; +} + +void GLCanvas3D::GizmoHighlighter::blink() +{ + if (m_gizmo_manager) { + if (m_blink_counter % 2 == 0) + m_gizmo_manager->set_highlight(m_gizmo_type, true); + else + m_gizmo_manager->set_highlight(m_gizmo_type, false); + + m_render_arrow = !m_render_arrow; + m_canvas->set_as_dirty(); + } + else + invalidate(); + + if ((++m_blink_counter) >= 11) + invalidate(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index df936977d7..154d1f1be5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -1158,18 +1158,18 @@ void TriangleSelectorGUI::update_render_data() #if !ENABLE_LEGACY_OPENGL_REMOVAL void GLPaintContour::render() const { - assert(this->m_contour_VBO_id != 0); - assert(this->m_contour_EBO_id != 0); + assert(m_contour_VBO_id != 0); + assert(m_contour_EBO_id != 0); glsafe(::glLineWidth(4.0f)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_contour_VBO_id)); glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); if (this->contour_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_contour_EBO_id)); glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } @@ -1181,20 +1181,20 @@ void GLPaintContour::render() const void GLPaintContour::finalize_geometry() { - assert(this->m_contour_VBO_id == 0); - assert(this->m_contour_EBO_id == 0); + assert(m_contour_VBO_id == 0); + assert(m_contour_EBO_id == 0); if (!this->contour_vertices.empty()) { - glsafe(::glGenBuffers(1, &this->m_contour_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); + glsafe(::glGenBuffers(1, &m_contour_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_contour_VBO_id)); glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); this->contour_vertices.clear(); } if (!this->contour_indices.empty()) { - glsafe(::glGenBuffers(1, &this->m_contour_EBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); + glsafe(::glGenBuffers(1, &m_contour_EBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_contour_EBO_id)); glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); this->contour_indices.clear(); @@ -1203,13 +1203,13 @@ void GLPaintContour::finalize_geometry() void GLPaintContour::release_geometry() { - if (this->m_contour_VBO_id) { - glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id)); - this->m_contour_VBO_id = 0; + if (m_contour_VBO_id) { + glsafe(::glDeleteBuffers(1, &m_contour_VBO_id)); + m_contour_VBO_id = 0; } - if (this->m_contour_EBO_id) { - glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id)); - this->m_contour_EBO_id = 0; + if (m_contour_EBO_id) { + glsafe(::glDeleteBuffers(1, &m_contour_EBO_id)); + m_contour_EBO_id = 0; } this->clear(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 04f24b20d3..94aca4fdb7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -41,7 +41,7 @@ public: void render() const; - inline bool has_VBO() const { return this->m_contour_EBO_id != 0; } + inline bool has_VBO() const { return m_contour_EBO_id != 0; } // Release the geometry data, release OpenGL VBOs. void release_geometry(); diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index cfc78f9f80..fe57d7d5a4 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -772,11 +772,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) // get index of the last VolumeItem in CildrenList size_t vol_idx = GetItemIndexForFirstVolume(node_parent); - ObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx); - // if last volume is text then don't delete it - if (last_child_node->is_text_volume()) - return parent; // delete this last volume DeleteSettings(wxDataViewItem(last_child_node)); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 1855b6a4e5..cfa2ef15a6 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -101,7 +101,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co this->back_to_initial_value(opt_id); }; field->m_back_to_sys_value = [this](std::string opt_id) { - if (!this->m_disabled) + if (!m_disabled) this->back_to_sys_value(opt_id); }; diff --git a/src/slic3r/Utils/AppUpdater.cpp b/src/slic3r/Utils/AppUpdater.cpp index 27f2e34bc8..a17adea8a5 100644 --- a/src/slic3r/Utils/AppUpdater.cpp +++ b/src/slic3r/Utils/AppUpdater.cpp @@ -168,7 +168,7 @@ bool AppUpdater::priv::http_get_file(const std::string& url, size_t size_limit, .size_limit(size_limit) .on_progress([&, progress_fn](Http::Progress progress, bool& cancel) { // progress function returns true as success (to continue) - cancel = (this->m_cancel ? true : !progress_fn(std::move(progress))); + cancel = (m_cancel ? true : !progress_fn(std::move(progress))); if (cancel) { error_message = GUI::format("Error getting: `%1%`: Download was canceled.", //lm:typo //dk: am i blind? :) url); @@ -259,7 +259,7 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d ); if (!res) { - if (this->m_cancel) + if (m_cancel) { BOOST_LOG_TRIVIAL(info) << error_message; //lm:Is this an error? // dk: changed to info wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); // FAILED with empty msg only closes progress notification diff --git a/t/combineinfill.t b/t/combineinfill.t deleted file mode 100644 index ebb4304196..0000000000 --- a/t/combineinfill.t +++ /dev/null @@ -1,110 +0,0 @@ -use Test::More; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Surface ':types'; -use Slic3r::Test; - -plan tests => 8; - -{ - my $test = sub { - my ($config) = @_; - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok my $gcode = Slic3r::Test::gcode($print), "infill_every_layers does not crash"; - - my $tool = undef; - my %layers = (); # layer_z => 1 - my %layer_infill = (); # layer_z => has_infill - Slic3r::GCode::Reader->new->parse($gcode, sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0 && $tool != $config->support_material_extruder-1) { - $layer_infill{$self->Z} //= 0; - if ($tool == $config->infill_extruder-1) { - $layer_infill{$self->Z} = 1; - } - } - # Previously, all G-code commands had a fixed number of decimal points with means with redundant zeros after decimal points. - # We changed this behavior and got rid of these redundant padding zeros, which caused this test to fail - # because the position in Z-axis is compared as a string, and previously, G-code contained the following two commands: - # "G1 Z5 F5000 ; lift nozzle" - # "G1 Z5.000 F7800.000" - # That has a different Z-axis position from the view of string comparisons of floating-point numbers. - # To correct the computation of the number of printed layers, even in the case of string comparisons of floating-point numbers, - # we filtered out the G-code command with the commend 'lift nozzle'. - $layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} && index($info->{comment}, 'lift nozzle') == -1; - }); - - my $layers_with_perimeters = scalar(keys %layer_infill); - my $layers_with_infill = grep $_ > 0, values %layer_infill; - is scalar(keys %layers), $layers_with_perimeters+$config->raft_layers, 'expected number of layers'; - - if ($config->raft_layers == 0) { - # first infill layer printed directly on print bed is not combined, so we don't consider it. - $layers_with_infill--; - $layers_with_perimeters--; - } - - # we expect that infill is generated for half the number of combined layers - # plus for each single layer that was not combined (remainder) - is $layers_with_infill, - int($layers_with_perimeters/$config->infill_every_layers) + ($layers_with_perimeters % $config->infill_every_layers), - 'infill is only present in correct number of layers'; - }; - - my $config = Slic3r::Config::new_from_defaults; - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.5,0.5,0.5,0.5]); - $config->set('infill_every_layers', 2); - $config->set('perimeter_extruder', 1); - $config->set('infill_extruder', 2); - $config->set('wipe_into_infill', 0); - $config->set('support_material_extruder', 3); - $config->set('support_material_interface_extruder', 3); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $test->($config); - - $config->set('skirts', 0); # prevent usage of perimeter_extruder in raft layers - $config->set('raft_layers', 5); - $test->($config); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.5]); - $config->set('infill_every_layers', 2); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - $print->process; - - ok defined(first { @{$_->get_region(0)->fill_surfaces->filter_by_type(S_TYPE_INTERNALVOID)} > 0 } - @{$print->print->get_object(0)->layers}), - 'infill combination produces internal void surfaces'; - - # we disable combination after infill has been generated - $config->set('infill_every_layers', 1); - $print->apply($print->print->model->clone, $config); - $print->process; - - ok !(defined first { @{$_->get_region(0)->fill_surfaces} == 0 } - @{$print->print->get_object(0)->layers}), - 'infill combination is idempotent'; -} - -__END__ diff --git a/t/geometry.t b/t/geometry.t index 981621ee8d..83fb72eeab 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 11; +plan tests => 10; BEGIN { use FindBin; @@ -57,11 +57,11 @@ my $polygons = [ #========================================================== -{ - my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), [0, 1], [10, 2], [20, 2] ]); - $bb->scale(2); - is_deeply [ $bb->min_point->pp, $bb->max_point->pp ], [ [0,2], [40,4] ], 'bounding box is scaled correctly'; -} +#{ +# my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), [0, 1], [10, 2], [20, 2] ]); +# $bb->scale(2); +# is_deeply [ $bb->min_point->pp, $bb->max_point->pp ], [ [0,2], [40,4] ], 'bounding box is scaled correctly'; +#} #========================================================== diff --git a/t/shells.t b/t/shells.t index 29bc0b5f00..cd96f9cde1 100644 --- a/t/shells.t +++ b/t/shells.t @@ -1,4 +1,4 @@ -use Test::More tests => 20; +use Test::More tests => 17; use strict; use warnings; @@ -13,73 +13,6 @@ use Slic3r; use Slic3r::Geometry qw(epsilon); use Slic3r::Test; -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 0); - $config->set('solid_infill_speed', 99); - $config->set('top_solid_infill_speed', 99); - $config->set('bridge_speed', 72); - $config->set('first_layer_speed', '100%'); - $config->set('cooling', [ 0 ]); - - my $test = sub { - my ($conf) = @_; - $conf ||= $config; - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - - my %z = (); # Z => 1 - my %layers_with_solid_infill = (); # Z => $count - my %layers_with_bridge_infill = (); # Z => $count - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($self->Z > 0) { - $z{ $self->Z } = 1; - if ($info->{extruding} && $info->{dist_XY} > 0) { - my $F = $args->{F} // $self->F; - $layers_with_solid_infill{$self->Z} = 1 - if $F == $config->solid_infill_speed*60; - $layers_with_bridge_infill{$self->Z} = 1 - if $F == $config->bridge_speed*60; - } - } - }); - my @z = sort { $a <=> $b } keys %z; - my @shells = map $layers_with_solid_infill{$_} || $layers_with_bridge_infill{$_}, @z; - fail "insufficient number of bottom solid layers" - unless !defined(first { !$_ } @shells[0..$config->bottom_solid_layers-1]); - fail "excessive number of bottom solid layers" - unless scalar(grep $_, @shells[0 .. $#shells/2]) == $config->bottom_solid_layers; - fail "insufficient number of top solid layers" - unless !defined(first { !$_ } @shells[-$config->top_solid_layers..-1]); - fail "excessive number of top solid layers" - unless scalar(grep $_, @shells[($#shells/2)..$#shells]) == $config->top_solid_layers; - if ($config->top_solid_layers > 0) { - fail "unexpected solid infill speed in first solid layer over sparse infill" - if $layers_with_solid_infill{ $z[-$config->top_solid_layers] }; - die "bridge speed not used in first solid layer over sparse infill" - if !$layers_with_bridge_infill{ $z[-$config->top_solid_layers] }; - } - 1; - }; - - $config->set('top_solid_layers', 3); - $config->set('bottom_solid_layers', 3); - ok $test->(), "proper number of shells is applied"; - - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - ok $test->(), "no shells are applied when both top and bottom are set to zero"; - - $config->set('perimeters', 1); - $config->set('top_solid_layers', 3); - $config->set('bottom_solid_layers', 3); - $config->set('fill_density', 0); - ok $test->(), "proper number of shells is applied even when fill density is none"; -} - # issue #1161 { my $config = Slic3r::Config::new_from_defaults; diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 0b3b920b93..6e9a80ef86 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable(${_TEST_NAME}_tests test_print.cpp test_printgcode.cpp test_printobject.cpp + test_shells.cpp test_skirt_brim.cpp test_support_material.cpp test_thin_walls.cpp diff --git a/tests/fff_print/test_clipper.cpp b/tests/fff_print/test_clipper.cpp index dc49ce0f15..ea923b48e3 100644 --- a/tests/fff_print/test_clipper.cpp +++ b/tests/fff_print/test_clipper.cpp @@ -2,25 +2,115 @@ #include "test_data.hpp" #include "clipper/clipper_z.hpp" +#include "libslic3r/clipper.hpp" using namespace Slic3r; -// Test case for an issue with duplicity vertices (same XY coordinates but differ in Z coordinates) in Clipper 6.2.9, -// (related to https://sourceforge.net/p/polyclipping/bugs/160/) that was fixed in Clipper 6.4.2. +// tests for ExPolygon::overlaps(const ExPolygon &other) +SCENARIO("Clipper intersection with polyline", "[Clipper]") +{ + struct TestData { + ClipperLib::Path subject; + ClipperLib::Path clip; + ClipperLib::Paths result; + }; + + auto run_test = [](const TestData &data) { + ClipperLib::Clipper clipper; + clipper.AddPath(data.subject, ClipperLib::ptSubject, false); + clipper.AddPath(data.clip, ClipperLib::ptClip, true); + + ClipperLib::PolyTree polytree; + ClipperLib::Paths paths; + clipper.Execute(ClipperLib::ctIntersection, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + ClipperLib::PolyTreeToPaths(polytree, paths); + + REQUIRE(paths == data.result); + }; + + WHEN("Open polyline completely inside stays inside") { + run_test({ + { { 10, 0 }, { 20, 0 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { 20, 0 }, { 10, 0 } } } + }); + }; + WHEN("Closed polyline completely inside stays inside") { + run_test({ + { { 10, 0 }, { 20, 0 }, { 20, 20 }, { 10, 20 }, { 10, 0 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { 10, 0 }, { 20, 0 }, { 20, 20 }, { 10, 20 }, { 10, 0 } } } + }); + }; + WHEN("Polyline which crosses right rectangle boundary is trimmed") { + run_test({ + { { 10, 0 }, { 2000, 0 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { 1000, 0 }, { 10, 0 } } } + }); + }; + WHEN("Polyline which is outside clipping region is removed") { + run_test({ + { { 1500, 0 }, { 2000, 0 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { } + }); + }; + + WHEN("Polyline on left vertical boundary is kept") { + run_test({ + { { -1000, -1000 }, { -1000, 1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { -1000, -1000 }, { -1000, 1000 } } } + }); + run_test({ + { { -1000, 1000 }, { -1000, -1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { -1000, 1000 }, { -1000, -1000 } } } + }); + }; + WHEN("Polyline on right vertical boundary is kept") { + run_test({ + { { 1000, -1000 }, { 1000, 1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { 1000, -1000 }, { 1000, 1000 } } } + }); + run_test({ + { { 1000, 1000 }, { 1000, -1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { 1000, 1000 }, { 1000, -1000 } } } + }); + }; + WHEN("Polyline on bottom horizontal boundary is removed") { + run_test({ + { { -1000, -1000 }, { 1000, -1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { } + }); + run_test({ + { { 1000, -1000 }, { -1000, -1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { } + }); + }; + WHEN("Polyline on top horizontal boundary is removed") { + run_test({ + { { -1000, 1000 }, { 1000, 1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { } + }); + run_test({ + { { 1000, 1000 }, { -1000, 1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { } + }); + }; +} + SCENARIO("Clipper Z", "[ClipperZ]") { - ClipperLib_Z::Path subject; - - subject.emplace_back(-2000, -1000, 10); - subject.emplace_back(-2000, 1000, 10); - subject.emplace_back( 2000, 1000, 10); - subject.emplace_back( 2000, -1000, 10); - - ClipperLib_Z::Path clip; - clip.emplace_back(-1000, -2000, -5); - clip.emplace_back(-1000, 2000, -5); - clip.emplace_back( 1000, 2000, -5); - clip.emplace_back( 1000, -2000, -5); + ClipperLib_Z::Path subject { { -2000, -1000, 10 }, { -2000, 1000, 10 }, { 2000, 1000, 10 }, { 2000, -1000, 10 } }; + ClipperLib_Z::Path clip{ { -1000, -2000, -5 }, { -1000, 2000, -5 }, { 1000, 2000, -5 }, { 1000, -2000, -5 } }; ClipperLib_Z::Clipper clipper; clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, @@ -40,4 +130,5 @@ SCENARIO("Clipper Z", "[ClipperZ]") REQUIRE(paths.front().size() == 2); for (const ClipperLib_Z::IntPoint &pt : paths.front()) REQUIRE(pt.z() == 1); -} \ No newline at end of file +} + diff --git a/tests/fff_print/test_extrusion_entity.cpp b/tests/fff_print/test_extrusion_entity.cpp index c98e0ec1a4..56588d98a4 100644 --- a/tests/fff_print/test_extrusion_entity.cpp +++ b/tests/fff_print/test_extrusion_entity.cpp @@ -35,7 +35,254 @@ static Slic3r::ExtrusionPaths random_paths(size_t count = 10, size_t length = 20 return p; } -SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]") { +SCENARIO("ExtrusionPath", "[ExtrusionEntity]") { + GIVEN("Simple path") { + Slic3r::ExtrusionPath path{ erExternalPerimeter }; + path.polyline = { { 100, 100 }, { 200, 100 }, { 200, 200 } }; + path.mm3_per_mm = 1.; + THEN("first point") { + REQUIRE(path.first_point() == path.polyline.front()); + } + THEN("cloned") { + auto cloned = std::unique_ptr(path.clone()); + REQUIRE(cloned->role() == path.role()); + } + } +} + +static ExtrusionPath new_extrusion_path(const Polyline &polyline, ExtrusionRole role, double mm3_per_mm) +{ + ExtrusionPath path(role); + path.polyline = polyline; + path.mm3_per_mm = 1.; + return path; +} + +SCENARIO("ExtrusionLoop", "[ExtrusionEntity]") +{ + GIVEN("Square") { + Polygon square { { 100, 100 }, { 200, 100 }, { 200, 200 }, { 100, 200 } }; + + ExtrusionLoop loop; + loop.paths.emplace_back(new_extrusion_path(square.split_at_first_point(), erExternalPerimeter, 1.)); + THEN("polygon area") { + REQUIRE(loop.polygon().area() == Approx(square.area())); + } + THEN("loop length") { + REQUIRE(loop.length() == Approx(square.length())); + } + + WHEN("cloned") { + auto loop2 = std::unique_ptr(dynamic_cast(loop.clone())); + THEN("cloning worked") { + REQUIRE(loop2 != nullptr); + } + THEN("loop contains one path") { + REQUIRE(loop2->paths.size() == 1); + } + THEN("cloned role") { + REQUIRE(loop2->paths.front().role() == erExternalPerimeter); + } + } + WHEN("cloned and split") { + auto loop2 = std::unique_ptr(dynamic_cast(loop.clone())); + loop2->split_at_vertex(square.points[2]); + THEN("splitting a single-path loop results in a single path") { + REQUIRE(loop2->paths.size() == 1); + } + THEN("path has correct number of points") { + REQUIRE(loop2->paths.front().size() == 5); + } + THEN("expected point order") { + REQUIRE(loop2->paths.front().polyline[0] == square.points[2]); + REQUIRE(loop2->paths.front().polyline[1] == square.points[3]); + REQUIRE(loop2->paths.front().polyline[2] == square.points[0]); + REQUIRE(loop2->paths.front().polyline[3] == square.points[1]); + REQUIRE(loop2->paths.front().polyline[4] == square.points[2]); + } + } + } + + GIVEN("Loop with two pieces") { + Polyline polyline1 { { 100, 100 }, { 200, 100 }, { 200, 200 } }; + Polyline polyline2 { { 200, 200 }, { 100, 200 }, { 100, 100 } }; + ExtrusionLoop loop; + loop.paths.emplace_back(new_extrusion_path(polyline1, erExternalPerimeter, 1.)); + loop.paths.emplace_back(new_extrusion_path(polyline2, erOverhangPerimeter, 1.)); + + double tot_len = polyline1.length() + polyline2.length(); + THEN("length") { + REQUIRE(loop.length() == Approx(tot_len)); + } + + WHEN("splitting at intermediate point") { + auto loop2 = std::unique_ptr(dynamic_cast(loop.clone())); + loop2->split_at_vertex(polyline1.points[1]); + THEN("length after splitting is unchanged") { + REQUIRE(loop2->length() == Approx(tot_len)); + } + THEN("loop contains three paths after splitting") { + REQUIRE(loop2->paths.size() == 3); + } + THEN("expected starting point") { + REQUIRE(loop2->paths.front().polyline.front() == polyline1.points[1]); + } + THEN("expected ending point") { + REQUIRE(loop2->paths.back().polyline.back() == polyline1.points[1]); + } + THEN("paths have common point") { + REQUIRE(loop2->paths.front().polyline.back() == loop2->paths[1].polyline.front()); + REQUIRE(loop2->paths[1].polyline.back() == loop2->paths[2].polyline.front()); + } + THEN("expected order after splitting") { + REQUIRE(loop2->paths.front().role() == erExternalPerimeter); + REQUIRE(loop2->paths[1].role() == erOverhangPerimeter); + REQUIRE(loop2->paths[2].role() == erExternalPerimeter); + } + THEN("path has correct number of points") { + REQUIRE(loop2->paths.front().polyline.size() == 2); + REQUIRE(loop2->paths[1].polyline.size() == 3); + REQUIRE(loop2->paths[2].polyline.size() == 2); + } + THEN("clipped path has expected length") { + double l = loop2->length(); + ExtrusionPaths paths; + loop2->clip_end(3, &paths); + double l2 = 0; + for (const ExtrusionPath &p : paths) + l2 += p.length(); + REQUIRE(l2 == Approx(l - 3.)); + } + } + + WHEN("splitting at endpoint") { + auto loop2 = std::unique_ptr(dynamic_cast(loop.clone())); + loop2->split_at_vertex(polyline2.points.front()); + THEN("length after splitting is unchanged") { + REQUIRE(loop2->length() == Approx(tot_len)); + } + THEN("loop contains two paths after splitting") { + REQUIRE(loop2->paths.size() == 2); + } + THEN("expected starting point") { + REQUIRE(loop2->paths.front().polyline.front() == polyline2.points.front()); + } + THEN("expected ending point") { + REQUIRE(loop2->paths.back().polyline.back() == polyline2.points.front()); + } + THEN("paths have common point") { + REQUIRE(loop2->paths.front().polyline.back() == loop2->paths[1].polyline.front()); + REQUIRE(loop2->paths[1].polyline.back() == loop2->paths.front().polyline.front()); + } + THEN("expected order after splitting") { + REQUIRE(loop2->paths.front().role() == erOverhangPerimeter); + REQUIRE(loop2->paths[1].role() == erExternalPerimeter); + } + THEN("path has correct number of points") { + REQUIRE(loop2->paths.front().polyline.size() == 3); + REQUIRE(loop2->paths[1].polyline.size() == 3); + } + } + + WHEN("splitting at an edge") { + Point point(250, 150); + auto loop2 = std::unique_ptr(dynamic_cast(loop.clone())); + loop2->split_at(point, false, 0); + THEN("length after splitting is unchanged") { + REQUIRE(loop2->length() == Approx(tot_len)); + } + Point expected_start_point(200, 150); + THEN("expected starting point") { + REQUIRE(loop2->paths.front().polyline.front() == expected_start_point); + } + THEN("expected ending point") { + REQUIRE(loop2->paths.back().polyline.back() == expected_start_point); + } + } + } + + GIVEN("Loop with four pieces") { + Polyline polyline1 { { 59312736, 4821067 }, { 64321068, 4821067 }, { 64321068, 4821067 }, { 64321068, 9321068 }, { 59312736, 9321068 } }; + Polyline polyline2 { { 59312736, 9321068 }, { 9829401, 9321068 } }; + Polyline polyline3 { { 9829401, 9321068 }, { 4821067, 9321068 }, { 4821067, 4821067 }, { 9829401, 4821067 } }; + Polyline polyline4 { { 9829401, 4821067 }, { 59312736,4821067 } }; + ExtrusionLoop loop; + loop.paths.emplace_back(new_extrusion_path(polyline1, erExternalPerimeter, 1.)); + loop.paths.emplace_back(new_extrusion_path(polyline2, erOverhangPerimeter, 1.)); + loop.paths.emplace_back(new_extrusion_path(polyline3, erExternalPerimeter, 1.)); + loop.paths.emplace_back(new_extrusion_path(polyline4, erOverhangPerimeter, 1.)); + double len = loop.length(); + WHEN("splitting at vertex") { + Point point(4821067, 9321068); + if (! loop.split_at_vertex(point)) + loop.split_at(point, false, 0); + THEN("total length is preserved after splitting") { + REQUIRE(loop.length() == Approx(len)); + } + THEN("order is correctly preserved after splitting") { + REQUIRE(loop.paths.front().role() == erExternalPerimeter); + REQUIRE(loop.paths[1].role() == erOverhangPerimeter); + REQUIRE(loop.paths[2].role() == erExternalPerimeter); + REQUIRE(loop.paths[3].role() == erOverhangPerimeter); + } + } + } + + GIVEN("Some complex loop") { + ExtrusionLoop loop; + loop.paths.emplace_back(new_extrusion_path( + Polyline { { 15896783, 15868739 }, { 24842049, 12117558 }, { 33853238, 15801279 }, { 37591780, 24780128 }, { 37591780, 24844970 }, + { 33853231, 33825297 }, { 24842049, 37509013 }, { 15896798, 33757841 }, { 12211841, 24812544 }, { 15896783, 15868739 } }, + erExternalPerimeter, 1.)); + double len = loop.length(); + THEN("split_at() preserves total length") { + loop.split_at({ 15896783, 15868739 }, false, 0); + REQUIRE(loop.length() == Approx(len)); + } + } +} + +SCENARIO("ExtrusionEntityCollection: Basics", "[ExtrusionEntity]") +{ + Polyline polyline { { 100, 100 }, { 200, 100 }, { 200, 200 } }; + ExtrusionPath path = new_extrusion_path(polyline, erExternalPerimeter, 1.); + ExtrusionLoop loop; + loop.paths.emplace_back(new_extrusion_path(Polygon(polyline.points).split_at_first_point(), erInternalInfill, 1.)); + ExtrusionEntityCollection collection; + collection.append(path); + THEN("no_sort is false by default") { + REQUIRE(! collection.no_sort); + } + collection.append(collection); + THEN("append ExtrusionEntityCollection") { + REQUIRE(collection.entities.size() == 2); + } + collection.append(path); + THEN("append ExtrusionPath") { + REQUIRE(collection.entities.size() == 3); + } + collection.append(loop); + THEN("append ExtrusionLoop") { + REQUIRE(collection.entities.size() == 4); + } + THEN("appended collection was duplicated") { + REQUIRE(dynamic_cast(collection.entities[1])->entities.size() == 1); + } + WHEN("cloned") { + auto coll2 = std::unique_ptr(dynamic_cast(collection.clone())); + THEN("expected no_sort value") { + assert(! coll2->no_sort); + } + coll2->no_sort = true; + THEN("no_sort is kept after clone") { + auto coll3 = std::unique_ptr(dynamic_cast(coll2->clone())); + assert(coll3->no_sort); + } + } +} + +SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]") +{ srand(0xDEADBEEF); // consistent seed for test reproducibility. // Generate one specific random path set and save it for later comparison @@ -101,7 +348,7 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { { {0,20}, {0,18}, {0,15} }, { {0,10}, {0,8}, {0,5} } }, - { 0,30 } + { 0, 30 } }, { { @@ -112,7 +359,7 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { { {20,5}, {15,5}, {10,5} }, { {15,0}, {10,0}, {4,0} } }, - { 30,0 } + { 30, 0 } }, { { @@ -123,7 +370,7 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { { {20,5}, {15,5}, {10,5} }, { {15,0}, {10,0}, {4,0} } }, - { 30,0 } + { 30, 0 } }, }; for (const Test &test : tests) { @@ -132,12 +379,24 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { ExtrusionEntityCollection unchained_extrusions; extrusion_entities_append_paths(unchained_extrusions.entities, test.unchained, erInternalInfill, 0., 0.4f, 0.3f); - ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); - REQUIRE(chained_extrusions.entities.size() == test.chained.size()); - for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { - const Points &p1 = test.chained[i].points; - const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; - REQUIRE(p1 == p2); + THEN("Chaining works") { + ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); + REQUIRE(chained_extrusions.entities.size() == test.chained.size()); + for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { + const Points &p1 = test.chained[i].points; + const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; + REQUIRE(p1 == p2); + } + } + THEN("Chaining produces no change with no_sort") { + unchained_extrusions.no_sort = true; + ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); + REQUIRE(chained_extrusions.entities.size() == test.unchained.size()); + for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { + const Points &p1 = test.unchained[i].points; + const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; + REQUIRE(p1 == p2); + } } } } diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 75eb16a97f..af59fca2a4 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -3,14 +3,17 @@ #include #include +#include "libslic3r/libslic3r.h" + #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Fill/Fill.hpp" #include "libslic3r/Flow.hpp" +#include "libslic3r/Layer.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Point.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SVG.hpp" -#include "libslic3r/libslic3r.h" #include "test_data.hpp" @@ -329,6 +332,130 @@ SCENARIO("Infill only where needed", "[Fill]") } } +SCENARIO("Combine infill", "[Fill]") +{ + { + auto test = [](const DynamicPrintConfig &config) { + std::string gcode = Test::slice({ Test::TestMesh::cube_20x20x20 }, config); + THEN("infill_every_layers does not crash") { + REQUIRE(! gcode.empty()); + } + + Slic3r::GCodeReader parser; + int tool = -1; + std::set layers; // layer_z => 1 + std::map layer_infill; // layer_z => has_infill + const int infill_extruder = config.opt_int("infill_extruder"); + const int support_material_extruder = config.opt_int("support_material_extruder"); + parser.parse_buffer(gcode, + [&tool, &layers, &layer_infill, infill_extruder, support_material_extruder](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + coord_t z = line.new_Z(self) / SCALING_FACTOR; + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1); + } else if (line.cmd_is("G1") && line.extruding(self) && line.dist_XY(self) > 0 && tool + 1 != support_material_extruder) { + if (tool + 1 == infill_extruder) + layer_infill[z] = true; + else if (auto it = layer_infill.find(z); it == layer_infill.end()) + layer_infill.insert(it, std::make_pair(z, false)); + } + // Previously, all G-code commands had a fixed number of decimal points with means with redundant zeros after decimal points. + // We changed this behavior and got rid of these redundant padding zeros, which caused this test to fail + // because the position in Z-axis is compared as a string, and previously, G-code contained the following two commands: + // "G1 Z5 F5000 ; lift nozzle" + // "G1 Z5.000 F7800.000" + // That has a different Z-axis position from the view of string comparisons of floating-point numbers. + // To correct the computation of the number of printed layers, even in the case of string comparisons of floating-point numbers, + // we filtered out the G-code command with the commend 'lift nozzle'. + if (line.cmd_is("G1") && line.dist_Z(self) != 0 && line.comment().find("lift nozzle") == std::string::npos) + layers.insert(z); + }); + + auto layers_with_perimeters = int(layer_infill.size()); + auto layers_with_infill = int(std::count_if(layer_infill.begin(), layer_infill.end(), [](auto &v){ return v.second; })); + THEN("expected number of layers") { + REQUIRE(layers.size() == layers_with_perimeters + config.opt_int("raft_layers")); + } + + if (config.opt_int("raft_layers") == 0) { + // first infill layer printed directly on print bed is not combined, so we don't consider it. + -- layers_with_infill; + -- layers_with_perimeters; + } + + // we expect that infill is generated for half the number of combined layers + // plus for each single layer that was not combined (remainder) + THEN("infill is only present in correct number of layers") { + int infill_every = config.opt_int("infill_every_layers"); + REQUIRE(layers_with_infill == int(layers_with_perimeters / infill_every) + (layers_with_perimeters % infill_every)); + } + }; + + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "nozzle_diameter", "0.5, 0.5, 0.5, 0.5" }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "infill_every_layers", 2 }, + { "perimeter_extruder", 1 }, + { "infill_extruder", 2 }, + { "wipe_into_infill", false }, + { "support_material_extruder", 3 }, + { "support_material_interface_extruder", 3 }, + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 } + }); + + test(config); + + // Reuse the config above + config.set_deserialize_strict({ + { "skirts", 0 }, // prevent usage of perimeter_extruder in raft layers + { "raft_layers", 5 } + }); + test(config); + } + + WHEN("infill_every_layers == 2") { + Slic3r::Print print; + Slic3r::Test::init_and_process_print({ Test::TestMesh::cube_20x20x20 }, print, { + { "nozzle_diameter", "0.5" }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "infill_every_layers", 2 } + }); + THEN("infill combination produces internal void surfaces") { + bool has_void = false; + for (const Layer *layer : print.get_object(0)->layers()) + if (layer->get_region(0)->fill_surfaces().filter_by_type(stInternalVoid).size() > 0) { + has_void = true; + break; + } + REQUIRE(has_void); + } + } + + WHEN("infill_every_layers disabled") { + // we disable combination after infill has been generated + Slic3r::Print print; + Slic3r::Test::init_and_process_print({ Test::TestMesh::cube_20x20x20 }, print, { + { "nozzle_diameter", "0.5" }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "infill_every_layers", 1 } + }); + + THEN("infill combination is idempotent") { + bool has_infill_on_each_layer = true; + for (const Layer *layer : print.get_object(0)->layers()) + if (layer->get_region(0)->fill_surfaces().empty()) { + has_infill_on_each_layer = false; + break; + } + REQUIRE(has_infill_on_each_layer); + } + } +} + SCENARIO("Infill density zero", "[Fill]") { WHEN("20mm cube is sliced") { diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index 71ab3e6755..d8f0660ca3 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -44,22 +44,31 @@ SCENARIO("Perimeter nesting", "[Perimeters]") ExtrusionEntityCollection loops; ExtrusionEntityCollection gap_fill; - SurfaceCollection fill_surfaces; - PerimeterGenerator perimeter_generator( - &slices, + ExPolygons fill_expolygons; + Flow flow(1., 1., 1.); + PerimeterGenerator::Parameters perimeter_generator_params( 1., // layer height - Flow(1., 1., 1.), - static_cast(&config), - static_cast(&config), - static_cast(&config), - false, // spiral_vase - // output: - &loops, &gap_fill, &fill_surfaces); + -1, // layer ID + flow, flow, flow, flow, + static_cast(config), + static_cast(config), + static_cast(config), + false); // spiral_vase + Polygons lower_layer_polygons_cache; + for (const Surface &surface : slices) // FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation. // if (config.perimeter_generator == PerimeterGeneratorType::Arachne) -// perimeter_generator.process_arachne(); +// PerimeterGenerator::process_arachne(); // else - perimeter_generator.process_classic(); + PerimeterGenerator::process_classic( + // input: + perimeter_generator_params, + surface, + nullptr, + // cache: + lower_layer_polygons_cache, + // output: + loops, gap_fill, fill_expolygons); THEN("expected number of collections") { REQUIRE(loops.entities.size() == data.expolygons.size()); @@ -459,8 +468,10 @@ SCENARIO("Some weird coverage test", "[Perimeters]") object->slice(); Layer *layer = object->get_layer(1); LayerRegion *layerm = layer->get_region(0); - layerm->slices.clear(); - layerm->slices.append({ expolygon }, stInternal); + layerm->m_slices.clear(); + layerm->m_slices.append({ expolygon }, stInternal); + layer->lslices = { expolygon }; + layer->lslices_ex = { { get_extents(expolygon) } }; // make perimeters layer->make_perimeters(); @@ -472,22 +483,22 @@ SCENARIO("Some weird coverage test", "[Perimeters]") Polygons covered_by_infill; { Polygons acc; - for (const ExtrusionEntity *ee : layerm->perimeters.entities) + for (const ExtrusionEntity *ee : layerm->perimeters()) for (const ExtrusionEntity *ee : dynamic_cast(ee)->entities) append(acc, offset(dynamic_cast(ee)->polygon().split_at_first_point(), float(pflow.scaled_width() / 2.f + SCALED_EPSILON))); covered_by_perimeters = union_(acc); } { Polygons acc; - for (const Surface &surface : layerm->fill_surfaces.surfaces) - append(acc, to_polygons(surface.expolygon)); - for (const ExtrusionEntity *ee : layerm->thin_fills.entities) + for (const ExPolygon &expolygon : layerm->fill_expolygons()) + append(acc, to_polygons(expolygon)); + for (const ExtrusionEntity *ee : layerm->thin_fills().entities) append(acc, offset(dynamic_cast(ee)->polyline, float(iflow.scaled_width() / 2.f + SCALED_EPSILON))); covered_by_infill = union_(acc); } // compute the non covered area - ExPolygons non_covered = diff_ex(to_polygons(layerm->slices.surfaces), union_(covered_by_perimeters, covered_by_infill)); + ExPolygons non_covered = diff_ex(to_polygons(layerm->slices().surfaces), union_(covered_by_perimeters, covered_by_infill)); /* if (0) { @@ -506,7 +517,8 @@ SCENARIO("Some weird coverage test", "[Perimeters]") } */ THEN("no gap between perimeters and infill") { - size_t num_non_convered = std::count_if(non_covered.begin(), non_covered.end(), [&iflow](const ExPolygon &ex){ return ex.area() > sqr(double(iflow.scaled_width())); }); + size_t num_non_convered = std::count_if(non_covered.begin(), non_covered.end(), + [&iflow](const ExPolygon &ex){ return ex.area() > sqr(double(iflow.scaled_width())); }); REQUIRE(num_non_convered == 0); } } diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index d8ab5a3faa..204f3f8e0d 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -20,11 +20,11 @@ SCENARIO("PrintObject: Perimeter generation", "[PrintObject]") { } THEN("Every layer in region 0 has 1 island of perimeters") { for (const Layer *layer : object.layers()) - REQUIRE(layer->regions().front()->perimeters.entities.size() == 1); + REQUIRE(layer->regions().front()->perimeters().size() == 1); } THEN("Every layer in region 0 has 3 paths in its perimeters list.") { for (const Layer *layer : object.layers()) - REQUIRE(layer->regions().front()->perimeters.items_count() == 3); + REQUIRE(layer->regions().front()->perimeters().items_count() == 3); } } } @@ -66,7 +66,7 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t // iterate over all of the regions in the layer for (const LayerRegion *region : layer.regions()) { // for each region, iterate over the fill surfaces - for (const Surface &surface : region->fill_surfaces.surfaces) + for (const Surface &surface : region->fill_surfaces()) CHECK(surface.is_solid()); } }; diff --git a/tests/fff_print/test_shells.cpp b/tests/fff_print/test_shells.cpp new file mode 100644 index 0000000000..df0e8f6bb6 --- /dev/null +++ b/tests/fff_print/test_shells.cpp @@ -0,0 +1,112 @@ +#include + +#include "libslic3r/GCodeReader.hpp" + +#include "test_data.hpp" // get access to init_print, etc + +using namespace Slic3r::Test; +using namespace Slic3r; + +SCENARIO("Shells", "[Shells]") { + GIVEN("20mm box") { + auto test = [](const DynamicPrintConfig &config){ + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + + std::vector zs; + std::set layers_with_solid_infill; + std::set layers_with_bridge_infill; + const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60; + const double bridge_speed = config.opt_float("bridge_speed") * 60; + GCodeReader parser; + parser.parse_buffer(gcode, + [&zs, &layers_with_solid_infill, &layers_with_bridge_infill, solid_infill_speed, bridge_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + double z = line.new_Z(self); + REQUIRE(z >= 0); + if (z > 0) { + coord_t scaled_z = scaled(z); + zs.emplace_back(scaled_z); + if (line.extruding(self) && line.dist_XY(self) > 0) { + double f = line.new_F(self); + if (std::abs(f - solid_infill_speed) < EPSILON) + layers_with_solid_infill.insert(scaled_z); + if (std::abs(f - bridge_speed) < EPSILON) + layers_with_bridge_infill.insert(scaled_z); + } + } + }); + sort_remove_duplicates(zs); + + auto has_solid_infill = [&layers_with_solid_infill](coord_t z) { return layers_with_solid_infill.find(z) != layers_with_solid_infill.end(); }; + auto has_bridge_infill = [&layers_with_bridge_infill](coord_t z) { return layers_with_bridge_infill.find(z) != layers_with_bridge_infill.end(); }; + auto has_shells = [&has_solid_infill, &has_bridge_infill, &zs](int layer_idx) { coord_t z = zs[layer_idx]; return has_solid_infill(z) || has_bridge_infill(z); }; + const int bottom_solid_layers = config.opt_int("bottom_solid_layers"); + const int top_solid_layers = config.opt_int("top_solid_layers"); + THEN("correct number of bottom solid layers") { + for (int i = 0; i < bottom_solid_layers; ++ i) + REQUIRE(has_shells(i)); + for (int i = bottom_solid_layers; i < int(zs.size() / 2); ++ i) + REQUIRE(! has_shells(i)); + } + THEN("correct number of top solid layers") { + for (int i = 0; i < top_solid_layers; ++ i) + REQUIRE(has_shells(int(zs.size()) - i - 1)); + for (int i = top_solid_layers; i < int(zs.size() / 2); ++ i) + REQUIRE(! has_shells(int(zs.size()) - i - 1)); + } + if (top_solid_layers > 0) { + THEN("solid infill speed is used on solid infill") { + for (int i = 0; i < top_solid_layers - 1; ++ i) { + auto z = zs[int(zs.size()) - i - 1]; + REQUIRE(has_solid_infill(z)); + REQUIRE(! has_bridge_infill(z)); + } + } + THEN("bridge used in first solid layer over sparse infill") { + auto z = zs[int(zs.size()) - top_solid_layers]; + REQUIRE(! has_solid_infill(z)); + REQUIRE(has_bridge_infill(z)); + } + } + }; + + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 0 }, + { "perimeters", 0 }, + { "solid_infill_speed", 99 }, + { "top_solid_infill_speed", 99 }, + { "bridge_speed", 72 }, + { "first_layer_speed", "100%" }, + { "cooling", "0" } + }); + + WHEN("three top and bottom layers") { + // proper number of shells is applied + config.set_deserialize_strict({ + { "top_solid_layers", 3 }, + { "bottom_solid_layers", 3 } + }); + test(config); + } + + WHEN("zero top and bottom layers") { + // no shells are applied when both top and bottom are set to zero + config.set_deserialize_strict({ + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 } + }); + test(config); + } + + WHEN("three top and bottom layers, zero infill") { + // proper number of shells is applied even when fill density is none + config.set_deserialize_strict({ + { "perimeters", 1 }, + { "top_solid_layers", 3 }, + { "bottom_solid_layers", 3 } + }); + test(config); + } + } +} diff --git a/tests/fff_print/test_thin_walls.cpp b/tests/fff_print/test_thin_walls.cpp index cd80f3bd66..59fb6b080d 100644 --- a/tests/fff_print/test_thin_walls.cpp +++ b/tests/fff_print/test_thin_walls.cpp @@ -16,7 +16,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} }); ExPolygon expolygon{ square, hole_in_square }; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(40.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(40.)); THEN("medial axis of a square shape is a single path") { REQUIRE(res.size() == 1); } @@ -32,7 +32,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { GIVEN("narrow rectangle") { ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) }; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(20.)); THEN("medial axis of a narrow rectangle is a single line") { REQUIRE(res.size() == 1); } @@ -50,7 +50,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { {100, 200} })}; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(1.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(1.)); THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") { REQUIRE(res.size() == 1); } @@ -81,7 +81,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { {1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0} }}; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(1.324888), scaled(0.25)); + Polylines res = expolygon.medial_axis(scaled(0.25), scaled(1.324888)); THEN("medial axis of a semicircumference is a single line") { REQUIRE(res.size() == 1); } @@ -103,7 +103,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { GIVEN("narrow trapezoid") { ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) }; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(20.)); THEN("medial axis of a narrow trapezoid is a single line") { REQUIRE(res.size() == 1); } @@ -115,7 +115,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { GIVEN("L shape") { ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) }; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(20.)); THEN("medial axis of an L shape is a single line") { REQUIRE(res.size() == 1); } @@ -134,7 +134,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { {-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024}, }}; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(819998., 102499.75); + Polylines res = expolygon.medial_axis(102499.75, 819998.); THEN("medial axis is a single line") { REQUIRE(res.size() == 1); } @@ -147,7 +147,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { GIVEN("narrow triangle") { ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) }; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(4.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(4.)); THEN("medial axis of a narrow triangle is a single line") { REQUIRE(res.size() == 1); } @@ -159,7 +159,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { GIVEN("GH #2474") { ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }}; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(1871238, 500000); + Polylines res = expolygon.medial_axis(500000, 1871238); THEN("medial axis is a single line") { REQUIRE(res.size() == 1); } diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 840c34ae90..ae2474ad50 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests test_curve_fitting.cpp test_cut_surface.cpp test_elephant_foot_compensation.cpp + test_expolygon.cpp test_geometry.cpp test_placeholder_parser.cpp test_polygon.cpp diff --git a/tests/libslic3r/test_expolygon.cpp b/tests/libslic3r/test_expolygon.cpp new file mode 100644 index 0000000000..63d763e2f6 --- /dev/null +++ b/tests/libslic3r/test_expolygon.cpp @@ -0,0 +1,67 @@ +#include + +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/ExPolygon.hpp" + +using namespace Slic3r; + +static inline bool points_close(const Point &p1, const Point &p2) +{ + return (p1 - p2).cast().norm() < SCALED_EPSILON; +} + +static bool polygons_close_permuted(const Polygon &poly1, const Polygon &poly2, const std::vector &permutation2) +{ + if (poly1.size() != poly2.size() || poly1.size() != permutation2.size()) + return false; + for (size_t i = 0; i < poly1.size(); ++ i) + if (poly1[i] != poly2[permutation2[i]]) + return false; + return true; +} + +SCENARIO("Basics", "[ExPolygon]") { + GIVEN("ccw_square") { + Polygon ccw_square{ { 100, 100 }, { 200, 100 }, { 200, 200 }, { 100, 200 } }; + Polygon cw_hole_in_square{ { 140, 140 }, { 140, 160 }, { 160, 160 }, { 160, 140 } }; + ExPolygon expolygon { ccw_square, cw_hole_in_square }; + THEN("expolygon is valid") { + REQUIRE(expolygon.is_valid()); + } + THEN("expolygon area") { + REQUIRE(expolygon.area() == Approx(100*100-20*20)); + } + WHEN("Expolygon scaled") { + ExPolygon expolygon2 = expolygon; + expolygon2.scale(2.5); + REQUIRE(expolygon.contour.size() == expolygon2.contour.size()); + REQUIRE(expolygon.holes.size() == 1); + REQUIRE(expolygon2.holes.size() == 1); + for (size_t i = 0; i < expolygon.contour.size(); ++ i) + REQUIRE(points_close(expolygon.contour[i] * 2.5, expolygon2.contour[i])); + for (size_t i = 0; i < expolygon.holes.front().size(); ++ i) + REQUIRE(points_close(expolygon.holes.front()[i] * 2.5, expolygon2.holes.front()[i])); + } + WHEN("Expolygon translated") { + ExPolygon expolygon2 = expolygon; + expolygon2.translate(10, -5); + REQUIRE(expolygon.contour.size() == expolygon2.contour.size()); + REQUIRE(expolygon.holes.size() == 1); + REQUIRE(expolygon2.holes.size() == 1); + for (size_t i = 0; i < expolygon.contour.size(); ++ i) + REQUIRE(points_close(expolygon.contour[i] + Point(10, -5), expolygon2.contour[i])); + for (size_t i = 0; i < expolygon.holes.front().size(); ++ i) + REQUIRE(points_close(expolygon.holes.front()[i] + Point(10, -5), expolygon2.holes.front()[i])); + } + WHEN("Expolygon rotated around point") { + ExPolygon expolygon2 = expolygon; + expolygon2.rotate(M_PI / 2, Point(150, 150)); + REQUIRE(expolygon.contour.size() == expolygon2.contour.size()); + REQUIRE(expolygon.holes.size() == 1); + REQUIRE(expolygon2.holes.size() == 1); + REQUIRE(polygons_close_permuted(expolygon2.contour, expolygon.contour, { 1, 2, 3, 0})); + REQUIRE(polygons_close_permuted(expolygon2.holes.front(), expolygon.holes.front(), { 3, 0, 1, 2})); + } + } +} diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index fb36541dde..967cb0e96a 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -1918,7 +1918,6 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]") Lines lines = to_lines(poly); construct_voronoi(lines.begin(), lines.end(), &vd); Slic3r::Voronoi::annotate_inside_outside(vd, lines); - Slic3r::Voronoi::annotate_inside_outside(vd, lines); static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees std::vector skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index efc07af312..b16f221b99 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -43,22 +43,15 @@ set(XS_MAIN_XS ${CMAKE_CURRENT_BINARY_DIR}/main.xs) set(XSP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/xsp) #FIXME list the dependecies explicitely, add dependency on the typemap. set(XS_XSP_FILES - ${XSP_DIR}/BoundingBox.xsp ${XSP_DIR}/Config.xsp ${XSP_DIR}/ExPolygon.xsp - ${XSP_DIR}/ExtrusionEntityCollection.xsp - ${XSP_DIR}/ExtrusionLoop.xsp - ${XSP_DIR}/ExtrusionPath.xsp ${XSP_DIR}/Geometry.xsp - ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp ${XSP_DIR}/Model.xsp ${XSP_DIR}/Point.xsp ${XSP_DIR}/Polygon.xsp ${XSP_DIR}/Polyline.xsp ${XSP_DIR}/Print.xsp - ${XSP_DIR}/Surface.xsp - ${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/TriangleMesh.xsp ${XSP_DIR}/XS.xsp ) diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 87fb267c51..60c9a9316d 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -48,61 +48,6 @@ use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; -package Slic3r::ExtrusionPath::Collection; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -sub new { - my ($class, @paths) = @_; - - my $self = $class->_new; - $self->append(@paths); - return $self; -} - -package Slic3r::ExtrusionLoop; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -sub new_from_paths { - my ($class, @paths) = @_; - - my $loop = $class->new; - $loop->append($_) for @paths; - return $loop; -} - -package Slic3r::ExtrusionPath; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -sub new { - my ($class, %args) = @_; - - return $class->_new( - $args{polyline}, # required - $args{role}, # required - $args{mm3_per_mm} // die("Missing required mm3_per_mm in ExtrusionPath constructor"), - $args{width} // -1, - $args{height} // -1, - ); -} - -sub clone { - my ($self, %args) = @_; - - return __PACKAGE__->_new( - $args{polyline} // $self->polyline, - $args{role} // $self->role, - $args{mm3_per_mm} // $self->mm3_per_mm, - $args{width} // $self->width, - $args{height} // $self->height, - ); -} - package Slic3r::Surface; sub new { @@ -155,12 +100,6 @@ for my $class (qw( Slic3r::Config::Print Slic3r::Config::Static Slic3r::ExPolygon - Slic3r::ExtrusionLoop - Slic3r::ExtrusionPath - Slic3r::ExtrusionPath::Collection - Slic3r::Geometry::BoundingBox - Slic3r::Layer - Slic3r::Layer::Region Slic3r::Line Slic3r::Model Slic3r::Model::Instance @@ -175,10 +114,6 @@ for my $class (qw( Slic3r::Polyline Slic3r::Polyline::Collection Slic3r::Print - Slic3r::Print::Object - Slic3r::Print::Region - Slic3r::Surface - Slic3r::Surface::Collection Slic3r::TriangleMesh )) { diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 8484b1d64f..3f75617dd5 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -4,12 +4,7 @@ namespace Slic3r { REGISTER_CLASS(ExPolygon, "ExPolygon"); -REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); -REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); -REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); REGISTER_CLASS(GCode, "GCode"); -REGISTER_CLASS(Layer, "Layer"); -REGISTER_CLASS(LayerRegion, "Layer::Region"); REGISTER_CLASS(Line, "Line"); REGISTER_CLASS(Polygon, "Polygon"); REGISTER_CLASS(Polyline, "Polyline"); diff --git a/xs/t/03_point.t b/xs/t/03_point.t index f888349b3d..389e120c75 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 21; +use Test::More tests => 16; my $point = Slic3r::Point->new(10, 15); @@ -59,22 +59,4 @@ is_deeply [ @$point2 ], [30, 15], 'translate'; ok $p0->ccw($p1, $p2) < 0, 'ccw() does not overflow'; } -{ - my $point = Slic3r::Point->new(15,15); - my $line = Slic3r::Line->new([10,10], [20,10]); - is_deeply $point->projection_onto_line($line)->pp, [15,10], 'project_onto_line'; - - $point = Slic3r::Point->new(0, 15); - is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; - - $point = Slic3r::Point->new(25, 15); - is_deeply $point->projection_onto_line($line)->pp, [20,10], 'project_onto_line'; - - $point = Slic3r::Point->new(10,10); - is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; - - $point = Slic3r::Point->new(12, 10); - is_deeply $point->projection_onto_line($line)->pp, [12,10], 'project_onto_line'; -} - __END__ diff --git a/xs/t/04_expolygon.t b/xs/t/04_expolygon.t deleted file mode 100644 index 9132c44b96..0000000000 --- a/xs/t/04_expolygon.t +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use List::Util qw(first sum); -use Slic3r::XS; -use Test::More tests => 7; - -use constant PI => 4 * atan2(1, 1); - -my $square = [ # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -]; -my $hole_in_square = [ # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], -]; - -my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - -ok $expolygon->is_valid, 'is_valid'; - -is_deeply $expolygon->clone->pp, [$square, $hole_in_square], 'clone'; - -is $expolygon->area, 100*100-20*20, 'area'; - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->scale(2.5); - is_deeply $expolygon2->pp, [ - [map [ 2.5*$_->[0], 2.5*$_->[1] ], @$square], - [map [ 2.5*$_->[0], 2.5*$_->[1] ], @$hole_in_square] - ], 'scale'; -} - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->translate(10, -5); - is_deeply $expolygon2->pp, [ - [map [ $_->[0]+10, $_->[1]-5 ], @$square], - [map [ $_->[0]+10, $_->[1]-5 ], @$hole_in_square] - ], 'translate'; -} - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->rotate(PI/2, Slic3r::Point->new(150,150)); - is_deeply $expolygon2->pp, [ - [ @$square[1,2,3,0] ], - [ @$hole_in_square[3,0,1,2] ] - ], 'rotate around Point'; -} - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->rotate(PI/2, [150,150]); - is_deeply $expolygon2->pp, [ - [ @$square[1,2,3,0] ], - [ @$hole_in_square[3,0,1,2] ] - ], 'rotate around pure-Perl Point'; -} - -__END__ diff --git a/xs/t/05_surface.t b/xs/t/05_surface.t deleted file mode 100644 index 4d9eb5b891..0000000000 --- a/xs/t/05_surface.t +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 11; - -my $square = [ # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -]; -my $hole_in_square = [ # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], -]; - -my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); -my $surface = Slic3r::Surface->new( - expolygon => $expolygon, - surface_type => Slic3r::Surface::S_TYPE_INTERNAL, -); - -$surface = $surface->clone; - -is $surface->surface_type, Slic3r::Surface::S_TYPE_INTERNAL, 'surface_type'; -$surface->surface_type(Slic3r::Surface::S_TYPE_BOTTOM); -is $surface->surface_type, Slic3r::Surface::S_TYPE_BOTTOM, 'modify surface_type'; - -$surface->bridge_angle(30); -is $surface->bridge_angle, 30, 'bridge_angle'; - -$surface->extra_perimeters(2); -is $surface->extra_perimeters, 2, 'extra_perimeters'; - -{ - my $surface2 = $surface->clone; - $surface2->expolygon->scale(2); - isnt $surface2->expolygon->area, $expolygon->area, 'expolygon is returned by reference'; -} - -{ - my $collection = Slic3r::Surface::Collection->new; - $collection->append($_) for $surface, $surface->clone; - is scalar(@$collection), 2, 'collection has the right number of items'; - is_deeply $collection->[0]->expolygon->pp, [$square, $hole_in_square], - 'collection returns a correct surface expolygon'; - $collection->clear; - is scalar(@$collection), 0, 'clear collection'; - $collection->append($surface); - is scalar(@$collection), 1, 'append to collection'; - - my $item = $collection->[0]; - $item->surface_type(Slic3r::Surface::S_TYPE_INTERNAL); - is $item->surface_type, $collection->[0]->surface_type, 'collection returns items by reference'; -} - -{ - my $collection = Slic3r::Surface::Collection->new; - $collection->append($_) for - Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_BOTTOM), - Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_BOTTOM), - Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_TOP); - is scalar(@{$collection->group}), 2, 'group() returns correct number of groups'; -} - -__END__ diff --git a/xs/t/07_extrusionpath.t b/xs/t/07_extrusionpath.t deleted file mode 100644 index 084b4f03ea..0000000000 --- a/xs/t/07_extrusionpath.t +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 5; - -my $points = [ - [100, 100], - [200, 100], - [200, 200], -]; - -my $path = Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@$points), - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => 1, -); - -$path->reverse; -is_deeply $path->polyline->pp, [ reverse @$points ], 'reverse path'; - -$path->append([ 150, 150 ]); -is scalar(@$path), 4, 'append to path'; - -$path->pop_back; -is scalar(@$path), 3, 'pop_back from path'; - -ok $path->first_point->coincides_with($path->polyline->[0]), 'first_point'; - -$path = $path->clone; - -is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; - -__END__ diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t deleted file mode 100644 index 3abfbd728a..0000000000 --- a/xs/t/08_extrusionloop.t +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use List::Util qw(sum); -use Slic3r::XS; -use Test::More tests => 46; - -{ - my $square = [ - [100, 100], - [200, 100], - [200, 200], - [100, 200], - ]; - my $square_p = Slic3r::Polygon->new(@$square); - - my $loop = Slic3r::ExtrusionLoop->new; - $loop->append(Slic3r::ExtrusionPath->new( - polyline => $square_p->split_at_first_point, - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => 1, - )); - - isa_ok $loop, 'Slic3r::ExtrusionLoop'; - isa_ok $loop->polygon, 'Slic3r::Polygon', 'loop polygon'; - is $loop->polygon->area, $square_p->area, 'polygon area'; - is $loop->length, $square_p->length(), 'loop length'; - - $loop = $loop->clone; - - is scalar(@$loop), 1, 'loop contains one path'; - { - my $path = $loop->[0]; - is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; - } - - $loop->split_at_vertex($square_p->[2]); - is scalar(@$loop), 1, 'splitting a single-path loop results in a single path'; - is scalar(@{$loop->[0]->polyline}), 5, 'path has correct number of points'; - ok $loop->[0]->polyline->[0]->coincides_with($square_p->[2]), 'expected point order'; - ok $loop->[0]->polyline->[1]->coincides_with($square_p->[3]), 'expected point order'; - ok $loop->[0]->polyline->[2]->coincides_with($square_p->[0]), 'expected point order'; - ok $loop->[0]->polyline->[3]->coincides_with($square_p->[1]), 'expected point order'; - ok $loop->[0]->polyline->[4]->coincides_with($square_p->[2]), 'expected point order'; -} - -{ - my $polyline1 = Slic3r::Polyline->new([100,100], [200,100], [200,200]); - my $polyline2 = Slic3r::Polyline->new([200,200], [100,200], [100,100]); - - my $loop = Slic3r::ExtrusionLoop->new_from_paths( - Slic3r::ExtrusionPath->new( - polyline => $polyline1, - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => 1, - ), - Slic3r::ExtrusionPath->new( - polyline => $polyline2, - role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, - mm3_per_mm => 1, - ), - ); - my $tot_len = sum($polyline1->length, $polyline2->length); - is $loop->length, $tot_len, 'length'; - is scalar(@$loop), 2, 'loop contains two paths'; - - { - # check splitting at intermediate point - my $loop2 = $loop->clone; - isa_ok $loop2, 'Slic3r::ExtrusionLoop'; - $loop2->split_at_vertex($polyline1->[1]); - is $loop2->length, $tot_len, 'length after splitting is unchanged'; - is scalar(@$loop2), 3, 'loop contains three paths after splitting'; - ok $loop2->[0]->polyline->[0]->coincides_with($polyline1->[1]), 'expected starting point'; - ok $loop2->[-1]->polyline->[-1]->coincides_with($polyline1->[1]), 'expected ending point'; - ok $loop2->[0]->polyline->[-1]->coincides_with($loop2->[1]->polyline->[0]), 'paths have common point'; - ok $loop2->[1]->polyline->[-1]->coincides_with($loop2->[2]->polyline->[0]), 'paths have common point'; - is $loop2->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; - is $loop2->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; - is $loop2->[2]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; - is scalar(@{$loop2->[0]->polyline}), 2, 'path has correct number of points'; - is scalar(@{$loop2->[1]->polyline}), 3, 'path has correct number of points'; - is scalar(@{$loop2->[2]->polyline}), 2, 'path has correct number of points'; - - my @paths = @{$loop2->clip_end(3)}; - is sum(map $_->length, @paths), $loop2->length - 3, 'returned paths have expected length'; - } - - { - # check splitting at endpoint - my $loop2 = $loop->clone; - $loop2->split_at_vertex($polyline2->[0]); - is $loop2->length, $tot_len, 'length after splitting is unchanged'; - is scalar(@$loop2), 2, 'loop contains two paths after splitting'; - ok $loop2->[0]->polyline->[0]->coincides_with($polyline2->[0]), 'expected starting point'; - ok $loop2->[-1]->polyline->[-1]->coincides_with($polyline2->[0]), 'expected ending point'; - ok $loop2->[0]->polyline->[-1]->coincides_with($loop2->[1]->polyline->[0]), 'paths have common point'; - ok $loop2->[1]->polyline->[-1]->coincides_with($loop2->[0]->polyline->[0]), 'paths have common point'; - is $loop2->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; - is $loop2->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; - is scalar(@{$loop2->[0]->polyline}), 3, 'path has correct number of points'; - is scalar(@{$loop2->[1]->polyline}), 3, 'path has correct number of points'; - } - - { - my $loop2 = $loop->clone; - my $point = Slic3r::Point->new(250,150); - $loop2->split_at($point); - is $loop2->length, $tot_len, 'length after splitting is unchanged'; - is scalar(@$loop2), 3, 'loop contains three paths after splitting'; - my $expected_start_point = Slic3r::Point->new(200,150); - ok $loop2->[0]->polyline->[0]->coincides_with($expected_start_point), 'expected starting point'; - ok $loop2->[-1]->polyline->[-1]->coincides_with($expected_start_point), 'expected ending point'; - } -} - -{ - my @polylines = ( - Slic3r::Polyline->new([59312736,4821067],[64321068,4821067],[64321068,4821067],[64321068,9321068],[59312736,9321068]), - Slic3r::Polyline->new([59312736,9321068],[9829401,9321068]), - Slic3r::Polyline->new([9829401,9321068],[4821067,9321068],[4821067,4821067],[9829401,4821067]), - Slic3r::Polyline->new([9829401,4821067],[59312736,4821067]), - ); - my $loop = Slic3r::ExtrusionLoop->new; - $loop->append($_) for ( - Slic3r::ExtrusionPath->new(polyline => $polylines[0], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), - Slic3r::ExtrusionPath->new(polyline => $polylines[1], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), - Slic3r::ExtrusionPath->new(polyline => $polylines[2], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), - Slic3r::ExtrusionPath->new(polyline => $polylines[3], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), - ); - my $len = $loop->length; - my $point = Slic3r::Point->new(4821067,9321068); - $loop->split_at_vertex($point) or $loop->split_at($point); - is $loop->length, $len, 'total length is preserved after splitting'; - is_deeply [ map $_->role, @$loop ], [ - Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, - Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, - Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - ], 'order is correctly preserved after splitting'; -} - -{ - my $loop = Slic3r::ExtrusionLoop->new; - $loop->append(Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new([15896783,15868739],[24842049,12117558],[33853238,15801279],[37591780,24780128],[37591780,24844970],[33853231,33825297],[24842049,37509013],[15896798,33757841],[12211841,24812544],[15896783,15868739]), - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1 - )); - my $len = $loop->length; - $loop->split_at(Slic3r::Point->new(15896783,15868739)); - is $loop->length, $len, 'split_at() preserves total length'; -} - -__END__ diff --git a/xs/t/12_extrusionpathcollection.t b/xs/t/12_extrusionpathcollection.t deleted file mode 100644 index e028542451..0000000000 --- a/xs/t/12_extrusionpathcollection.t +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 13; - -my $points = [ - [100, 100], - [200, 100], - [200, 200], -]; - -my $path = Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@$points), - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => 1, -); - -my $loop = Slic3r::ExtrusionLoop->new_from_paths( - Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polygon->new(@$points)->split_at_first_point, - role => Slic3r::ExtrusionPath::EXTR_ROLE_FILL, - mm3_per_mm => 1, - ), -); - -my $collection = Slic3r::ExtrusionPath::Collection->new( - $path, -); -isa_ok $collection, 'Slic3r::ExtrusionPath::Collection', 'collection object with items in constructor'; -ok !$collection->no_sort, 'no_sort is false by default'; - -$collection->append($collection); -is scalar(@$collection), 2, 'append ExtrusionPath::Collection'; - -$collection->append($path); -is scalar(@$collection), 3, 'append ExtrusionPath'; - -$collection->append($loop); -is scalar(@$collection), 4, 'append ExtrusionLoop'; - -is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; - -{ - my $collection_loop = $collection->[3]; - $collection_loop->polygon->scale(2); - is_deeply $collection->[3]->polygon->pp, $collection_loop->polygon->pp, 'items are returned by reference'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->y, map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained_path_from'; - is_deeply - [ map $_->y, map @{$_->polyline}, @{$collection->chained_path(0)} ], - [15, 18, 20, 10, 8, 5], - 'chained_path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([15,0], [10,0], [4,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->x, map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained_path_from'; - - $collection->no_sort(1); - my @foo = @{$collection->chained_path(0)}; - pass 'chained_path with no_sort'; -} - -{ - my $coll2 = $collection->clone; - ok !$coll2->no_sort, 'expected no_sort value'; - $coll2->no_sort(1); - ok $coll2->clone->no_sort, 'no_sort is kept after clone'; -} - -__END__ diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp deleted file mode 100644 index 75592e7c38..0000000000 --- a/xs/xsp/BoundingBox.xsp +++ /dev/null @@ -1,52 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/Point.hpp" -%} - -%name{Slic3r::Geometry::BoundingBox} class BoundingBox { - BoundingBox(); - ~BoundingBox(); - Clone clone() - %code{% RETVAL = THIS; %}; - void merge(BoundingBox* bb) %code{% THIS->merge(*bb); %}; - void merge_point(Point* point) %code{% THIS->merge(*point); %}; - void scale(double factor); - void translate(double x, double y); - void offset(double delta); - bool contains_point(Point* point) %code{% RETVAL = THIS->contains(*point); %}; - bool overlap(BoundingBox* bbox) %code{% RETVAL = THIS->overlap(*bbox); %}; - Clone polygon(); - Clone size(); - Clone center(); - bool empty() %code{% RETVAL = empty(*THIS); %}; - double radius(); - Clone min_point() %code{% RETVAL = THIS->min; %}; - Clone max_point() %code{% RETVAL = THIS->max; %}; - int x_min() %code{% RETVAL = THIS->min(0); %}; - int x_max() %code{% RETVAL = THIS->max(0); %}; - int y_min() %code{% RETVAL = THIS->min(1); %}; - int y_max() %code{% RETVAL = THIS->max(1); %}; - void set_x_min(double val) %code{% THIS->min(0) = val; %}; - void set_x_max(double val) %code{% THIS->max(0) = val; %}; - void set_y_min(double val) %code{% THIS->min(1) = val; %}; - void set_y_max(double val) %code{% THIS->max(1) = val; %}; - std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld;%ld,%ld", THIS->min(0), THIS->min(1), THIS->max(0), THIS->max(1)); RETVAL = buf; %}; - bool defined() %code{% RETVAL = THIS->defined; %}; - -%{ - -BoundingBox* -new_from_points(CLASS, points) - char* CLASS - Points points - CODE: - RETVAL = new BoundingBox(points); - OUTPUT: - RETVAL - -%} -}; - diff --git a/xs/xsp/ExtrusionEntityCollection.xsp b/xs/xsp/ExtrusionEntityCollection.xsp deleted file mode 100644 index 1c33373037..0000000000 --- a/xs/xsp/ExtrusionEntityCollection.xsp +++ /dev/null @@ -1,102 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExtrusionEntityCollection.hpp" -%} - -%name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection { - %name{_new} ExtrusionEntityCollection(); - ~ExtrusionEntityCollection(); - Clone clone() - %code{% RETVAL = (ExtrusionEntityCollection*)THIS->clone(); %}; - void reverse(); - void clear(); - ExtrusionEntityCollection* chained_path(bool no_reverse, ExtrusionRole role = erMixed) - %code{% - if (no_reverse) - croak("no_reverse must be false"); - RETVAL = new ExtrusionEntityCollection(); - *RETVAL = THIS->chained_path_from(THIS->entities.front()->first_point()); - %}; - ExtrusionEntityCollection* chained_path_from(Point* start_near, bool no_reverse, ExtrusionRole role = erMixed) - %code{% - if (no_reverse) - croak("no_reverse must be false"); - RETVAL = new ExtrusionEntityCollection(); - *RETVAL = THIS->chained_path_from(*start_near, role); - %}; - Clone first_point(); - Clone last_point(); - int count() - %code{% RETVAL = THIS->entities.size(); %}; - int items_count() - %code{% RETVAL = THIS->items_count(); %}; - ExtrusionEntityCollection* flatten() - %code{% - RETVAL = new ExtrusionEntityCollection(); - *RETVAL = THIS->flatten(); - %}; - double min_mm3_per_mm(); - bool empty() - %code{% RETVAL = THIS->entities.empty(); %}; - Polygons polygons_covered_by_width(); - Polygons polygons_covered_by_spacing(); -%{ - -SV* -ExtrusionEntityCollection::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->entities.size()-1); - int i = 0; - for (ExtrusionEntitiesPtr::iterator it = THIS->entities.begin(); it != THIS->entities.end(); ++it) { - SV* sv = newSV(0); - // return our item by reference - if (ExtrusionPath* path = dynamic_cast(*it)) { - sv_setref_pv( sv, perl_class_name_ref(path), path ); - } else if (ExtrusionLoop* loop = dynamic_cast(*it)) { - sv_setref_pv( sv, perl_class_name_ref(loop), loop ); - } else if (ExtrusionEntityCollection* collection = dynamic_cast(*it)) { - sv_setref_pv( sv, perl_class_name_ref(collection), collection ); - } else { - croak("Unexpected type in ExtrusionEntityCollection"); - } - av_store(av, i++, sv); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -void -ExtrusionEntityCollection::append(...) - CODE: - for (unsigned int i = 1; i < items; i++) { - if(!sv_isobject( ST(i) ) || (SvTYPE(SvRV( ST(i) )) != SVt_PVMG)) { - croak("Argument %d is not object", i); - } - ExtrusionEntity* entity = (ExtrusionEntity *)SvIV((SV*)SvRV( ST(i) )); - // append COPIES - if (ExtrusionPath* path = dynamic_cast(entity)) { - THIS->entities.push_back( new ExtrusionPath(*path) ); - } else if (ExtrusionLoop* loop = dynamic_cast(entity)) { - THIS->entities.push_back( new ExtrusionLoop(*loop) ); - } else if(ExtrusionEntityCollection* collection = dynamic_cast(entity)) { - THIS->entities.push_back( collection->clone() ); - } else { - croak("Argument %d is of unknown type", i); - } - } - -bool -ExtrusionEntityCollection::no_sort(...) - CODE: - if (items > 1) { - THIS->no_sort = SvTRUE(ST(1)); - } - RETVAL = THIS->no_sort; - OUTPUT: - RETVAL - -%} -}; diff --git a/xs/xsp/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp deleted file mode 100644 index ded17cb19b..0000000000 --- a/xs/xsp/ExtrusionLoop.xsp +++ /dev/null @@ -1,65 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExtrusionEntity.hpp" -%} - -%name{Slic3r::ExtrusionLoop} class ExtrusionLoop { - ExtrusionLoop(); - ~ExtrusionLoop(); - Clone clone() - %code{% RETVAL = THIS; %}; - void reverse(); - bool make_clockwise(); - bool make_counter_clockwise(); - Clone first_point(); - Clone last_point(); - Clone polygon(); - void append(ExtrusionPath* path) - %code{% THIS->paths.push_back(*path); %}; - double length(); - bool split_at_vertex(Point* point) - %code{% RETVAL = THIS->split_at_vertex(*point); %}; - void split_at(Point* point, int prefer_non_overhang = 0, double scaled_epsilon = 0.) - %code{% THIS->split_at(*point, prefer_non_overhang != 0, scaled_epsilon); %}; - ExtrusionPaths clip_end(double distance) - %code{% THIS->clip_end(distance, &RETVAL); %}; - bool has_overhang_point(Point* point) - %code{% RETVAL = THIS->has_overhang_point(*point); %}; - ExtrusionRole role() const; - ExtrusionLoopRole loop_role() const; - Polygons polygons_covered_by_width(); - Polygons polygons_covered_by_spacing(); -%{ - -SV* -ExtrusionLoop::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->paths.size()-1); - for (ExtrusionPaths::iterator it = THIS->paths.begin(); it != THIS->paths.end(); ++it) { - av_store(av, it - THIS->paths.begin(), perl_to_SV_ref(*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -%} -}; - -%package{Slic3r::ExtrusionLoop}; -%{ - -IV -_constant() - ALIAS: - EXTRL_ROLE_DEFAULT = elrDefault - EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER = elrContourInternalPerimeter - EXTRL_ROLE_SKIRT = elrSkirt - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} diff --git a/xs/xsp/ExtrusionPath.xsp b/xs/xsp/ExtrusionPath.xsp deleted file mode 100644 index 1dbc009174..0000000000 --- a/xs/xsp/ExtrusionPath.xsp +++ /dev/null @@ -1,126 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/ExtrusionEntityCollection.hpp" -%} - -%name{Slic3r::ExtrusionPath} class ExtrusionPath { - ~ExtrusionPath(); - SV* arrayref() - %code{% RETVAL = to_AV(&THIS->polyline); %}; - SV* pp() - %code{% RETVAL = to_SV_pureperl(&THIS->polyline); %}; - void pop_back() - %code{% THIS->polyline.points.pop_back(); %}; - void reverse(); - Lines lines() - %code{% RETVAL = THIS->polyline.lines(); %}; - Clone first_point(); - Clone last_point(); - void clip_end(double distance); - void simplify(double tolerance); - double length(); - ExtrusionRole role() const; - bool is_bridge() - %code{% RETVAL = is_bridge(THIS->role()); %}; - Polygons polygons_covered_by_width(); - Polygons polygons_covered_by_spacing(); -%{ - -ExtrusionPath* -_new(CLASS, polyline_sv, role, mm3_per_mm, width, height) - char* CLASS; - SV* polyline_sv; - ExtrusionRole role; - double mm3_per_mm; - float width; - float height; - CODE: - RETVAL = new ExtrusionPath (role); - from_SV_check(polyline_sv, &RETVAL->polyline); - RETVAL->mm3_per_mm = mm3_per_mm; - RETVAL->width = width; - RETVAL->height = height; - OUTPUT: - RETVAL - -Ref -ExtrusionPath::polyline(...) - CODE: - if (items > 1) { - from_SV_check(ST(1), &THIS->polyline); - } - RETVAL = &(THIS->polyline); - OUTPUT: - RETVAL - -double -ExtrusionPath::mm3_per_mm(...) - CODE: - if (items > 1) { - THIS->mm3_per_mm = (double)SvNV(ST(1)); - } - RETVAL = THIS->mm3_per_mm; - OUTPUT: - RETVAL - -float -ExtrusionPath::width(...) - CODE: - if (items > 1) { - THIS->width = (float)SvNV(ST(1)); - } - RETVAL = THIS->width; - OUTPUT: - RETVAL - -float -ExtrusionPath::height(...) - CODE: - if (items > 1) { - THIS->height = (float)SvNV(ST(1)); - } - RETVAL = THIS->height; - OUTPUT: - RETVAL - -void -ExtrusionPath::append(...) - CODE: - for (unsigned int i = 1; i < items; i++) { - Point p; - from_SV_check(ST(i), &p); - THIS->polyline.points.push_back(p); - } - -%} -}; - -%package{Slic3r::ExtrusionPath}; -%{ - -IV -_constant() - ALIAS: - EXTR_ROLE_NONE = erNone - EXTR_ROLE_PERIMETER = erPerimeter - EXTR_ROLE_EXTERNAL_PERIMETER = erExternalPerimeter - EXTR_ROLE_OVERHANG_PERIMETER = erOverhangPerimeter - EXTR_ROLE_FILL = erInternalInfill - EXTR_ROLE_SOLIDFILL = erSolidInfill - EXTR_ROLE_TOPSOLIDFILL = erTopSolidInfill - EXTR_ROLE_BRIDGE = erBridgeInfill - EXTR_ROLE_GAPFILL = erGapFill - EXTR_ROLE_SKIRT = erSkirt - EXTR_ROLE_SUPPORTMATERIAL = erSupportMaterial - EXTR_ROLE_SUPPORTMATERIAL_INTERFACE = erSupportMaterialInterface - EXTR_ROLE_MIXED = erMixed - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} - diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp deleted file mode 100644 index e42486985d..0000000000 --- a/xs/xsp/Layer.xsp +++ /dev/null @@ -1,72 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Layer.hpp" -%} - -%name{Slic3r::Layer::Region} class LayerRegion { - // owned by Layer, no constructor/destructor - - Ref layer(); - Ref region() - %code%{ RETVAL = &THIS->region(); %}; - - Ref slices() - %code%{ RETVAL = &THIS->slices; %}; - Ref thin_fills() - %code%{ RETVAL = &THIS->thin_fills; %}; - Ref fill_surfaces() - %code%{ RETVAL = &THIS->fill_surfaces; %}; - Ref perimeters() - %code%{ RETVAL = &THIS->perimeters; %}; - Ref fills() - %code%{ RETVAL = &THIS->fills; %}; - - void prepare_fill_surfaces(); - void make_perimeters(SurfaceCollection* slices, SurfaceCollection* fill_surfaces) - %code%{ THIS->make_perimeters(*slices, fill_surfaces); %}; - double infill_area_threshold(); - - void export_region_slices_to_svg(const char *path) const; - void export_region_fill_surfaces_to_svg(const char *path) const; - void export_region_slices_to_svg_debug(const char *name) const; - void export_region_fill_surfaces_to_svg_debug(const char *name) const; -}; - -%name{Slic3r::Layer} class Layer { - // owned by PrintObject, no constructor/destructor - - Ref as_layer() - %code%{ RETVAL = THIS; %}; - - int id(); - void set_id(int id); - Ref object(); - bool slicing_errors() - %code%{ RETVAL = THIS->slicing_errors; %}; - coordf_t slice_z() - %code%{ RETVAL = THIS->slice_z; %}; - coordf_t print_z() - %code%{ RETVAL = THIS->print_z; %}; - coordf_t height() - %code%{ RETVAL = THIS->height; %}; - - size_t region_count(); - Ref get_region(int idx); - Ref add_region(PrintRegion* print_region); - - int ptr() - %code%{ RETVAL = (int)(intptr_t)THIS; %}; - - void make_slices(); - void backup_untyped_slices(); - void restore_untyped_slices(); - void make_perimeters(); - void make_fills(); - - void export_region_slices_to_svg(const char *path); - void export_region_fill_surfaces_to_svg(const char *path); - void export_region_slices_to_svg_debug(const char *name); - void export_region_fill_surfaces_to_svg_debug(const char *name); -}; diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index 2d70e02036..0d44ea3644 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -29,9 +29,8 @@ %code{% (*THIS)(0) = val; %}; void set_y(int val) %code{% (*THIS)(1) = val; %}; - int nearest_point_index(Points points); Clone nearest_point(Points points) - %code{% Point p; THIS->nearest_point(points, &p); RETVAL = p; %}; + %code{% RETVAL = nearest_point(points, *THIS).first; %}; double distance_to(Point* point) %code{% RETVAL = (*point - *THIS).cast().norm(); %}; double distance_to_line(Line* line) @@ -40,8 +39,6 @@ %code{% RETVAL = line->perp_distance_to(*THIS); %}; double ccw(Point* p1, Point* p2) %code{% RETVAL = cross2((*p1 - *THIS).cast(), (*p2 - *p1).cast()); %}; - Point* projection_onto_line(Line* line) - %code{% RETVAL = new Point(THIS->projection_onto(*line)); %}; Point* negative() %code{% RETVAL = new Point(- *THIS); %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld", (*THIS)(0), (*THIS)(1)); RETVAL = buf; %}; diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index 873c4bcc93..7b1fbb83c7 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -2,9 +2,7 @@ %{ #include -#include "libslic3r/BoundingBox.hpp" #include "libslic3r/Polygon.hpp" -#include "libslic3r/BoundingBox.hpp" %} %name{Slic3r::Polygon} class Polygon { @@ -35,7 +33,6 @@ %code{% RETVAL = THIS->contains(*point); %}; Polygons simplify(double tolerance); Clone centroid(); - Clone bounding_box(); Clone first_intersection(Line* line) %code{% Point p; diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index 3534e329ed..595d54ec33 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -2,7 +2,6 @@ %{ #include -#include "libslic3r/BoundingBox.hpp" #include "libslic3r/Polyline.hpp" %} @@ -31,7 +30,6 @@ void simplify(double tolerance); void split_at(Point* point, Polyline* p1, Polyline* p2) %code{% THIS->split_at(*point, p1, p2); %}; - Clone bounding_box(); %{ Polyline* diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 6b6f6e9be2..584a2c1003 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -5,28 +5,6 @@ #include "libslic3r/Print.hpp" %} -%name{Slic3r::Print::Region} class PrintRegion { - // owned by Print, no constructor/destructor - - Ref config() - %code%{ RETVAL = &THIS->config(); %}; -}; - -%name{Slic3r::Print::Object} class PrintObject { - // owned by Print, no constructor/destructor - - Ref print(); - Ref model_object(); - Ref config() - %code%{ RETVAL = &THIS->config(); %}; - Clone bounding_box(); - - size_t layer_count(); - Ref get_layer(int idx); - - void slice(); -}; - %name{Slic3r::Print} class Print { Print(); ~Print(); @@ -35,31 +13,8 @@ %code%{ RETVAL = const_cast(&THIS->model()); %}; Ref config() %code%{ RETVAL = const_cast(static_cast(&THIS->config())); %}; - Ref skirt() - %code%{ RETVAL = const_cast(&THIS->skirt()); %}; - Ref brim() - %code%{ RETVAL = const_cast(&THIS->brim()); %}; double total_used_filament() %code%{ RETVAL = THIS->print_statistics().total_used_filament; %}; - double total_extruded_volume() - %code%{ RETVAL = THIS->print_statistics().total_extruded_volume; %}; - double total_weight() - %code%{ RETVAL = THIS->print_statistics().total_weight; %}; - double total_cost() - %code%{ RETVAL = THIS->print_statistics().total_cost; %}; - double total_wipe_tower_cost() - %code%{ RETVAL = THIS->print_statistics().total_wipe_tower_cost; %}; - double total_wipe_tower_filament() - %code%{ RETVAL = THIS->print_statistics().total_wipe_tower_filament; %}; - int wipe_tower_number_of_toolchanges() - %code%{ RETVAL = THIS->wipe_tower_data().number_of_toolchanges; %}; - PrintObjectPtrs* objects() - %code%{ RETVAL = const_cast(&THIS->objects_mutable()); %}; - Ref get_object(int idx) - %code%{ RETVAL = THIS->objects_mutable()[idx]; %}; - size_t object_count() - %code%{ RETVAL = THIS->objects().size(); %}; - void auto_assign_extruders(ModelObject* model_object); std::string output_filepath(std::string path = "") diff --git a/xs/xsp/Surface.xsp b/xs/xsp/Surface.xsp deleted file mode 100644 index 3fffea9ab8..0000000000 --- a/xs/xsp/Surface.xsp +++ /dev/null @@ -1,107 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Surface.hpp" -%} - -%name{Slic3r::Surface} class Surface { - ~Surface(); - Ref expolygon() - %code{% RETVAL = &(THIS->expolygon); %}; - double thickness() - %code{% RETVAL = THIS->thickness; %}; - unsigned short thickness_layers() - %code{% RETVAL = THIS->thickness_layers; %}; - double area(); - bool is_solid() const; - bool is_external() const; - bool is_internal() const; - bool is_bottom() const; - bool is_bridge() const; -%{ - -Surface* -_new(CLASS, expolygon, surface_type, thickness, thickness_layers, bridge_angle, extra_perimeters) - char* CLASS; - ExPolygon* expolygon; - SurfaceType surface_type; - double thickness; - unsigned short thickness_layers; - double bridge_angle; - unsigned short extra_perimeters; - CODE: - RETVAL = new Surface (surface_type, *expolygon); - RETVAL->thickness = thickness; - RETVAL->thickness_layers = thickness_layers; - RETVAL->bridge_angle = bridge_angle; - RETVAL->extra_perimeters = extra_perimeters; - // we don't delete expolygon here because it's referenced by a Perl SV - // whose DESTROY will take care of destruction - OUTPUT: - RETVAL - -SurfaceType -Surface::surface_type(...) - CODE: - if (items > 1) { - THIS->surface_type = (SurfaceType)SvUV(ST(1)); - } - RETVAL = THIS->surface_type; - OUTPUT: - RETVAL - -double -Surface::bridge_angle(...) - CODE: - if (items > 1) { - THIS->bridge_angle = (double)SvNV(ST(1)); - } - RETVAL = THIS->bridge_angle; - OUTPUT: - RETVAL - -unsigned short -Surface::extra_perimeters(...) - CODE: - if (items > 1) { - THIS->extra_perimeters = (double)SvUV(ST(1)); - } - RETVAL = THIS->extra_perimeters; - OUTPUT: - RETVAL - -Polygons -Surface::polygons() - CODE: - RETVAL.push_back(THIS->expolygon.contour); - for (Polygons::iterator it = THIS->expolygon.holes.begin(); it != THIS->expolygon.holes.end(); ++it) { - RETVAL.push_back((*it)); - } - OUTPUT: - RETVAL - -%} -}; - -%package{Slic3r::Surface}; -%{ - -IV -_constant() - ALIAS: - S_TYPE_TOP = stTop - S_TYPE_BOTTOM = stBottom - S_TYPE_BOTTOMBRIDGE = stBottomBridge - S_TYPE_INTERNAL = stInternal - S_TYPE_INTERNALSOLID = stInternalSolid - S_TYPE_INTERNALBRIDGE = stInternalBridge - S_TYPE_INTERNALVOID = stInternalVoid - S_TYPW_PERIMETER = stPerimeter - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} - diff --git a/xs/xsp/SurfaceCollection.xsp b/xs/xsp/SurfaceCollection.xsp deleted file mode 100644 index 0d31c5ae3b..0000000000 --- a/xs/xsp/SurfaceCollection.xsp +++ /dev/null @@ -1,70 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/SurfaceCollection.hpp" -%} - -%name{Slic3r::Surface::Collection} class SurfaceCollection { - %name{_new} SurfaceCollection(); - ~SurfaceCollection(); - void clear() - %code{% THIS->surfaces.clear(); %}; - void append(Surface* surface) - %code{% THIS->surfaces.push_back(*surface); %}; - int count() - %code{% RETVAL = THIS->surfaces.size(); %}; - void simplify(double tolerance); -%{ - -SV* -SurfaceCollection::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->surfaces.size()-1); - int i = 0; - for (Surfaces::iterator it = THIS->surfaces.begin(); it != THIS->surfaces.end(); ++it) { - av_store(av, i++, perl_to_SV_ref(*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -SV* -SurfaceCollection::filter_by_type(surface_type) - SurfaceType surface_type; - CODE: - AV* av = newAV(); - for (Surfaces::iterator it = THIS->surfaces.begin(); it != THIS->surfaces.end(); ++it) { - if ((*it).surface_type == surface_type) av_push(av, perl_to_SV_ref(*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -SV* -SurfaceCollection::group() - CODE: - // perform grouping - std::vector groups; - THIS->group(&groups); - - // build return arrayref - AV* av = newAV(); - av_fill(av, groups.size()-1); - size_t i = 0; - for (std::vector::iterator it = groups.begin(); it != groups.end(); ++it) { - AV* innerav = newAV(); - av_fill(innerav, it->size()-1); - size_t j = 0; - for (SurfacesPtr::iterator it_s = it->begin(); it_s != it->end(); ++it_s) { - av_store(innerav, j++, perl_to_SV_clone_ref(**it_s)); - } - av_store(av, i++, newRV_noinc((SV*)innerav)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -%} -}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 42ca74292c..ba5ed6e046 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -14,10 +14,6 @@ std::vector T_STD_VECTOR_UINT std::vector T_STD_VECTOR_DOUBLE -BoundingBox* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - DynamicPrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -70,25 +66,6 @@ ExPolygon* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -ExtrusionEntityCollection* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -ExtrusionPath* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -ExtrusionLoop* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Surface* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -SurfaceCollection* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - Model* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -109,22 +86,10 @@ ModelInstance* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -PrintRegion* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -PrintObject* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - Print* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -LayerRegion* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -Layer* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - Axis T_UV ExtrusionLoopRole T_UV ExtrusionRole T_UV @@ -137,8 +102,6 @@ Lines T_ARRAYREF Polygons T_ARRAYREF Polylines T_ARRAYREF ExPolygons T_ARRAYREF -ExtrusionPaths T_ARRAYREF -Surfaces T_ARRAYREF # we return these types whenever we want the items to be returned # by reference and marked ::Ref because they're contained in another @@ -147,9 +110,6 @@ Polygons* T_ARRAYREF_PTR ModelObjectPtrs* T_PTR_ARRAYREF_PTR ModelVolumePtrs* T_PTR_ARRAYREF_PTR ModelInstancePtrs* T_PTR_ARRAYREF_PTR -PrintRegionPtrs* T_PTR_ARRAYREF_PTR -PrintObjectPtrs* T_PTR_ARRAYREF_PTR -LayerPtrs* T_PTR_ARRAYREF_PTR # we return these types whenever we want the items to be returned # by reference and not marked ::Ref because they're newly allocated diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index bf1e8e2acc..7b9c02319d 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -27,9 +27,6 @@ %typemap{Vec3d*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{BoundingBox*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{DynamicPrintConfig*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; @@ -53,53 +50,20 @@ %typemap{Polygon*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{ExtrusionEntityCollection*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ExtrusionPath*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ExtrusionLoop*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{TriangleMesh*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{SurfaceCollection*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{Surface*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{PrintState*}; -%typemap{Ref}{simple}; - -%typemap{PrintRegion*}; -%typemap{Ref}{simple}; - -%typemap{PrintObject*}; -%typemap{Ref}{simple}; %typemap{Print*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{LayerRegion*}; -%typemap{Ref}{simple}; - -%typemap{Layer*}; -%typemap{Ref}{simple}; - %typemap{Points}; %typemap{Pointfs}; %typemap{Lines}; %typemap{Polygons}; %typemap{Polylines}; %typemap{ExPolygons}; -%typemap{ExtrusionPaths}; -%typemap{Surfaces}; %typemap{Polygons*}; %typemap{TriangleMesh*}; %typemap{Model*}; @@ -127,31 +91,9 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{PrintRegionPtrs*}; -%typemap{PrintObjectPtrs*}; -%typemap{LayerPtrs*}; - %typemap{Axis}{parsed}{ %cpp_type{Axis}; %precall_code{% $CVar = (Axis)SvUV($PerlVar); %}; }; -%typemap{SurfaceType}{parsed}{ - %cpp_type{SurfaceType}; - %precall_code{% - $CVar = (SurfaceType)SvUV($PerlVar); - %}; -}; -%typemap{ExtrusionLoopRole}{parsed}{ - %cpp_type{ExtrusionLoopRole}; - %precall_code{% - $CVar = (ExtrusionLoopRole)SvUV($PerlVar); - %}; -}; -%typemap{ExtrusionRole}{parsed}{ - %cpp_type{ExtrusionRole}; - %precall_code{% - $CVar = (ExtrusionRole)SvUV($PerlVar); - %}; -};