From 963f8d86b73c4ac4a96f445af4a8ce94d4182f6c Mon Sep 17 00:00:00 2001 From: Matthias Nott Date: Mon, 9 Feb 2026 20:38:46 +0100 Subject: [PATCH] feat: Add Z Anti-Aliasing (ZAA) contouring support Port Z Anti-Aliasing from BambuStudio-ZAA (https://github.com/adob/BambuStudio-ZAA) to OrcaSlicer. ZAA eliminates stair-stepping on curved and sloped top surfaces by raycasting each extrusion point against the original 3D mesh and micro-adjusting Z height to follow the actual surface geometry. Key changes: - Add ContourZ.cpp raycasting algorithm (~330 lines) - Extend geometry with 3D support (Point3, Line3, Polyline3, MultiPoint3) - Template arc fitting for 2D/3D compatibility - Change ExtrusionPath::polyline from Polyline to Polyline3 - Add 5 ZAA config options (zaa_enabled, zaa_min_z, etc.) - Add posContouring pipeline step in PrintObject - Update GCode writer for 3D coordinate output - Add ZAA settings UI in Print Settings > Quality - Add docs/ZAA.md with usage and implementation details ZAA is opt-in and disabled by default. When disabled, the slicing pipeline is unchanged. --- .gitignore | 1 + docs/ZAA.md | 41 +++ src/libslic3r/ArcFitter.cpp | 56 ++- src/libslic3r/ArcFitter.hpp | 2 + src/libslic3r/BoundingBox.hpp | 13 +- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/Circle.cpp | 113 ++++++ src/libslic3r/Circle.hpp | 9 + src/libslic3r/ClipperUtils.cpp | 13 + src/libslic3r/ClipperUtils.hpp | 3 + src/libslic3r/ContourZ.cpp | 333 ++++++++++++++++++ src/libslic3r/ExtrusionEntity.cpp | 98 ++++-- src/libslic3r/ExtrusionEntity.hpp | 110 ++++-- src/libslic3r/ExtrusionEntityCollection.hpp | 4 +- src/libslic3r/ExtrusionSimulator.cpp | 2 +- src/libslic3r/Fill/Fill.cpp | 4 + src/libslic3r/Fill/FillBase.cpp | 4 +- src/libslic3r/Fill/FillBase.hpp | 1 + src/libslic3r/GCode.cpp | 127 ++++--- src/libslic3r/GCode.hpp | 13 +- src/libslic3r/GCode/ConflictChecker.hpp | 2 +- src/libslic3r/GCode/ExtrusionProcessor.hpp | 14 +- src/libslic3r/GCode/PrintExtents.cpp | 6 +- src/libslic3r/GCode/SeamPlacer.cpp | 6 +- src/libslic3r/Layer.hpp | 5 + src/libslic3r/Line.hpp | 48 ++- src/libslic3r/MultiPoint.cpp | 119 +++++++ src/libslic3r/MultiPoint.hpp | 30 +- src/libslic3r/PerimeterGenerator.cpp | 30 +- src/libslic3r/Point.cpp | 77 ++++ src/libslic3r/Point.hpp | 121 ++++++- src/libslic3r/Polyline.cpp | 235 ++++++++++++ src/libslic3r/Polyline.hpp | 62 ++++ src/libslic3r/Preset.cpp | 2 + src/libslic3r/Print.cpp | 25 +- src/libslic3r/Print.hpp | 11 +- src/libslic3r/PrintConfig.cpp | 51 +++ src/libslic3r/PrintConfig.hpp | 10 + src/libslic3r/PrintObject.cpp | 53 ++- src/libslic3r/PrintObjectSlice.cpp | 8 + src/libslic3r/ShortestPath.cpp | 9 +- src/libslic3r/ShortestPath.hpp | 1 + src/libslic3r/Support/SupportCommon.cpp | 7 +- src/libslic3r/Support/SupportMaterial.cpp | 4 +- src/libslic3r/VariableWidth.cpp | 22 +- src/libslic3r/libslic3r_version.h.in | 1 + src/slic3r/GUI/3DScene.cpp | 6 +- src/slic3r/GUI/GUI_App.cpp | 39 +- src/slic3r/GUI/GUI_App.hpp | 1 + src/slic3r/GUI/GUI_Factories.cpp | 4 +- src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp | 6 +- src/slic3r/GUI/MainFrame.cpp | 14 + src/slic3r/GUI/OptionsGroup.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 13 +- src/slic3r/GUI/Search.cpp | 20 +- src/slic3r/GUI/Tab.cpp | 8 + version.inc | 1 + 57 files changed, 1817 insertions(+), 204 deletions(-) create mode 100644 docs/ZAA.md create mode 100644 src/libslic3r/ContourZ.cpp diff --git a/.gitignore b/.gitignore index 5b369a47fe..7268d4a273 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ test.js /.cache/ .clangd internal_docs/ +resources/nonplanar/ diff --git a/docs/ZAA.md b/docs/ZAA.md new file mode 100644 index 0000000000..dbeecc6548 --- /dev/null +++ b/docs/ZAA.md @@ -0,0 +1,41 @@ +# Z Anti-Aliasing (ZAA) — Z Contouring + +ZAA eliminates stair-stepping on curved and sloped top surfaces by adjusting the Z height of each extrusion point to follow the actual 3D model surface. + +Instead of printing flat horizontal layers, ZAA raycasts each point of the toolpath against the original mesh and micro-adjusts its Z coordinate to match the true surface geometry. The result is visibly smoother surfaces on domes, chamfers, and shallow slopes — without post-processing. + +This is a port of the ZAA implementation from [BambuStudio-ZAA](https://github.com/adob/BambuStudio-ZAA) by adob. + +## Configuration + +ZAA adds five settings under **Print Settings > Quality**: + +| Setting | Type | Default | Description | +|---------|------|---------|-------------| +| `zaa_enabled` | bool | off | Master enable/disable switch | +| `zaa_min_z` | float | 0.06 mm | Minimum Z layer height; also controls the slicing plane offset | +| `zaa_minimize_perimeter_height` | float | 35° | Reduce perimeter heights on slopes below this angle (0 = disabled) | +| `zaa_dont_alternate_fill_direction` | bool | off | Keep fill direction consistent instead of alternating per layer | +| `zaa_region_disable` | bool | off | Disable ZAA for a specific print region/material | + +## How It Works + +1. The slicer slices normally, then runs a **posContouring** step on each layer. +2. `ContourZ.cpp` raycasts every extrusion point vertically against the source mesh. +3. Each point's Z is adjusted to the mesh intersection, converting flat `Polyline` paths into `Polyline3` paths that carry per-point Z coordinates. +4. The G-code writer emits the adjusted Z values, so the printer follows the true surface. + +## Key Implementation Details + +- **Core algorithm**: `src/libslic3r/ContourZ.cpp` (~330 lines) +- **3D geometry**: `Point3`, `Line3`, `Polyline3`, `MultiPoint3` extend the existing 2D types +- **Pipeline step**: `posContouring` in `PrintObject.cpp`, runs after perimeter/infill generation +- **G-code output**: `GCode.cpp` writes per-point Z when `path.z_contoured` is set +- **Arc fitting**: Templated to work with both 2D and 3D geometry +- **ExtrusionPath change**: `polyline` field changed from `Polyline` to `Polyline3` + +## Testing + +1. Load a model with curved top surfaces (spheres, domes, chamfered edges) +2. Enable **Z contouring** in Print Settings > Quality +3. Slice and inspect the G-code — Z values should vary within each layer on contoured surfaces diff --git a/src/libslic3r/ArcFitter.cpp b/src/libslic3r/ArcFitter.cpp index cdfd708b10..b15548ee84 100644 --- a/src/libslic3r/ArcFitter.cpp +++ b/src/libslic3r/ArcFitter.cpp @@ -1,4 +1,5 @@ #include "ArcFitter.hpp" +#include "Point.hpp" #include "Polyline.hpp" #include @@ -6,7 +7,17 @@ namespace Slic3r { -void ArcFitter::do_arc_fitting(const Points& points, std::vector& result, double tolerance) +// Helper functions to dispatch to the correct douglas_peucker implementation +static inline Points douglas_peucker_helper(const Points &points, double tolerance) { + return MultiPoint::_douglas_peucker(points, tolerance); +} + +static inline Points3 douglas_peucker_helper(const Points3 &points, double tolerance) { + return MultiPoint3::_douglas_peucker(points, tolerance); +} + +template +static void do_arc_fitting_tmpl(const POINTS& points, std::vector& result, double tolerance) { #ifdef DEBUG_ARC_FITTING static int irun = 0; @@ -39,7 +50,7 @@ void ArcFitter::do_arc_fitting(const Points& points, std::vector 2) { //BBS: althought current point_stack can't be fit as arc, //but previous must can be fit if removing the top in stack, so save last arc - result.emplace_back(std::move(PathFittingData{ front_index, + result.emplace_back(PathFittingData{ front_index, back_index - 1, last_arc.direction == ArcDirection::Arc_Dir_CCW ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw, - last_arc })); + last_arc }); } else { //BBS: save the first segment as line move when 3 point-line can't be fit as arc move if (result.empty() || result.back().path_type != EMovePathType::Linear_move) @@ -94,7 +105,18 @@ void ArcFitter::do_arc_fitting(const Points& points, std::vector& result, double tolerance) +void ArcFitter::do_arc_fitting(const Points &points, std::vector& result, double tolerance) +{ + do_arc_fitting_tmpl(points, result, tolerance); +} + +void ArcFitter::do_arc_fitting(const Points3 &points, std::vector& result, double tolerance) +{ + do_arc_fitting_tmpl(points, result, tolerance); +} + +template +static void do_arc_fitting_and_simplify_tmpl(POINTS &points, std::vector& result, double tolerance) { //BBS: 1 do arc fit first if (abs(tolerance) > SCALED_EPSILON) @@ -106,12 +128,12 @@ void ArcFitter::do_arc_fitting_and_simplify(Points& points, std::vector reduce_count(result.size(), 0); @@ -124,11 +146,11 @@ void ArcFitter::do_arc_fitting_and_simplify(Points& points, std::vector& result, double tolerance) +{ + do_arc_fitting_and_simplify_tmpl(points, result, tolerance); +} + +void ArcFitter::do_arc_fitting_and_simplify(Points3& points, std::vector& result, double tolerance) +{ + do_arc_fitting_and_simplify_tmpl(points, result, tolerance); +} + } \ No newline at end of file diff --git a/src/libslic3r/ArcFitter.hpp b/src/libslic3r/ArcFitter.hpp index f2b2ee49d8..6672b0b771 100644 --- a/src/libslic3r/ArcFitter.hpp +++ b/src/libslic3r/ArcFitter.hpp @@ -42,9 +42,11 @@ class ArcFitter { public: //BBS: this function is used to check the point list and return which part can fit as arc, which part should be line static void do_arc_fitting(const Points& points, std::vector &result, double tolerance); + static void do_arc_fitting(const Points3& points, std::vector &result, double tolerance); //BBS: this function is used to check the point list and return which part can fit as arc, which part should be line. //By the way, it also use DP simplify to reduce point of straight part and only keep the start and end point of arc. static void do_arc_fitting_and_simplify(Points& points, std::vector& result, double tolerance); + static void do_arc_fitting_and_simplify(Points3& points, std::vector& result, double tolerance); }; } diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index d6baa27012..26b840ade6 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -236,7 +236,18 @@ class BoundingBox3 : public BoundingBox3Base public: BoundingBox3() : BoundingBox3Base() {} BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base(pmin, pmax) {} - BoundingBox3(const Points3& points) : BoundingBox3Base(points) {} + BoundingBox3(const Points3& points) : BoundingBox3Base() { + if (!points.empty()) { + this->min = points.front(); + this->max = points.front(); + for (const auto &p : points) { + this->min = this->min.cwiseMin(static_cast(p)); + this->max = this->max.cwiseMax(static_cast(p)); + } + this->defined = true; + } + } + BoundingBox3(const std::vector& points) : BoundingBox3Base(points) {} }; class BoundingBoxf : public BoundingBoxBase diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 4ab335879d..d5d5c66c76 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -100,6 +100,7 @@ set(lisbslic3r_sources CommonDefs.hpp Config.cpp Config.hpp + ContourZ.cpp CustomGCode.cpp CustomGCode.hpp CutUtils.cpp diff --git a/src/libslic3r/Circle.cpp b/src/libslic3r/Circle.cpp index bcd7fc47c1..c277a67795 100644 --- a/src/libslic3r/Circle.cpp +++ b/src/libslic3r/Circle.cpp @@ -3,6 +3,7 @@ #include #include #include "Geometry.hpp" +#include "Polygon.hpp" //BBS: Refer to ArcWelderLib for the arc fitting functions @@ -94,6 +95,12 @@ bool Circle::try_create_circle(const Points& points, const double max_radius, co return found_circle; } +bool Circle::try_create_circle(const Points3& points, const double max_radius, const double tolerance, Circle& new_circle) +{ + return Circle::try_create_circle(to_points(points), max_radius, tolerance, new_circle); +} + + double Circle::get_polar_radians(const Point& p1) const { double polar_radians = atan2(p1.y() - center.y(), p1.x() - center.x()); @@ -291,6 +298,31 @@ bool ArcSegment::try_create_arc( return false; } +bool ArcSegment::try_create_arc( + const Points3& points, + ArcSegment& target_arc, + double approximate_length, + double max_radius, + double tolerance, + double path_tolerance_percent) +{ + Circle test_circle = (Circle)target_arc; + if (!Circle::try_create_circle(points, max_radius, tolerance, test_circle)) + return false; + + int mid_point_index = ((points.size() - 2) / 2) + 1; + ArcSegment test_arc; + if (!ArcSegment::try_create_arc(test_circle, points[0].to_point(), points[mid_point_index].to_point(), points[points.size() - 1].to_point(), test_arc, approximate_length, path_tolerance_percent)) + return false; + + if (ArcSegment::are_points_within_slice(test_arc, points)) + { + target_arc = test_arc; + return true; + } + return false; +} + bool ArcSegment::try_create_arc( const Circle& c, const Point& start_point, @@ -455,6 +487,87 @@ bool ArcSegment::are_points_within_slice(const ArcSegment& test_arc, const Point return true; } +bool ArcSegment::are_points_within_slice(const ArcSegment& test_arc, const Points3& points) +{ + //BBS: Check all the points and see if they fit inside of the angles + double previous_polar = test_arc.polar_start_theta; + bool will_cross_zero = false; + bool crossed_zero = false; + const int point_count = points.size(); + + Vec2d start_norm(((double)test_arc.start_point.x() - (double)test_arc.center.x()) / test_arc.radius, + ((double)test_arc.start_point.y() - (double)test_arc.center.y()) / test_arc.radius); + Vec2d end_norm(((double)test_arc.end_point.x() - (double)test_arc.center.x()) / test_arc.radius, + ((double)test_arc.end_point.y() - (double)test_arc.center.y()) / test_arc.radius); + + if (test_arc.direction == ArcDirection::Arc_Dir_CCW) + will_cross_zero = test_arc.polar_start_theta > test_arc.polar_end_theta; + else + will_cross_zero = test_arc.polar_start_theta < test_arc.polar_end_theta; + + //BBS: check if point 1 to point 2 cross zero + double polar_test; + for (int index = point_count - 2; index < point_count; index++) + { + if (index < point_count - 1) + polar_test = test_arc.get_polar_radians(points[index].to_point()); + else + polar_test = test_arc.polar_end_theta; + + //BBS: First ensure the test point is within the arc + if (test_arc.direction == ArcDirection::Arc_Dir_CCW) + { + //BBS: Only check to see if we are within the arc if this isn't the endpoint + if (index < point_count - 1) { + if (will_cross_zero) { + if (!(polar_test > test_arc.polar_start_theta || polar_test < test_arc.polar_end_theta)) + return false; + } else if (!(test_arc.polar_start_theta < polar_test && polar_test < test_arc.polar_end_theta)) + return false; + } + //BBS: check the angles are increasing + if (previous_polar > polar_test) { + if (!will_cross_zero) + return false; + + //BBS: Allow the angle to cross zero once + if (crossed_zero) + return false; + crossed_zero = true; + } + } else { + if (index < point_count - 1) { + if (will_cross_zero) { + if (!(polar_test < test_arc.polar_start_theta || polar_test > test_arc.polar_end_theta)) + return false; + } else if (!(test_arc.polar_start_theta > polar_test && polar_test > test_arc.polar_end_theta)) + return false; + } + //BBS: Now make sure the angles are decreasing + if (previous_polar < polar_test) + { + if (!will_cross_zero) + return false; + //BBS: Allow the angle to cross zero once + if (crossed_zero) + return false; + crossed_zero = true; + } + } + + // BBS: check if the segment intersects either of the vector from the center of the circle to the endpoints of the arc + Line segmemt(points[index - 1].to_point(), points[index].to_point()); + if ((index != 1 && ray_intersects_segment(test_arc.center, start_norm, segmemt)) || + (index != point_count - 1 && ray_intersects_segment(test_arc.center, end_norm, segmemt))) + return false; + previous_polar = polar_test; + } + //BBS: Ensure that all arcs that cross zero + if (will_cross_zero != crossed_zero) + return false; + return true; +} + // BBS: this function is used to detect whether a ray cross the segment bool ArcSegment::ray_intersects_segment(const Point &rayOrigin, const Vec2d &rayDirection, const Line& segment) { diff --git a/src/libslic3r/Circle.hpp b/src/libslic3r/Circle.hpp index 8c649181dd..c06b197e19 100644 --- a/src/libslic3r/Circle.hpp +++ b/src/libslic3r/Circle.hpp @@ -28,6 +28,7 @@ public: static bool try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius, Circle& new_circle); static bool try_create_circle(const Points& points, const double max_radius, const double tolerance, Circle& new_circle); + static bool try_create_circle(const Points3& points, const double max_radius, const double tolerance, Circle& new_circle); double get_polar_radians(const Point& p1) const; bool is_over_deviation(const Points& points, const double tolerance); bool get_deviation_sum_squared(const Points& points, const double tolerance, double& sum_deviation); @@ -111,8 +112,16 @@ public: double max_radius = DEFAULT_SCALED_MAX_RADIUS, double tolerance = DEFAULT_SCALED_RESOLUTION, double path_tolerance_percent = DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE); + static bool try_create_arc( + const Points3 &points, + ArcSegment& target_arc, + double approximate_length, + double max_radius = DEFAULT_SCALED_MAX_RADIUS, + double tolerance = DEFAULT_SCALED_RESOLUTION, + double path_tolerance_percent = DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE); static bool are_points_within_slice(const ArcSegment& test_arc, const Points &points); + static bool are_points_within_slice(const ArcSegment& test_arc, const Points3 &points); // BBS: this function is used to detect whether a ray cross the segment static bool ray_intersects_segment(const Point& rayOrigin, const Vec2d& rayDirection, const Line& segment); // BBS: these three functions are used to calculate related arguments of arc in unscale_field. diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 2440983f17..2f97e08f53 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -411,6 +411,19 @@ Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type) { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit, end_type))); } + +Slic3r::Polygons offset(const Slic3r::Polyline3 &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type) +{ + assert(delta > 0); + return to_polygons( + clipper_union( + raw_offset_polyline( + ClipperUtils::SinglePathProvider(polyline.to_polyline().points), + delta, + joinType, + miterLimit, + end_type))); +} Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type) { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit, end_type))); } diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index cfe1f40935..9c2fa23926 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -334,6 +334,7 @@ Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, Clipp // Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better. // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons. Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType); +Slic3r::Polygons offset(const Slic3r::Polyline3 &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType); Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType); Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); @@ -524,6 +525,8 @@ Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); +Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::Polygon &clip); +Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::ExPolygon &clip); inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip) { diff --git a/src/libslic3r/ContourZ.cpp b/src/libslic3r/ContourZ.cpp new file mode 100644 index 0000000000..13a266573f --- /dev/null +++ b/src/libslic3r/ContourZ.cpp @@ -0,0 +1,333 @@ +#include "Exception.hpp" +#include "ExtrusionEntity.hpp" +#include "ExtrusionEntityCollection.hpp" +#include "Layer.hpp" +#include "Point.hpp" +#include "Print.hpp" +#include "SLA/IndexedMesh.hpp" +#include "libslic3r.h" +#include +#include +#include +#include + +namespace Slic3r { + +static void contour_extrusion_entity(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntity *extr); + +// static double lowest_z_within_distance(const Vec3d &normal, double dist) { +// const Vec3d p(0.0, 0.0, 0.0); +// Eigen::Vector3d n_unit = normal.normalized(); +// Eigen::Vector3d z_hat(0.0, 0.0, 1.0); + +// // Project the negative z-direction into the tangent plane +// Eigen::Vector3d v_dir = -z_hat + (z_hat.dot(n_unit)) * n_unit; + +// double norm_v = v_dir.norm(); +// if (norm_v == 0.0) { +// // Surface is horizontal, cannot go lower in z within tangent plane +// return p.z(); +// } + +// Eigen::Vector3d v = dist * v_dir / norm_v; +// Eigen::Vector3d q = p + v; +// return q.z(); +// } + +static double follow_slope_down(double angle_rad, double dist) { + return -dist * std::sin(angle_rad); +} + +static double slope_from_normal(const Eigen::Vector3d& normal) { + // Ensure the normal is normalized + Eigen::Vector3d n = normal.normalized(); + + // Compute angle between normal and z-axis + double angle_rad = std::acos(std::abs(n.z())); // angle between normal and vertical + return angle_rad; + + // calculate fall over dist + // double dist = 0.2; + // double z_dist = lowest_z(angle_rad, dist); + // printf("fall %f vs %f\n", z_dist, lowest_z_within_distance(normal, dist)); + + // double angle_deg = angle_rad * 180.0 / M_PI; + // return angle_deg; +} + +// const int LINE = 180; + +static bool contour_extrusion_path(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionPath &path) { + if (region->region().config().zaa_region_disable) { + return false; + } + + if (path.role() != erTopSolidInfill && path.role() != erIroning && path.role() != erExternalPerimeter && path.role() != erPerimeter) { + return false; + } + + Layer *layer = region->layer(); + coordf_t mesh_z = layer->print_z + mesh.ground_level(); + coordf_t min_z = layer->object()->config().zaa_min_z; + + const Points3 &points = path.polyline.points; + double resolution_mm = 0.1; + + coordf_t height = layer->height; + // std::cout << "LAYER " << (layer->id()+1) << std::endl; + // std::cout << "PRINT Z " << layer->print_z << std::endl; + // std::cout << "LAYER HEIGHT " << height << std::endl; + // std::cout << "EXTRUSION HEIGHT " << path.height << std::endl; + // std::cout << "EXTRUSION WIDTH " << path.width << std::endl; + // std::cout << "EXTRUSION ROLE: " << ExtrusionEntity::role_to_string(path.role()) << std::endl; + // std::cout << "FIRST POINT: " << path.polyline.first_point() << std::endl; + + double minimize_perimeter_height_angle = region->region().config().zaa_minimize_perimeter_height; + + Pointf3s contoured_points; + bool was_contoured = false; + // bool is_perimeter = path.role() == erExternalPerimeter || path.role() == erPerimeter || path.role() == erOverhangPerimeter; + + for (Points3::const_iterator it = points.begin(); it != points.end()-1; ++it) { + Vec2d p1d(unscale_(it->x()), unscale_(it->y())); + Vec2d p2d(unscale_((it+1)->x()), unscale_((it+1)->y())); + Linef line(p1d, p2d); + + double length_mm = line.length(); + int num_segments = int(std::ceil(length_mm / resolution_mm)); + Vec2d delta = line.vector(); + + for (int i = 0; i < num_segments+1; i++) { + Vec2d p = p1d + delta*i/num_segments; + + coordf_t x = p.x(); + coordf_t y = p.y(); + + sla::IndexedMesh::hit_result hit_up = mesh.query_ray_hit({x, y, mesh_z}, {0.0, 0.0, 1.0}); + sla::IndexedMesh::hit_result hit_down = mesh.query_ray_hit({x, y, mesh_z}, {0.0, 0.0, -1.0}); + + double up = hit_up.distance(); + double down = hit_down.distance(); + double d = up < down ? up : -down; + const Vec3d &normal = (up < down ? hit_up : hit_down).normal(); + + double max_up = min_z; + double min_down = -(height - min_z); + double half_width = path.width / 2.0; + if (path.role() == erIroning) { + max_up = height; + min_down = -(height + 0.1); + } + + double slope_rad = slope_from_normal(normal); + double slope_degrees = slope_rad * 180.0 / M_PI; + + if (d > min_down && minimize_perimeter_height_angle > 0 && minimize_perimeter_height_angle < slope_degrees && path.role() == erExternalPerimeter) { + double adjustment = follow_slope_down(slope_rad, half_width); + if (adjustment > 0) { + throw RuntimeError("ContourZ: got positive adjustment"); + } + d += adjustment; + if (d < min_down) { + d = min_down; + } + } + + if (d > max_up + 0.03 || d < min_down) { + d = 0; + } else { + if (d > max_up) { + d = max_up; + } + } + + if (path.role() == erExternalPerimeter && d > 0) { + // do not increase height of external perimeters as this may create an appearance of a seam + d = 0; + } + + if (std::abs(d) > EPSILON) { + was_contoured = true; + } + + Vec3d new_point = {p.x(), p.y(), d}; + + if (contoured_points.size() > 2) { + double dist = Linef3::distance_to_infinite_squared( + contoured_points[contoured_points.size() - 2], + contoured_points[contoured_points.size() - 1], + new_point); + if (dist < EPSILON) { + contoured_points[contoured_points.size() - 1] = new_point; + continue; + } + } + + contoured_points.push_back(new_point); + } + } + + if (!was_contoured) { + return false; + } + + Polyline3 polyline; + for (const Vec3d &point : contoured_points) { + polyline.append(Point3(scale_(point.x()), scale_(point.y()), scale_(point.z()))); + } + + path.polyline = std::move(polyline); + path.z_contoured = true; + return true; +} + +static void contour_extrusion_multipath(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionMultiPath &multipath) +{ + for (ExtrusionPath &path : multipath.paths) { + contour_extrusion_path(region, mesh, path); + } +} + +static void contour_extrusion_loop(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionLoop &loop) +{ + for (ExtrusionPath &path : loop.paths) { + contour_extrusion_path(region, mesh, path); + } +} + +static void contour_extrusion_entitiy_collection(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntityCollection &collection) { + for (ExtrusionEntity *entity : collection.entities) { + contour_extrusion_entity(region, mesh, entity); + } +} + +static void contour_extrusion_entity(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntity *extr) { + const ExtrusionPathSloped *sloped = dynamic_cast(extr); + if (sloped != nullptr) { + throw RuntimeError("ExtrusionPathSloped not implemented"); + return; + } + + ExtrusionMultiPath *multipath = dynamic_cast(extr); + if (multipath != nullptr) { + contour_extrusion_multipath(region, mesh, *multipath); + return; + } + + ExtrusionPath *path = dynamic_cast(extr); + if (path != nullptr) { + contour_extrusion_path(region, mesh, *path); + return; + } + + ExtrusionLoop *loop = dynamic_cast(extr); + if (loop != nullptr) { + contour_extrusion_loop(region, mesh, *loop); + return; + } + + const ExtrusionLoopSloped *loop_sloped = dynamic_cast(extr); + if (loop_sloped != nullptr) { + throw RuntimeError("ExtrusionLoopSloped not implemented"); + return; + } + + ExtrusionEntityCollection *collection = dynamic_cast(extr); + if (collection != nullptr) { + contour_extrusion_entitiy_collection(region, mesh, *collection); + return; + } + + throw RuntimeError("ContourZ: ExtrusionEntity type not implemented: " + std::string(typeid(*extr).name())); + return; +} + +static void handle_extrusion_collection(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntityCollection &collection, std::initializer_list roles) { + for (ExtrusionEntity *extr : collection.entities) { + // printf("handling extrusion collection %p %p\n", &collection, extr); + if (!contains(roles, extr->role())) { + continue; + } + + contour_extrusion_entity(region, mesh, extr); + } +} + +// static void find_point(ExtrusionPath &path, const std::string &path_info) { +// Points3 &points = path.polyline.points; + +// size_t i = 0; +// for (Points3::const_iterator it = points.begin(); it != points.end()-1; ++it) { +// if (it->x() == -883971 && it->y() == 979001) { +// std::cout << "FOUND POINT " << ExtrusionEntity::role_to_string(path.role()) << " at path " << path_info << "[" + std::to_string(i) + "]" << std::endl; +// } +// i++; +// } +// } + +// static void find_point(ExtrusionLoop &loop, const std::string &path_info) { +// size_t i = 0; +// for (ExtrusionPath &path : loop.paths) { +// find_point(path, path_info + "[" + std::to_string(i) + "]"); +// i++; +// } +// } + +// static void find_point(ExtrusionEntity &extr, const std::string &path); + +// static void find_point(ExtrusionEntityCollection &collection, const std::string &path) { +// size_t i = 0; +// for (ExtrusionEntity *extr : collection.entities) { +// find_point(*extr, path + "[" + std::to_string(i) + "]"); +// i++; +// } +// } + +// static void find_point(ExtrusionEntity &extr, const std::string &path_info) { +// const ExtrusionPathSloped *sloped = dynamic_cast(&extr); +// if (sloped != nullptr) { +// throw RuntimeError("ExtrusionPathSloped not implemented"); +// return; +// } + +// ExtrusionPath *path = dynamic_cast(&extr); +// if (path != nullptr) { +// find_point(*path, path_info + " as ExtrusionPath " + ExtrusionEntity::role_to_string(extr.role())); +// return; +// } + +// ExtrusionLoop *loop = dynamic_cast(&extr); +// if (loop != nullptr) { +// find_point(*loop, path_info + " as ExtrusionLoop " + ExtrusionEntity::role_to_string(extr.role())); +// return; +// } + +// const ExtrusionLoopSloped *loop_sloped = dynamic_cast(&extr); +// if (loop_sloped != nullptr) { +// throw RuntimeError("ExtrusionLoopSloped not implemented"); +// return; +// } + +// ExtrusionEntityCollection *collection = dynamic_cast(&extr); +// if (collection != nullptr) { +// find_point(*collection, path_info + " as ExtrusionEntityCollection " + ExtrusionEntity::role_to_string(extr.role())); +// return; +// } + +// throw RuntimeError("ContourZ: ExtrusionEntity type not implemented"); +// return; +// } + +void Layer::make_contour_z(const sla::IndexedMesh &mesh) +{ + // printf("make_contour_z() called\n"); + for (LayerRegion *region : this->regions()) { + // printf("processing layer region %p\n", region); + // find_point(region->fills, "fills"); + // find_point(region->perimeters, "perimeters"); + + handle_extrusion_collection(region, mesh, region->fills, {erTopSolidInfill, erIroning, erExternalPerimeter, erMixed}); + handle_extrusion_collection(region, mesh, region->perimeters, {erExternalPerimeter, erMixed}); + } +} +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 61d2cb9086..62cfbdd21a 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -17,12 +17,12 @@ static const double slope_inner_outer_wall_gap = 0.4; void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const { - this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection), retval); + this->_inflate_collection(intersection_pl(Polylines{ polyline.to_polyline() }, collection), retval); } void ExtrusionPath::subtract_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const { - this->_inflate_collection(diff_pl(Polylines{ this->polyline }, collection), retval); + this->_inflate_collection(diff_pl(Polylines{ this->polyline.to_polyline() }, collection), retval); } void ExtrusionPath::clip_end(double distance) @@ -32,11 +32,17 @@ void ExtrusionPath::clip_end(double distance) void ExtrusionPath::simplify(double tolerance) { + if (this->z_contoured) { + return; + } this->polyline.simplify(tolerance); } void ExtrusionPath::simplify_by_fitting_arc(double tolerance) { + if (this->z_contoured) { + return; + } this->polyline.simplify_by_fitting_arc(tolerance); } @@ -45,15 +51,23 @@ double ExtrusionPath::length() const return this->polyline.length(); } +void ExtrusionPath::collect_points(Points &dst) const +{ + dst.reserve(dst.size() + this->polyline.points.size()); + for (const Point3 &point : this->polyline.points) { + dst.emplace_back(point.x(), point.y()); + } +} + void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const { for (const Polyline &polyline : polylines) - collection->entities.emplace_back(new ExtrusionPath(polyline, *this)); + collection->entities.emplace_back(new ExtrusionPath(Polyline3(polyline), *this)); } void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { - polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon)); + polygons_append(out, offset(this->polyline.to_polyline(), float(scale_(this->width/2)) + scaled_epsilon)); } void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const @@ -64,7 +78,7 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale // SoftFever: TODO Mac trigger assersion errors // assert(! bridge || this->width == this->height); auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f); - polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon)); + polygons_append(out, offset(this->polyline.to_polyline(), 0.5f * float(flow.scaled_spacing()) + scaled_epsilon)); } void ExtrusionMultiPath::reverse() @@ -116,9 +130,10 @@ Polyline ExtrusionMultiPath::as_polyline() const len -= paths.size() - 1; assert(len > 0); out.points.reserve(len); - out.points.push_back(paths.front().polyline.points.front()); + out.points.push_back(paths.front().polyline.points.front().to_point()); for (size_t i_path = 0; i_path < paths.size(); ++ i_path) - out.points.insert(out.points.end(), paths[i_path].polyline.points.begin() + 1, paths[i_path].polyline.points.end()); + for (auto it = paths[i_path].polyline.points.begin() + 1; it != paths[i_path].polyline.points.end(); ++it) + out.points.push_back(it->to_point()); } return out; } @@ -149,7 +164,9 @@ Polygon ExtrusionLoop::polygon() const Polygon polygon; for (const ExtrusionPath &path : this->paths) { // for each polyline, append all points except the last one (because it coincides with the first one of the next polyline) - polygon.points.insert(polygon.points.end(), path.polyline.points.begin(), path.polyline.points.end()-1); + for (auto it = path.polyline.points.begin(); it != path.polyline.points.end() - 1; ++it) { + polygon.points.push_back(it->to_point()); + } } return polygon; } @@ -168,7 +185,7 @@ bool ExtrusionLoop::split_at_vertex(const Point &point, const double scaled_epsi if (int idx = path->polyline.find_point(point, scaled_epsilon); idx != -1) { if (this->paths.size() == 1) { // just change the order of points - Polyline p1, p2; + Polyline3 p1, p2; path->polyline.split_at_index(idx, &p1, &p2); if (p1.is_valid() && p2.is_valid()) { p2.append(std::move(p1)); @@ -178,7 +195,7 @@ bool ExtrusionLoop::split_at_vertex(const Point &point, const double scaled_epsi } else { // new paths list starts with the second half of current path ExtrusionPaths new_paths; - Polyline p1, p2; + Polyline3 p1, p2; path->polyline.split_at_index(idx, &p1, &p2); new_paths.reserve(this->paths.size() + 1); { @@ -218,16 +235,17 @@ ExtrusionLoop::ClosestPathPoint ExtrusionLoop::get_closest_path_and_point(const ClosestPathPoint best_non_overhang{0, 0}; double min2_non_overhang = std::numeric_limits::max(); for (const ExtrusionPath &path : this->paths) { - std::pair foot_pt_ = foot_pt(path.polyline.points, point); - double d2 = (foot_pt_.second - point).cast().squaredNorm(); + std::pair foot_pt_ = foot_pt(path.polyline.points, Point3(point)); + Point foot_pt_2d = Point(foot_pt_.second.x(), foot_pt_.second.y()); + double d2 = (foot_pt_2d - point).cast().squaredNorm(); if (d2 < min2) { - out.foot_pt = foot_pt_.second; + out.foot_pt = foot_pt_2d; out.path_idx = &path - &this->paths.front(); out.segment_idx = foot_pt_.first; min2 = d2; } if (prefer_non_overhang && !is_bridge(path.role()) && d2 < min2_non_overhang) { - best_non_overhang.foot_pt = foot_pt_.second; + best_non_overhang.foot_pt = foot_pt_2d; best_non_overhang.path_idx = &path - &this->paths.front(); best_non_overhang.segment_idx = foot_pt_.first; min2_non_overhang = d2; @@ -249,16 +267,18 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang, const // Snap p to start or end of segment_idx if closer than scaled_epsilon. { - const Point *p1 = this->paths[path_idx].polyline.points.data() + segment_idx; - const Point *p2 = p1; + const Point3 *p1 = this->paths[path_idx].polyline.points.data() + segment_idx; + const Point3 *p2 = p1; ++p2; - double d2_1 = (point - *p1).cast().squaredNorm(); - double d2_2 = (point - *p2).cast().squaredNorm(); + Point p1_2d = Point(p1->x(), p1->y()); + Point p2_2d = Point(p2->x(), p2->y()); + double d2_1 = (point - p1_2d).cast().squaredNorm(); + double d2_2 = (point - p2_2d).cast().squaredNorm(); const double thr2 = scaled_epsilon * scaled_epsilon; if (d2_1 < d2_2) { - if (d2_1 < thr2) p = *p1; + if (d2_1 < thr2) p = p1_2d; } else { - if (d2_2 < thr2) p = *p2; + if (d2_2 < thr2) p = p2_2d; } } @@ -411,16 +431,16 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths, : ExtrusionLoop(role) { // create slopes - const auto add_slop = [this, slope_max_segment_length, seam_gap](const ExtrusionPath &path, const Polyline &poly, double ratio_begin, double ratio_end) { + const auto add_slop = [this, slope_max_segment_length, seam_gap](const ExtrusionPath &path, const Polyline3 &poly, double ratio_begin, double ratio_end) { if (poly.empty()) { return; } // Ensure `slope_max_segment_length` - Polyline detailed_poly; + Polyline3 detailed_poly; { detailed_poly.append(poly.first_point()); // Recursively split the line into half until no longer than `slope_max_segment_length` - const std::function handle_line = [slope_max_segment_length, &detailed_poly, &handle_line](const Line &line) { + const std::function handle_line = [slope_max_segment_length, &detailed_poly, &handle_line](const Line3 &line) { if (line.length() <= slope_max_segment_length) { detailed_poly.append(line.b); } else { @@ -441,8 +461,8 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths, const auto seg_length = detailed_poly.length(); if (seg_length > seam_gap) { // Split the segment and remove the last `seam_gap` bit - const Polyline orig = detailed_poly; - Polyline tmp; + const Polyline3 orig = detailed_poly; + Polyline3 tmp; orig.split_at_length(seg_length - seam_gap, &detailed_poly, &tmp); ratio_end = lerp(ratio_begin, ratio_end, (seg_length - seam_gap) / seg_length); @@ -464,8 +484,8 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths, const double path_len = unscale_(path->length()); if (path_len > remaining_length) { // Split current path into slope and non-slope part - Polyline slope_path; - Polyline flat_path; + Polyline3 slope_path; + Polyline3 flat_path; path->polyline.split_at_length(scale_(remaining_length), &slope_path, &flat_path); add_slop(*path, slope_path, start_ratio, 1); @@ -631,4 +651,28 @@ ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role) return erNone; } +// ExtrusionPathContoured implementation +ExtrusionEntity *ExtrusionPathContoured::clone() const { + return new ExtrusionPathContoured(*this); +} + +ExtrusionEntity *ExtrusionPathContoured::clone_move() { + return new ExtrusionPathContoured(std::move(*this)); +} + +void ExtrusionPathContoured::simplify(double tolerance) { + // Do not simplify contoured paths + return; +} + +void ExtrusionPathContoured::simplify_by_fitting_arc(double tolerance) { + // Do not simplify contoured paths + return; +} + +void ExtrusionPathContoured::reverse() { + this->polyline.reverse(); + std::reverse(this->z_diffs.begin(), this->z_diffs.end()); +} + } diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index edd7839cc1..ce74d93e9e 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -116,8 +116,8 @@ public: virtual ExtrusionEntity* clone_move() = 0; virtual ~ExtrusionEntity() {} virtual void reverse() = 0; - virtual const Point& first_point() const = 0; - virtual const Point& last_point() const = 0; + virtual Point first_point() const = 0; + virtual Point last_point() const = 0; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0; @@ -150,13 +150,17 @@ typedef std::vector ExtrusionEntitiesPtr; class ExtrusionPath : public ExtrusionEntity { public: - Polyline polyline; + Polyline3 polyline; + double overhang_degree = 0; + int curve_degree = 0; // Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator. double mm3_per_mm; // Width of the extrusion, used for visualization purposes. float width; // Height of the extrusion, used for visualization purposes. float height; + double smooth_speed = 0; + bool z_contoured = false; ExtrusionPath() : mm3_per_mm(-1), width(-1), height(-1), m_role(erNone), m_no_extrusion(false) {} ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role), m_no_extrusion(false) {} @@ -164,36 +168,52 @@ public: ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline) + , overhang_degree(rhs.overhang_degree) + , curve_degree(rhs.curve_degree) , mm3_per_mm(rhs.mm3_per_mm) , width(rhs.width) , height(rhs.height) + , smooth_speed(rhs.smooth_speed) + , z_contoured(rhs.z_contoured) , m_can_reverse(rhs.m_can_reverse) , m_role(rhs.m_role) , m_no_extrusion(rhs.m_no_extrusion) {} ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)) + , overhang_degree(rhs.overhang_degree) + , curve_degree(rhs.curve_degree) , mm3_per_mm(rhs.mm3_per_mm) , width(rhs.width) , height(rhs.height) + , smooth_speed(rhs.smooth_speed) + , z_contoured(rhs.z_contoured) , m_can_reverse(rhs.m_can_reverse) , m_role(rhs.m_role) , m_no_extrusion(rhs.m_no_extrusion) {} - ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) + ExtrusionPath(const Polyline3 &polyline, const ExtrusionPath &rhs) : polyline(polyline) + , overhang_degree(rhs.overhang_degree) + , curve_degree(rhs.curve_degree) , mm3_per_mm(rhs.mm3_per_mm) , width(rhs.width) , height(rhs.height) + , smooth_speed(rhs.smooth_speed) + , z_contoured(rhs.z_contoured) , m_can_reverse(rhs.m_can_reverse) , m_role(rhs.m_role) , m_no_extrusion(rhs.m_no_extrusion) {} - ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) + ExtrusionPath(Polyline3 &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)) + , overhang_degree(rhs.overhang_degree) + , curve_degree(rhs.curve_degree) , mm3_per_mm(rhs.mm3_per_mm) , width(rhs.width) , height(rhs.height) + , smooth_speed(rhs.smooth_speed) + , z_contoured(rhs.z_contoured) , m_can_reverse(rhs.m_can_reverse) , m_role(rhs.m_role) , m_no_extrusion(rhs.m_no_extrusion) @@ -206,6 +226,10 @@ public: this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; + this->smooth_speed = rhs.smooth_speed; + this->z_contoured = rhs.z_contoured; + this->overhang_degree = rhs.overhang_degree; + this->curve_degree = rhs.curve_degree; this->polyline = rhs.polyline; return *this; } @@ -216,6 +240,10 @@ public: this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; + this->smooth_speed = rhs.smooth_speed; + this->z_contoured = rhs.z_contoured; + this->overhang_degree = rhs.overhang_degree; + this->curve_degree = rhs.curve_degree; this->polyline = std::move(rhs.polyline); return *this; } @@ -224,8 +252,10 @@ public: // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); } void reverse() override { this->polyline.reverse(); } - const Point& first_point() const override { return this->polyline.points.front(); } - const Point& last_point() const override { return this->polyline.points.back(); } + Point first_point() const override { return this->polyline.points.front().to_point(); } + Point3 first_point3() const { return this->polyline.points.front(); } + Point last_point() const override { return this->polyline.points.back().to_point(); } + Point3 last_point3() const { return this->polyline.points.back(); } size_t size() const { return this->polyline.size(); } bool empty() const { return this->polyline.empty(); } bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); } @@ -236,7 +266,7 @@ public: // Currently not used. void subtract_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const; void clip_end(double distance); - void simplify(double tolerance); + virtual void simplify(double tolerance); double length() const override; ExtrusionRole role() const override { return m_role; } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. @@ -252,9 +282,10 @@ public: { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const override { return this->mm3_per_mm; } - Polyline as_polyline() const override { return this->polyline; } - void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); } - void collect_points(Points &dst) const override { append(dst, this->polyline.points); } + Polyline as_polyline() const override { return this->polyline.to_polyline(); } + void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline.to_polyline()); } + void collect_points(Points &dst) const override; + void collect_points3(Points3 &dst) const { append(dst, this->polyline.points); } double total_volume() const override { return mm3_per_mm * unscale(length()); } //BBS: add new simplifing method by fitting arc @@ -274,6 +305,23 @@ private: bool m_no_extrusion = false; }; +class ExtrusionPathContoured : public ExtrusionPath { +public: + std::vector z_diffs; + + ExtrusionPathContoured(Polyline3 &&polyline, const ExtrusionPath &rhs, std::vector &&z_diffs) + : ExtrusionPath(std::move(polyline), rhs), z_diffs(std::move(z_diffs)) + {} + + virtual ExtrusionEntity *clone() const override; + virtual ExtrusionEntity *clone_move() override; + + void simplify(double tolerance) override; + virtual void simplify_by_fitting_arc(double tolerance); + + void reverse() override; +}; + class ExtrusionPathSloped : public ExtrusionPath { public: @@ -292,10 +340,10 @@ public: ExtrusionPathSloped(ExtrusionPath&& rhs, const Slope& begin, const Slope& end) : ExtrusionPath(std::move(rhs)), slope_begin(begin), slope_end(end) {} - ExtrusionPathSloped(const Polyline& polyline, const ExtrusionPath& rhs, const Slope& begin, const Slope& end) + ExtrusionPathSloped(const Polyline3& polyline, const ExtrusionPath& rhs, const Slope& begin, const Slope& end) : ExtrusionPath(polyline, rhs), slope_begin(begin), slope_end(end) {} - ExtrusionPathSloped(Polyline&& polyline, const ExtrusionPath& rhs, const Slope& begin, const Slope& end) + ExtrusionPathSloped(Polyline3&& polyline, const ExtrusionPath& rhs, const Slope& begin, const Slope& end) : ExtrusionPath(std::move(polyline), rhs), slope_begin(begin), slope_end(end) {} @@ -354,8 +402,8 @@ public: // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); } void reverse() override; - const Point& first_point() const override { return this->paths.front().polyline.points.front(); } - const Point& last_point() const override { return this->paths.back().polyline.points.back(); } + Point first_point() const override { return this->paths.front().polyline.points.front().to_point(); } + Point last_point() const override { return this->paths.back().polyline.points.back().to_point(); } size_t size() const { return this->paths.size(); } bool empty() const { return this->paths.empty(); } double length() const override; @@ -379,7 +427,7 @@ public: size_t n = std::accumulate(paths.begin(), paths.end(), 0, [](const size_t n, const ExtrusionPath &p){ return n + p.polyline.size(); }); dst.reserve(dst.size() + n); for (const ExtrusionPath &p : this->paths) - append(dst, p.polyline.points); + append(dst, to_points(p.polyline.points)); } double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } @@ -410,8 +458,8 @@ public: bool is_clockwise() { return this->polygon().is_clockwise(); } bool is_counter_clockwise() { return this->polygon().is_counter_clockwise(); } void reverse() override; - const Point& first_point() const override { return this->paths.front().polyline.points.front(); } - const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); } + Point first_point() const override { return this->paths.front().polyline.points.front().to_point(); } + Point last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back().to_point()); return this->first_point(); } Polygon polygon() const; double length() const override; bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled(0.001)); @@ -449,7 +497,7 @@ public: size_t n = std::accumulate(paths.begin(), paths.end(), 0, [](const size_t n, const ExtrusionPath &p){ return n + p.polyline.size(); }); dst.reserve(dst.size() + n); for (const ExtrusionPath &p : this->paths) - append(dst, p.polyline.points); + append(dst, to_points(p.polyline.points)); } double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } // check if the loop is smooth, angle_threshold is in radians, default is 10 degrees @@ -495,7 +543,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, Ex for (Polyline &polyline : polylines) if (polyline.is_valid()) { dst.emplace_back(role, mm3_per_mm, width, height); - dst.back().polyline = polyline; + dst.back().polyline = Polyline3(polyline); } } @@ -505,7 +553,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, E for (Polyline &polyline : polylines) if (polyline.is_valid()) { dst.emplace_back(role, mm3_per_mm, width, height); - dst.back().polyline = std::move(polyline); + dst.back().polyline = Polyline3(std::move(polyline)); } polylines.clear(); } @@ -515,7 +563,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polyline &&polyline, Ext dst.reserve(dst.size() + 1); if (polyline.is_valid()) { dst.emplace_back(role, mm3_per_mm, width, height); - dst.back().polyline = std::move(polyline); + dst.back().polyline = Polyline3(std::move(polyline)); } } @@ -526,7 +574,7 @@ inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines if (polyline.is_valid()) { ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height); dst.push_back(extrusion_path); - extrusion_path->polyline = polyline; + extrusion_path->polyline = Polyline3(polyline); } } @@ -537,7 +585,7 @@ inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines if (polyline.is_valid()) { ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height); dst.push_back(extrusion_path); - extrusion_path->polyline = std::move(polyline); + extrusion_path->polyline = Polyline3(std::move(polyline)); } polylines.clear(); } @@ -557,7 +605,7 @@ inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst, Point temp = polyline.first_point() - last_end_point; if (Vec2d(temp.x(), temp.y()).norm() <= 3 * scaled(width)) { multi_path->paths.emplace_back(role, mm3_per_mm, width, height, true); - multi_path->paths.back().polyline = std::move(Polyline(last_end_point, polyline.first_point())); + multi_path->paths.back().polyline = Polyline3(Polyline(last_end_point, polyline.first_point())); } else { dst.push_back(multi_path); multi_path = new ExtrusionMultiPath(); @@ -565,9 +613,9 @@ inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst, } multi_path->paths.emplace_back(role, mm3_per_mm, width, height); - multi_path->paths.back().polyline = std::move(polyline); + multi_path->paths.back().polyline = Polyline3(std::move(polyline)); last_end_point_valid = true; - last_end_point = multi_path->paths.back().polyline.last_point(); + last_end_point = multi_path->paths.back().polyline.last_point().to_point(); } } if (!multi_path->empty()) @@ -582,7 +630,9 @@ inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons for (Polygon &poly : loops) { if (poly.is_valid()) { ExtrusionPath path(role, mm3_per_mm, width, height); - path.polyline.points = std::move(poly.points); + path.polyline.points.reserve(poly.points.size() + 1); + for (const Point &pt : poly.points) + path.polyline.points.emplace_back(Point3(pt, 0)); path.polyline.points.push_back(path.polyline.points.front()); dst.emplace_back(new ExtrusionLoop(std::move(path))); } @@ -597,11 +647,11 @@ inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, if (polyline.is_valid()) { if (polyline.is_closed()) { ExtrusionPath extrusion_path(role, mm3_per_mm, width, height); - extrusion_path.polyline = std::move(polyline); + extrusion_path.polyline = Polyline3(std::move(polyline)); dst.emplace_back(new ExtrusionLoop(std::move(extrusion_path))); } else { ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height); - extrusion_path->polyline = std::move(polyline); + extrusion_path->polyline = Polyline3(std::move(polyline)); dst.emplace_back(extrusion_path); } } diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 613d531db0..35c9e24743 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -118,8 +118,8 @@ public: ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const { return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); } void reverse() override; - const Point& first_point() const override { return this->entities.front()->first_point(); } - const Point& last_point() const override { return this->entities.back()->last_point(); } + Point first_point() const override { return this->entities.front()->first_point(); } + Point last_point() const override { return this->entities.back()->last_point(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; diff --git a/src/libslic3r/ExtrusionSimulator.cpp b/src/libslic3r/ExtrusionSimulator.cpp index 6b1f76abea..4bf54edea9 100644 --- a/src/libslic3r/ExtrusionSimulator.cpp +++ b/src/libslic3r/ExtrusionSimulator.cpp @@ -962,7 +962,7 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const w = scale_(path.mm3_per_mm / path.height) * scalex; // printf("scalex: %f, scaley: %f\n", scalex, scaley); // printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y); - for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) { + for (Points3::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) { // printf("point %d,%d\n", it->x+shift.x(), it->y+shift.y); ExtrusionPoint ept; ept.center = V2f(float((*it)(0)+shift.x()-bbox.min.x()) * scalex, float((*it)(1)+shift.y()-bbox.min.y()) * scaley); diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index a44fa46606..79f835816a 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -1215,6 +1215,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); f->set_bounding_box(bbox); f->layer_id = this->id(); + f->dont_alternate_fill_direction = this->object()->config().zaa_dont_alternate_fill_direction; f->z = this->print_z; f->angle = surface_fill.params.angle; f->fixed_angle = surface_fill.params.fixed_angle; @@ -1408,6 +1409,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); f->set_bounding_box(bbox); f->layer_id = this->id() - this->object()->get_layer(0)->id(); // We need to subtract raft layers. + f->dont_alternate_fill_direction = this->object()->config().zaa_dont_alternate_fill_direction; f->z = this->print_z; f->angle = surface_fill.params.angle; f->fixed_angle = surface_fill.params.fixed_angle; @@ -1580,6 +1582,7 @@ void Layer::make_ironing() std::unique_ptr f = std::unique_ptr(Fill::new_from_type(f_pattern)); f->set_bounding_box(this->object()->bounding_box()); f->layer_id = this->id(); + f->dont_alternate_fill_direction = this->object()->config().zaa_dont_alternate_fill_direction; f->z = this->print_z; f->overlap = 0; for (size_t i = 0; i < by_extruder.size();) { @@ -1592,6 +1595,7 @@ void Layer::make_ironing() f = std::unique_ptr(Fill::new_from_type(f_pattern)); f->set_bounding_box(this->object()->bounding_box()); f->layer_id = this->id(); + f->dont_alternate_fill_direction = this->object()->config().zaa_dont_alternate_fill_direction; f->z = this->print_z; f->overlap = 0; } diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index eff83efe3e..9358667982 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -308,7 +308,9 @@ std::pair Fill::_infill_direction(const Surface *surface) const } else if (this->layer_id != size_t(-1) && !fixed_angle) { // alternate fill direction //Orca: Do not alternate direction if Fill.fixed_angle is true - out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers); + if (!this->dont_alternate_fill_direction) { + out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers); + } } else { // printf("Layer_ID undefined!\n"); } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 010dff8a08..47195c850d 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -141,6 +141,7 @@ public: // BBS: all no overlap expolygons in same layer ExPolygons no_overlap_expolygons; + bool dont_alternate_fill_direction = false; static float infill_anchor; static float infill_anchor_max; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 9b82b60b06..a5ee9942fd 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -22,10 +22,12 @@ #include "Time.hpp" #include "GCode/ExtrusionProcessor.hpp" #include +#include #include #include #include #include +#include #include #include #include @@ -5289,12 +5291,12 @@ void GCode::set_extruders(const std::vector &extruder_ids) void GCode::set_origin(const Vec2d &pointf) { // if origin increases (goes towards right), last_pos decreases because it goes towards left - const Point translate( + const Point3 translate( scale_(m_origin(0) - pointf(0)), scale_(m_origin(1) - pointf(1)) ); m_last_pos += translate; - m_wipe.path.translate(translate); + m_wipe.path.translate(translate.to_point()); m_origin = pointf; } @@ -5371,11 +5373,11 @@ static std::unique_ptr calculate_layer_edge_grid(const Layer& la return out; } -std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters, const Point* start_point) +std::string GCode::extrude_loop(const ExtrusionLoop &loop_ref, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters, const Point* start_point) { - // get a copy; don't modify the orientation of the original loop object otherwise // next copies (if any) would not detect the correct orientation + ExtrusionLoop loop = loop_ref; bool is_hole = (loop.loop_role() & elrHole) == elrHole; @@ -5447,13 +5449,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou const double nozzle_diam = nozzle_diameter; // note: previous & next are inverted to extrude "in the opposite direction, and we are "rewinding" - Point previous_point = paths.front().polyline.points[1]; - Point current_point = paths.front().polyline.points.front(); - Point next_point = paths.back().polyline.points.back(); + Point previous_point = Point(paths.front().polyline.points[1].x(), paths.front().polyline.points[1].y()); + Point current_point = Point(paths.front().polyline.points.front().x(), paths.front().polyline.points.front().y()); + Point next_point = Point(paths.back().polyline.points.back().x(), paths.back().polyline.points.back().y()); // can happen if seam_gap is null if (next_point == current_point) { - next_point = paths.back().polyline.points[paths.back().polyline.points.size() - 2]; + const Point3 &p3 = paths.back().polyline.points[paths.back().polyline.points.size() - 2]; + next_point = Point(p3.x(), p3.y()); } Point a = next_point; // second point @@ -5500,7 +5503,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // inside the model if(discoveredTouchingLines > 1){ // use extrude instead of travel_to_xy to trigger the unretract - ExtrusionPath fake_path_wipe(Polyline{pt, current_point}, paths.front()); + ExtrusionPath fake_path_wipe(Polyline3(Points3{Point3(pt), Point3(current_point)}), paths.front()); fake_path_wipe.set_force_no_extrusion(true); fake_path_wipe.mm3_per_mm = 0; //fake_path_wipe.set_extrusion_role(erExternalPerimeter); @@ -5594,10 +5597,12 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou for (ExtrusionPath &path : paths) { //BBS: Don't need to save duplicated point into wipe path if (!m_wipe.path.empty() && !path.empty() && - m_wipe.path.last_point() == path.first_point()) - m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end()); - else - m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path + m_wipe.path.last_point() == Point(path.first_point().x(), path.first_point().y())) { + // Convert Points3 to Points + for (auto it = path.polyline.points.begin() + 1; it != path.polyline.points.end(); ++it) + m_wipe.path.append(Point(it->x(), it->y())); + } else + m_wipe.path.append(path.polyline.to_polyline()); // TODO: don't limit wipe to last path } } @@ -5607,8 +5612,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // the side depends on the original winding order of the polygon (inwards for contours, outwards for holes) //FIXME improve the algorithm in case the loop is tiny. //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). - Point a = paths.front().polyline.points[1]; // second point - Point b = *(paths.back().polyline.points.end()-3); // second to last point + const Point3 &a3 = paths.front().polyline.points[1]; // second point + Point a = Point(a3.x(), a3.y()); + const Point3 &b3 = *(paths.back().polyline.points.end()-3); // second to last point + Point b = Point(b3.x(), b3.y()); if (is_hole == loop.is_counter_clockwise()) { // swap points Point c = a; a = b; b = c; @@ -5622,8 +5629,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // create the destination point along the first segment and rotate it // we make sure we don't exceed the segment length because we don't know // the rotation of the second segment so we might cross the object boundary - Vec2d p1 = paths.front().polyline.points.front().cast(); - Vec2d p2 = paths.front().polyline.points[1].cast(); + Vec2d p1 = paths.front().polyline.points.front().cast().head<2>(); + Vec2d p2 = paths.front().polyline.points[1].cast().head<2>(); Vec2d v = p2 - p1; double nd = scale_(EXTRUDER_CONFIG(nozzle_diameter)); double l2 = v.squaredNorm(); @@ -5635,7 +5642,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou if (nd * nd < l2) pt = (p1 + threshold * v * (nd / sqrt(l2))).cast(); //Point pt = ((nd * nd >= l2) ? (p1+v*0.4): (p1 + 0.2 * v * (nd / sqrt(l2)))).cast(); - pt.rotate(angle, paths.front().polyline.points.front()); + const Point3 ¢er3 = paths.front().polyline.points.front(); + pt.rotate(angle, Point(center3.x(), center3.y())); // generate the travel move gcode += m_writer.extrude_to_xy(this->point_to_gcode(pt), 0,"move inwards before travel",true); } @@ -5643,11 +5651,11 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou return gcode; } -std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed) +std::string GCode::extrude_multi_path(const ExtrusionMultiPath &multipath, std::string description, double speed) { // extrude along the path std::string gcode; - + //Orca: calculate multipath average mm3_per_mm value over the length of the path. //This is used for adaptive PA m_multi_flow_segment_path_pa_set = false; // always emit PA on the first path of the multi-path @@ -5664,8 +5672,8 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string if (total_multipath_length > 0.0) m_multi_flow_segment_path_average_mm3_per_mm = weighted_sum_mm3_per_mm / total_multipath_length; // Orca: end of multipath average mm3_per_mm value calculation - - for (ExtrusionPath path : multipath.paths){ + + for (const ExtrusionPath &path : multipath.paths){ gcode += this->_extrude(path, description, speed); // Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed // as we have already set the PA value to the average flow over the totality of the path @@ -5676,13 +5684,15 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string // BBS if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { m_wipe.path = Polyline(); - for (ExtrusionPath &path : multipath.paths) { + for (const ExtrusionPath &path : multipath.paths) { //BBS: Don't need to save duplicated point into wipe path if (!m_wipe.path.empty() && !path.empty() && - m_wipe.path.last_point() == path.first_point()) - m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end()); - else - m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path + m_wipe.path.last_point() == Point(path.first_point().x(), path.first_point().y())) { + // Convert Points3 to Points + for (auto it = path.polyline.points.begin() + 1; it != path.polyline.points.end(); ++it) + m_wipe.path.append(Point(it->x(), it->y())); + } else + m_wipe.path.append(path.polyline.to_polyline()); // TODO: don't limit wipe to last path } m_wipe.path.reverse(); } @@ -5703,7 +5713,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des return ""; } -std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) +std::string GCode::extrude_path(const ExtrusionPath &path, std::string description, double speed) { // Orca: Reset average multipath flow as this is a single line, single extrude volumetric speed path m_multi_flow_segment_path_pa_set = false; @@ -5711,17 +5721,17 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string description, dou // description += ExtrusionEntity::role_to_string(path.role()); std::string gcode = this->_extrude(path, description, speed); if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { - m_wipe.path = path.polyline; + m_wipe.path = path.polyline.to_polyline(); if (is_tree(this->config().support_type) && (path.role() == erSupportMaterial || path.role() == erSupportMaterialInterface || path.role() == erSupportTransition)) { if ((m_wipe.path.first_point() - m_wipe.path.last_point()).cast().norm() > scale_(0.2)) { double min_dist = scale_(0.2); int i = 0; for (; i < path.polyline.points.size(); i++) { - double dist = (path.polyline.points[i] - path.last_point()).cast().norm(); + double dist = (path.polyline.points[i] - path.last_point3()).cast().norm(); if (dist < min_dist) min_dist = dist; if (min_dist < scale_(0.2) && dist > min_dist) break; } - m_wipe.path = Polyline(Points(path.polyline.points.begin() + i - 1, path.polyline.points.end())); + m_wipe.path = Polyline3(Points3(path.polyline.points.begin() + i - 1, path.polyline.points.end())).to_polyline(); } } else m_wipe.path.reverse(); @@ -5764,11 +5774,11 @@ std::string GCode::extrude_infill(const Print &print, const std::vector(fill); if (eec) { - for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) + for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos.to_point()).entities) gcode += this->extrude_entity(*ee, extrusion_name); } else gcode += this->extrude_entity(*fill, extrusion_name); @@ -5799,7 +5809,7 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill if (extrusions.empty()) return gcode; - chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); + chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point()); const double support_speed = m_config.support_speed.value; const double support_interface_speed = m_config.get_abs_value("support_interface_speed"); @@ -5965,13 +5975,23 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // Move to first point of extrusion path // path is 2D. But in slope lift case, lift z is done in travel_to function. // Add m_need_change_layer_lift_z when change_layer in case of no lift if m_last_pos is equal to path.first_point() by chance - if (!m_last_pos_defined || m_last_pos != path.first_point() || m_need_change_layer_lift_z || slope_need_z_travel) { + Point3 first_point = path.first_point3(); + if (!m_last_pos_defined || m_last_pos != first_point || m_need_change_layer_lift_z || slope_need_z_travel) { const bool _last_pos_undefined = !m_last_pos_defined; + double z = DBL_MAX; + if (sloped != nullptr) { + z = get_sloped_z(sloped->slope_begin.z_ratio); + } else if ((!m_last_pos_defined && first_point.z() != 0) || m_last_pos.z() != first_point.z()) { + z = m_nominal_z + unscale_(first_point.z()); + if (z < 0.1) { + throw RuntimeError("GCode: very low z"); + } + } gcode += this->travel_to( path.first_point(), path.role(), - "move to first " + description + " point", - sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio) + "move to first " + description + " point; size " + std::to_string(path.polyline.size()), + z ); m_need_change_layer_lift_z = false; // Orca: ensure Z matches planned layer height @@ -6536,10 +6556,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } // BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode or we are doing sloped extrusion // Attention: G2 and G3 is not supported in spiral_mode mode - if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode || sloped != nullptr) { + if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode || sloped != nullptr || path.z_contoured) { double path_length = 0.; double total_length = sloped == nullptr ? 0. : path.polyline.length() * SCALING_FACTOR; - for (const Line& line : path.polyline.lines()) { + for (const Line3& line : path.polyline.lines()) { std::string tempDescription = description; const double line_length = line.length() * SCALING_FACTOR; if (line_length < EPSILON) @@ -6554,16 +6574,37 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length); } } - if (sloped == nullptr) { + if (path.z_contoured) { + // ZAA: Z anti-aliased extrusion with variable Z per point + Vec2d dest2d = this->point_to_gcode(line.b.to_point()); + coordf_t z_diff = unscale_(line.b.z()); + + double extrusion_ratio = 1; + if (path.role() != erIroning) { + extrusion_ratio = (path.height + z_diff) / path.height; + } + + double e = dE * extrusion_ratio; + + double z = m_nominal_z + z_diff; + if (z < 0.1) { + throw RuntimeError("GCode: very low z"); + } + gcode += m_writer.extrude_to_xyz( + Vec3d(dest2d.x(), dest2d.y(), z), + e, + tempDescription + "; z_diff " + std::to_string(z_diff) + " " + ExtrusionEntity::role_to_string(path.role()) + "; eratio " + std::to_string(extrusion_ratio)); + + } else if (sloped == nullptr) { // Normal extrusion gcode += m_writer.extrude_to_xy( - this->point_to_gcode(line.b), + this->point_to_gcode(line.b.to_point()), dE, GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion()); } else { // Sloped extrusion const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length); - Vec2d dest2d = this->point_to_gcode(line.b); + Vec2d dest2d = this->point_to_gcode(line.b.to_point()); Vec3d dest3d(dest2d(0), dest2d(1), get_sloped_z(z_ratio)); gcode += m_writer.extrude_to_xyz( dest3d, @@ -6582,7 +6623,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, size_t end_index = fitting_result[fitting_index].end_point_index; for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) { tempDescription = description; - const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]); + const Line line = Line(path.polyline.points[point_index - 1].to_point(), path.polyline.points[point_index].to_point()); const double line_length = line.length() * SCALING_FACTOR; if (line_length < EPSILON) continue; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index b2047bd3aa..d47e8f7c73 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -221,7 +221,7 @@ public: const Vec2d& origin() const { return m_origin; } void set_origin(const Vec2d &pointf); void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); } - const Point& last_pos() const { return m_last_pos; } + Point last_pos() const { return m_last_pos.to_point(); } Vec2d point_to_gcode(const Point &point) const; Point gcode_to_point(const Vec2d &point) const; Vec2d point_to_gcode_quantized(const Point& point) const; @@ -381,7 +381,8 @@ private: void check_placeholder_parser_failed(); size_t get_extruder_id(unsigned int filament_id) const; - void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } + void set_last_pos(const Point &pos) { m_last_pos = Point3(pos, 0); m_last_pos_defined = true; } + void set_last_pos(const Point3 &pos) { m_last_pos = pos; m_last_pos_defined = true; } bool last_pos_defined() const { return m_last_pos_defined; } void set_extruders(const std::vector &extruder_ids); std::string preamble(); @@ -392,9 +393,9 @@ private: std::string extrude_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1., const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr()); // Orca: pass the complete collection of region perimeters to the extrude loop to check whether the wipe before external loop // should be executed - std::string extrude_loop(ExtrusionLoop loop, std::string description, double speed = -1., const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr(), const Point* start_point = nullptr); - std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.); - std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.); + std::string extrude_loop(const ExtrusionLoop &loop, std::string description, double speed = -1., const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr(), const Point* start_point = nullptr); + std::string extrude_multi_path(const ExtrusionMultiPath &multipath, std::string description = "", double speed = -1.); + std::string extrude_path(const ExtrusionPath &path, std::string description = "", double speed = -1.); // Orca: Adaptive PA variables // Used for adaptive PA when extruding paths with multiple, varying flow segments. @@ -582,7 +583,7 @@ private: std::map> m_placeholder_error_messages; #endif - Point m_last_pos; + Point3 m_last_pos; bool m_last_pos_defined; std::unique_ptr m_cooling_buffer; diff --git a/src/libslic3r/GCode/ConflictChecker.hpp b/src/libslic3r/GCode/ConflictChecker.hpp index 30a83676e8..a10b969431 100644 --- a/src/libslic3r/GCode/ConflictChecker.hpp +++ b/src/libslic3r/GCode/ConflictChecker.hpp @@ -87,7 +87,7 @@ public: for (int i = b; i < e; ++i) { for (const ExtrusionPath &path : _piles[i].paths) { if (path.is_force_no_extrusion() == false) { - Polyline check_polyline = path.polyline; + Polyline check_polyline = path.polyline.to_polyline(); check_polyline.translate(_offset); Lines tmpLines = check_polyline.lines(); for (const Line &line : tmpLines) { lines.emplace_back(line, _id, path.role()); } diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index 62a01db11e..dad88a7485 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -81,7 +81,19 @@ std::vector estimate_points_properties(const POINTS if (input_points.empty()) return {}; float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f; - auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast(); }; + auto maybe_unscale = [](const P &p) -> Vec2d { + if constexpr (P::RowsAtCompileTime == 3) { + // 3D point - extract XY only + if constexpr (SCALED_INPUT) { + return unscaled(p).template head<2>(); + } else { + return p.template head<2>().template cast(); + } + } else { + // 2D point - use as is + return SCALED_INPUT ? unscaled(p) : p.template cast(); + } + }; std::vector points; points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1)); diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index af54f46a1f..4a65ae5ae4 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -30,7 +30,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path) { - BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))); + BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline.to_polyline(), coord_t(scale_(0.5 * extrusion_path.width))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = unscale(bbox.min); @@ -44,7 +44,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio { BoundingBox bbox; for (const ExtrusionPath &extrusion_path : extrusion_loop.paths) - bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)))); + bbox.merge(extrusion_polyline_extents(extrusion_path.polyline.to_polyline(), coord_t(scale_(0.5 * extrusion_path.width)))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = unscale(bbox.min); @@ -58,7 +58,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext { BoundingBox bbox; for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths) - bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)))); + bbox.merge(extrusion_polyline_extents(extrusion_path.polyline.to_polyline(), coord_t(scale_(0.5 * extrusion_path.width)))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = unscale(bbox.min); diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index d75f0c35fc..12f19f6fa8 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1514,7 +1514,8 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, current.path_idx = next_idx_modulo(current.path_idx, loop.paths.size()); current.segment_idx = 0; } - current.foot_pt = loop.paths[current.path_idx].polyline.points[current.segment_idx]; + const Point3 &p3 = loop.paths[current.path_idx].polyline.points[current.segment_idx]; + current.foot_pt = Point(p3.x(), p3.y()); return current; }; @@ -1527,7 +1528,8 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, size_t closest_perimeter_point_index = 0; { // local space for the closest_perimeter_point_index Perimeter *closest_perimeter = nullptr; - ExtrusionLoop::ClosestPathPoint closest_point{0,0,loop.paths[0].polyline.points[0]}; + const Point3 &init_p3 = loop.paths[0].polyline.points[0]; + ExtrusionLoop::ClosestPathPoint closest_point{0,0,Point(init_p3.x(), init_p3.y())}; size_t points_count = std::accumulate(loop.paths.begin(), loop.paths.end(), 0, [](size_t acc,const ExtrusionPath& p) { return acc + p.polyline.points.size(); }); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 4e9fdad059..f1acad9ba7 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -26,6 +26,10 @@ namespace FillLightning { class Generator; }; +namespace sla { + class IndexedMesh; +}; + class LayerRegion { public: @@ -191,6 +195,7 @@ public: FillAdaptive::Octree *support_fill_octree, FillLightning::Generator* lightning_generator) const; void make_ironing(); + void make_contour_z(const sla::IndexedMesh &mesh); void export_region_slices_to_svg(const char *path) const; void export_region_fill_surfaces_to_svg(const char *path) const; diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 028ea2aafa..28423b0dfc 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -224,17 +224,23 @@ using CurledLines = std::vector; class Line3 { public: - Line3() : a(Vec3crd::Zero()), b(Vec3crd::Zero()) {} - Line3(const Vec3crd& _a, const Vec3crd& _b) : a(_a), b(_b) {} + Line3() : a(Point3()), b(Point3()) {} + Line3(const Point3& _a, const Point3& _b) : a(_a), b(_b) {} + // Backward compatibility with Vec3crd + Line3(const Vec3crd& _a, const Vec3crd& _b) : a(Point3(_a)), b(Point3(_b)) {} double length() const { return (this->a - this->b).cast().norm(); } - Vec3crd vector() const { return this->b - this->a; } + Point3 vector() const { Vec3crd v = this->b - this->a; return Point3(v.x(), v.y(), v.z()); } + Point3 midpoint() const { return Point3((this->a.x() + this->b.x()) / 2, (this->a.y() + this->b.y()) / 2, (this->a.z() + this->b.z()) / 2); } - Vec3crd a; - Vec3crd b; + // Convert to 2D line by dropping Z coordinate + Line to_line() const { return Line(this->a.to_point(), this->b.to_point()); } + + Point3 a; + Point3 b; static const constexpr int Dim = 3; - using Scalar = Vec3crd::Scalar; + using Scalar = coord_t; }; class Linef @@ -243,6 +249,10 @@ public: Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {} Linef(const Vec2d& _a, const Vec2d& _b) : a(_a), b(_b) {} + Vec2d vector() const { return this->b - this->a; } + Vec2d unit_vector() const { return (length() == 0.0) ? Vec2d::Zero() : vector().normalized(); } + double length() const { return vector().norm(); } + Vec2d a; Vec2d b; @@ -263,6 +273,32 @@ public: Vec3d unit_vector() const { return (length() == 0.0) ? Vec3d::Zero() : vector().normalized(); } double length() const { return vector().norm(); } + double distance_to_infinite_squared(const Vec3d &point, Vec3d *closest_point) const { + const Vec3d v = this->b - this->a; + const Vec3d va = point - this->a; + const double l2 = v.squaredNorm(); + if (l2 == 0.) { + // a == b case + *closest_point = this->a; + return va.squaredNorm(); + } + // Consider the line extending the segment, parameterized as a + t (b - a). + // Find parameter value t of the projection of point onto the line. + const double t = va.dot(v) / l2; + *closest_point = this->a + t * v; + return (point - *closest_point).squaredNorm(); + } + + double distance_to_infinite_squared(const Vec3d &point) const { + Vec3d nearest_point; + return distance_to_infinite_squared(point, &nearest_point); + } + + static inline double distance_to_infinite_squared(const Vec3d &point, const Vec3d &a, const Vec3d &b) { + Linef3 line{a, b}; + return line.distance_to_infinite_squared(point); + } + Vec3d a; Vec3d b; diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index fbf898234e..cb9c67f0f9 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -471,6 +471,51 @@ bool MultiPoint3::remove_duplicate_points() return false; } +// Douglas-Peucker simplification for 3D points +Points3 MultiPoint3::_douglas_peucker(const Points3 &points, double tolerance) +{ + if (points.size() <= 2) return points; + + // Find the point with maximum distance from line segment + double max_dist = 0; + size_t max_idx = 0; + const Point3 &first = points.front(); + const Point3 &last = points.back(); + Line3 line(first, last); + + for (size_t i = 1; i < points.size() - 1; ++i) { + // Calculate perpendicular distance to line segment + Point3 proj = points[i].projection_onto(line); + double dist = points[i].distance_to(proj); + if (dist > max_dist) { + max_dist = dist; + max_idx = i; + } + } + + // If max distance is greater than tolerance, recursively simplify + if (max_dist > tolerance) { + // Recursive call for first part + Points3 left_points(points.begin(), points.begin() + max_idx + 1); + Points3 left_result = _douglas_peucker(left_points, tolerance); + + // Recursive call for second part + Points3 right_points(points.begin() + max_idx, points.end()); + Points3 right_result = _douglas_peucker(right_points, tolerance); + + // Concatenate results (avoiding duplicate middle point) + Points3 result = left_result; + result.insert(result.end(), right_result.begin() + 1, right_result.end()); + return result; + } else { + // All points between first and last can be removed + Points3 result; + result.push_back(first); + result.push_back(last); + return result; + } +} + BoundingBox get_extents(const MultiPoint &mp) { return BoundingBox(mp.points); @@ -514,4 +559,78 @@ void MultiPoint::symmetric_y(const coord_t &x_axis) } } +// MultiPoint3 implementations +void MultiPoint3::rotate(double cos_angle, double sin_angle) +{ + for (Point3 &pt : this->points) { + double cur_x = double(pt(0)); + double cur_y = double(pt(1)); + pt(0) = coord_t(round(cos_angle * cur_x - sin_angle * cur_y)); + pt(1) = coord_t(round(cos_angle * cur_y + sin_angle * cur_x)); + // Keep Z unchanged + } +} + +void MultiPoint3::rotate(double angle, const Point3 ¢er) +{ + double s = sin(angle); + double c = cos(angle); + for (Point3 &pt : points) { + Vec3crd v(pt - center); + pt(0) = (coord_t)round(double(center(0)) + c * v[0] - s * v[1]); + pt(1) = (coord_t)round(double(center(1)) + c * v[1] + s * v[0]); + // Keep Z unchanged from original point + } +} + +int MultiPoint3::find_point(const Point &point) const +{ + for (const Point3 &pt : this->points) + if (pt.to_point() == point) + return int(&pt - &this->points.front()); + return -1; // not found +} + +int MultiPoint3::find_point(const Point &point, double scaled_epsilon) const +{ + if (scaled_epsilon == 0) return this->find_point(point); + + auto dist2_min = std::numeric_limits::max(); + auto eps2 = scaled_epsilon * scaled_epsilon; + int idx_min = -1; + for (const Point3 &pt : this->points) { + double d2 = (pt.to_point() - point).cast().squaredNorm(); + if (d2 < dist2_min) { + idx_min = int(&pt - &this->points.front()); + dist2_min = d2; + } + } + return (dist2_min < eps2) ? idx_min : -1; +} + +int MultiPoint3::find_point(const Point3 &point) const +{ + for (const Point3 &pt : this->points) + if (pt == point) + return int(&pt - &this->points.front()); + return -1; // not found +} + +int MultiPoint3::find_point(const Point3 &point, double scaled_epsilon) const +{ + if (scaled_epsilon == 0) return this->find_point(point); + + auto dist2_min = std::numeric_limits::max(); + auto eps2 = scaled_epsilon * scaled_epsilon; + int idx_min = -1; + for (const Point3 &pt : this->points) { + double d2 = (pt - point).cast().squaredNorm(); + if (d2 < dist2_min) { + idx_min = int(&pt - &this->points.front()); + dist2_min = d2; + } + } + return (dist2_min < eps2) ? idx_min : -1; +} + } diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 0dfb98f4b4..44d1e86cf1 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -119,18 +119,46 @@ class MultiPoint3 public: Points3 points; - void append(const Vec3crd& point) { this->points.push_back(point); } + void append(const Point3& point) { this->points.push_back(point); } + void append(const Vec3crd& point) { this->points.push_back(Point3(point)); } void translate(double x, double y); void translate(const Point& vector); + void reverse() { std::reverse(this->points.begin(), this->points.end()); } + void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } + void rotate(double cos_angle, double sin_angle); + void rotate(double angle, const Point3 ¢er); + + Point3& first_point() { return this->points.front(); } + Point3& last_point() { return this->points.back(); } + const Point3& first_point() const { return this->points.front(); } + const Point3& last_point() const { return this->points.back(); } + size_t size() const { return this->points.size(); } + bool empty() const { return this->points.empty(); } + void clear() { this->points.clear(); } + + auto begin() { return this->points.begin(); } + auto end() { return this->points.end(); } + auto begin() const { return this->points.begin(); } + auto end() const { return this->points.end(); } + virtual Lines3 lines() const = 0; double length() const; bool is_valid() const { return this->points.size() >= 2; } BoundingBox3 bounding_box() const; + // Find a point in the points array + int find_point(const Point &point) const; + int find_point(const Point &point, const double scaled_epsilon) const; + int find_point(const Point3 &point) const; + int find_point(const Point3 &point, const double scaled_epsilon) const; + // Remove exact duplicates, return true if any duplicate has been removed. bool remove_duplicate_points(); + + // Douglas-Peucker simplification + static Points3 _douglas_peucker(const Points3 &points, double tolerance); }; extern BoundingBox get_extents(const MultiPoint &mp); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index cc055c9d8a..b7cb3e34ef 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -206,7 +206,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. if(paths.empty()) continue; - chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); + Point start_pt = Point(paths.front().first_point().x(), paths.front().first_point().y()); + chain_and_reorder_extrusion_paths(paths, &start_pt); } else { if (overhangs_reverse && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) { // Always reverse if detect overhang wall is not enabled @@ -216,7 +217,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime ExtrusionPath path(role); //BBS. - path.polyline = polygon.split_at_first_point(); + path.polyline = Polyline3(polygon.split_at_first_point()); path.mm3_per_mm = extrusion_mm3_per_mm; path.width = extrusion_width; path.height = (float)perimeter_generator.layer_height; @@ -429,7 +430,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p Polylines be_clipped; for (const ExtrusionPath &p : it.second) { - be_clipped.emplace_back(std::move(p.polyline)); + be_clipped.emplace_back(p.polyline.to_polyline()); } BoundingBox extrusion_bboxs = get_extents(be_clipped); @@ -463,11 +464,15 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p }; std::unordered_map point_occurrence; for (const ExtrusionPath& path : paths) { - ++point_occurrence[path.polyline.first_point()].occurrence; - ++point_occurrence[path.polyline.last_point()].occurrence; + const Point3 &first_p3 = path.polyline.first_point(); + const Point3 &last_p3 = path.polyline.last_point(); + Point first_p = Point(first_p3.x(), first_p3.y()); + Point last_p = Point(last_p3.x(), last_p3.y()); + ++point_occurrence[first_p].occurrence; + ++point_occurrence[last_p].occurrence; if (path.role() == erOverhangPerimeter) { - point_occurrence[path.polyline.first_point()].is_overhang = true; - point_occurrence[path.polyline.last_point()].is_overhang = true; + point_occurrence[first_p].is_overhang = true; + point_occurrence[last_p].is_overhang = true; } } @@ -655,11 +660,13 @@ bool paths_touch(const ExtrusionPath &path_one, const ExtrusionPath &path_two, d { AABBTreeLines::LinesDistancer lines_two{path_two.as_polyline().lines()}; for (size_t pt_idx = 0; pt_idx < path_one.polyline.size(); pt_idx++) { - if (lines_two.distance_from_lines(path_one.polyline.points[pt_idx]) < limit_distance) { return true; } + const Point3 &p3 = path_one.polyline.points[pt_idx]; + if (lines_two.distance_from_lines(Point(p3.x(), p3.y())) < limit_distance) { return true; } } AABBTreeLines::LinesDistancer lines_one{path_one.as_polyline().lines()}; for (size_t pt_idx = 0; pt_idx < path_two.polyline.size(); pt_idx++) { - if (lines_one.distance_from_lines(path_two.polyline.points[pt_idx]) < limit_distance) { return true; } + const Point3 &p3 = path_two.polyline.points[pt_idx]; + if (lines_one.distance_from_lines(Point(p3.x(), p3.y())) < limit_distance) { return true; } } return false; } @@ -1013,7 +1020,7 @@ std::tuple, Polygons> generate_extra_perimeters_over // polyline) bool first_overhang_is_closed_and_anchored = (overhang_region.front().first_point() == overhang_region.front().last_point() && - !intersection_pl(overhang_region.front().polyline, optimized_lower_slices).empty()); + !intersection_pl(overhang_region.front().polyline.to_polyline(), optimized_lower_slices).empty()); auto is_anchored = [&lower_layer_aabb_tree](const ExtrusionPath &path) { return lower_layer_aabb_tree.distance_from_lines(path.first_point()) <= 0 || @@ -1025,7 +1032,8 @@ std::tuple, Polygons> generate_extra_perimeters_over size_t min_dist_idx = 0; double min_dist = std::numeric_limits::max(); for (size_t i = 0; i < overhang_region.front().polyline.size(); i++) { - Point p = overhang_region.front().polyline[i]; + const Point3 &p3 = overhang_region.front().polyline.points[i]; + Point p = Point(p3.x(), p3.y()); if (double d = lower_layer_aabb_tree.distance_from_lines(p) < min_dist) { min_dist = d; min_dist_idx = i; diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 80c4d9815a..eea69b1195 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -1,6 +1,7 @@ #include "Point.hpp" #include "Line.hpp" #include "MultiPoint.hpp" +#include "Polyline.hpp" #include "Int128.hpp" #include "BoundingBox.hpp" #include @@ -257,4 +258,80 @@ int cross(const Vec2crd &v1, const Vec2crd &v2) } +// Point3 utility functions for ZAA (Z Anti-Aliasing) +Polyline to_polyline(const Points &points) { return Polyline(points); } +Polyline3 to_polyline(const Points3 &points) { return Polyline3(points); } + +Points to_points(const Points3 &points3) { + Points points2; + points2.reserve(points3.size()); + for (const Point3 &pt : points3) { + points2.push_back(pt.to_point()); + } + return points2; +} + +// Point3 method implementations +void Point3::rotate(double angle, const Point3 ¢er) { + Vec3crd diff = *this - center; + Point3 temp(diff.x(), diff.y(), diff.z()); + temp.rotate(angle); + Vec3crd sum = temp + center; + *this = Point3(sum.x(), sum.y(), sum.z()); +} + +int Point3::nearest_point_index(const Points &points) const { + return this->to_point().nearest_point_index(points); +} + +bool Point3::nearest_point(const Points &points, Point3* point) const { + Point pt2d; + bool result = this->to_point().nearest_point(points, &pt2d); + if (result && point) { + *point = Point3(pt2d, this->z()); + } + return result; +} + +double Point3::ccw(const Point3 &p1, const Point3 &p2) const { + return this->to_point().ccw(p1.to_point(), p2.to_point()); +} + +double Point3::ccw(const Line3 &line) const { + // Convert to 2D and use existing Point ccw implementation + Point a2d(line.a.x(), line.a.y()); + Point b2d(line.b.x(), line.b.y()); + return this->to_point().ccw(Line(a2d, b2d)); +} + +double Point3::ccw_angle(const Point3 &p1, const Point3 &p2) const { + return this->to_point().ccw_angle(p1.to_point(), p2.to_point()); +} + +Point3 Point3::projection_onto(const MultiPoint3 &poly) const { + // TODO: Implement proper 3D projection when MultiPoint3 conversion methods are ready + // For now, stub implementation + return *this; +} + +Point3 Point3::projection_onto(const Line3 &line) const { + // Project in 2D plane and interpolate Z + Point pt2d = this->to_point(); + Point line_a(line.a.x(), line.a.y()); + Point line_b(line.b.x(), line.b.y()); + Line line2d(line_a, line_b); + Point proj2d = pt2d.projection_onto(line2d); + + // Interpolate Z coordinate + double line_len = line.length(); + if (line_len < EPSILON) { + return Point3(proj2d, line.a.z()); + } + double dist_from_a = (proj2d - line_a).cast().norm(); + double t = dist_from_a / line_len; + t = std::clamp(t, 0.0, 1.0); + coord_t z = coord_t(line.a.z() + t * (line.b.z() - line.a.z())); + return Point3(proj2d, z); +} + } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index eda75a2b2f..f75bbb9a46 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -21,8 +21,13 @@ namespace Slic3r { class BoundingBox; class BoundingBoxf; class Line; +class Line3; class MultiPoint; +class MultiPoint3; class Point; +class Point3; +class Polyline; +class Polyline3; using Vector = Point; // Base template for eigen derived vectors @@ -57,7 +62,7 @@ using PointsAllocator = tbb::scalable_allocator; using Points = std::vector>; using PointPtrs = std::vector; using PointConstPtrs = std::vector; -using Points3 = std::vector; +using Points3 = std::vector; using Pointfs = std::vector; using Vec2ds = std::vector; using Pointf3s = std::vector; @@ -79,6 +84,11 @@ using Transform2d = Eigen::Transform; using Transform3d = Eigen::Transform; +// Utility functions for Point/Polyline conversion +Polyline to_polyline(const Points &points); +Polyline3 to_polyline(const Points3 &points); +Points to_points(const Points3 &points); + // using ColorRGBA = std::array; // I don't know why Eigen::Transform::Identity() return a const object... template Transform identity() { return Transform::Identity(); } @@ -258,6 +268,115 @@ inline Point operator* (const Point& l, const double& r) return { coord_t(l.x() * r), coord_t(l.y() * r) }; } +// Point3 class - 3D point with Z coordinate for non-planar printing (ZAA) +class Point3 : public Vec3crd { +public: + using coord_type = coord_t; + + Point3() : Vec3crd(0, 0, 0) {} + Point3(int32_t x, int32_t y, int32_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {} + Point3(int64_t x, int64_t y, int64_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {} + Point3(double x, double y, double z = 0.0) : Vec3crd(coord_t(std::round(x)), coord_t(std::round(y)), coord_t(std::round(z))) {} + Point3(const Point3 &rhs) { *this = rhs; } + explicit Point3(const Point &rhs, coord_t z = 0) : Vec3crd(rhs.x(), rhs.y(), z) {} + explicit Point3(const Vec3crd &vec3crd) : Vec3crd(vec3crd) {} + + static Point3 new_scale(coordf_t x, coordf_t y, coordf_t z) { + return Point3(coord_t(scale_(x)), coord_t(scale_(y)), coord_t(scale_(z))); + } + static Point3 new_scale(const Vec3d &v) { + return Point3(coord_t(scale_(v.x())), coord_t(scale_(v.y())), coord_t(scale_(v.z()))); + } + static Point3 new_scale(const Vec3f &v) { + return Point3(coord_t(scale_(v.x())), coord_t(scale_(v.y())), coord_t(scale_(v.z()))); + } + + // Assignment operator for Eigen expressions + template + Point3& operator=(const Eigen::MatrixBase &other) + { + this->Vec3crd::operator=(other); + return *this; + } + + Point3& operator+=(const Point3& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); this->z() += rhs.z(); return *this; } + Point3& operator-=(const Point3& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); this->z() -= rhs.z(); return *this; } + Point3& operator*=(const double &rhs) { + this->x() = coord_t(this->x() * rhs); + this->y() = coord_t(this->y() * rhs); + this->z() = coord_t(this->z() * rhs); + return *this; + } + Point3 operator*(const double &rhs) const { return Point3(this->x() * rhs, this->y() * rhs, this->z() * rhs); } + + bool both_comp(const Point3 &rhs, const std::string& op) { + if (op == ">") + return this->x() > rhs.x() && this->y() > rhs.y(); + else if (op == "<") + return this->x() < rhs.x() && this->y() < rhs.y(); + return false; + } + bool any_comp(const Point3 &rhs, const std::string &op) + { + if (op == ">") + return this->x() > rhs.x() || this->y() > rhs.y(); + else if (op == "<") + return this->x() < rhs.x() || this->y() < rhs.y(); + return false; + } + bool any_comp(const coord_t val, const std::string &op) + { + if (op == ">") + return this->x() > val || this->y() > val; + else if (op == "<") + return this->x() < val || this->y() < val; + return false; + } + + void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); } + void rotate(double cos_a, double sin_a) { + double cur_x = (double)this->x(); + double cur_y = (double)this->y(); + this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y); + this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x); + } + void rotate(double angle, const Point3 ¢er); + + Point3 rotated(double angle) const { Point3 res(*this); res.rotate(angle); return res; } + Point3 rotated(double cos_a, double sin_a) const { Point3 res(*this); res.rotate(cos_a, sin_a); return res; } + Point3 rotated(double angle, const Point3 ¢er) const { Point3 res(*this); res.rotate(angle, center); return res; } + Point3 rotate_90_degree_ccw() const { return Point3(-this->y(), this->x(), this->z()); } + + int nearest_point_index(const Points &points) const; + bool nearest_point(const Points &points, Point3* point) const; + double ccw(const Point3 &p1, const Point3 &p2) const; + double ccw(const Line3 &line) const; + double ccw_angle(const Point3 &p1, const Point3 &p2) const; + Point3 projection_onto(const MultiPoint3 &poly) const; + Point3 projection_onto(const Line3 &line) const; + + // Convert to 2D Point by dropping Z coordinate + Point to_point() const { + return Point(this->x(), this->y()); + } + + double distance_to(const Point3 &point) const { return (point - *this).cast().norm(); } +}; + +// Utility function to convert Points3 to Points +inline void append_points(Points &dst, const Points3 &src) { + std::transform(src.begin(), src.end(), + std::back_inserter(dst), + [](const Point3 &pt) { + return pt.to_point(); + }); +} + +inline Point3 operator* (const Point3& l, const double& r) +{ + return { coord_t(l.x() * r), coord_t(l.y() * r), coord_t(l.z() * r) }; +} + inline std::ostream &operator<<(std::ostream &os, const Point &pt) { os << unscale_(pt.x()) << "," << unscale_(pt.y()); diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index b30564f3c1..8ed180c72d 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -612,6 +612,28 @@ std::pair foot_pt(const Points &polyline, const Point &pt) return std::make_pair(int(it_proj - polyline.begin()) - 1, foot_pt_min); } +std::pair foot_pt(const Points3 &polyline, const Point3 &pt) +{ + if (polyline.size() < 2) return std::make_pair(-1, Point3(0, 0, 0)); + + auto d2_min = std::numeric_limits::max(); + Point3 foot_pt_min; + Point3 prev = polyline.front(); + auto it = polyline.begin(); + auto it_proj = polyline.begin(); + for (++it; it != polyline.end(); ++it) { + Point3 foot_pt = pt.projection_onto(Line3(prev, *it)); + double d2 = (foot_pt - pt).cast().squaredNorm(); + if (d2 < d2_min) { + d2_min = d2; + foot_pt_min = foot_pt; + it_proj = it; + } + prev = *it; + } + return std::make_pair(int(it_proj - polyline.begin()) - 1, foot_pt_min); +} + ThickLines ThickPolyline::thicklines() const { ThickLines lines; @@ -650,4 +672,217 @@ Lines3 Polyline3::lines() const return lines; } +// Polyline3 ZAA methods implementation +Polyline Polyline3::to_polyline() const { + Polyline out; + out.points.reserve(this->points.size()); + for (const Point3 &point : this->points) { + out.points.emplace_back(point.x(), point.y()); + } + return out; +} + +void Polyline3::clip_end(double distance) { + size_t remove_after_index = this->size(); + while (distance > 0) { + Vec3d last_point = this->last_point().cast(); + this->points.pop_back(); + remove_after_index--; + if (this->points.empty()) { + this->fitting_result.clear(); + return; + } + Vec3d v = this->last_point().cast() - last_point; + double lsqr = v.squaredNorm(); + if (lsqr > distance * distance) { + Vec3d result = last_point + v * (distance / sqrt(lsqr)); + this->points.emplace_back(Point3(coord_t(result.x()), coord_t(result.y()), coord_t(result.z()))); + break; + } + distance -= sqrt(lsqr); + } + + // Clear fitting result if it's affected + if (!fitting_result.empty()) { + while (!fitting_result.empty() && fitting_result.back().start_point_index >= remove_after_index) + fitting_result.pop_back(); + if (!fitting_result.empty()) { + fitting_result.back().end_point_index = this->points.size() - 1; + } + } +} + +void Polyline3::simplify(double tolerance) { + this->points = MultiPoint3::_douglas_peucker(this->points, tolerance); + this->fitting_result.clear(); +} + +void Polyline3::simplify_by_fitting_arc(double tolerance) { + // For now, just use regular simplify + // Full ZAA implementation would use ArcFitter::do_arc_fitting_and_simplify + this->simplify(tolerance); +} + +bool Polyline3::split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const +{ + if (index > this->size() - 1) + return false; + + if (index == 0) { + p1->clear(); + p1->append(this->first_point()); + *p2 = *this; + } else if (index == this->size() - 1) { + p2->clear(); + p2->append(this->last_point()); + *p1 = *this; + } else { + // Split first part + p1->clear(); + p1->points.reserve(index + 1); + p1->points.insert(p1->begin(), this->begin(), this->begin() + index + 1); + Point3 new_endpoint; + if (this->split_fitting_result_before_index(index, new_endpoint, p1->fitting_result)) + p1->points.back() = new_endpoint; + + // Split second part + p2->clear(); + p2->points.reserve(this->size() - index); + p2->points.insert(p2->begin(), this->begin() + index, this->end()); + Point3 new_startpoint; + if (this->split_fitting_result_after_index(index, new_startpoint, p2->fitting_result)) + p2->points.front() = new_startpoint; + } + return true; +} + +void Polyline3::append(const Point3& point) { + // Don't append if same as last point + if (!this->empty() && this->last_point() == point) + return; + + this->points.push_back(point); + // Clear fitting result as structure changed + this->fitting_result.clear(); +} + +void Polyline3::append(const Polyline3 &src) { + if (!src.is_valid()) return; + + if (this->points.empty()) { + this->points = src.points; + this->fitting_result = src.fitting_result; + } else { + // Append points + if (!src.points.empty() && !this->points.empty() && this->last_point() == src.points.front()) { + // Skip first point if it's the same as our last point + this->points.insert(this->points.end(), src.points.begin() + 1, src.points.end()); + } else { + this->points.insert(this->points.end(), src.points.begin(), src.points.end()); + } + // Note: Full arc fitting integration would merge fitting_result here + this->fitting_result.clear(); + } +} + +void Polyline3::append_before(const Point3& point) { + // Don't append if same as first point + if (!this->empty() && this->first_point() == point) + return; + + this->points.insert(this->points.begin(), point); + // Clear fitting result as structure changed + this->fitting_result.clear(); +} + +void Polyline3::split_at(Point &point, Polyline3* p1, Polyline3* p2) const { + if (this->points.empty()) return; + + // Check if the point is on the polyline + int index = this->find_point(point); + if (index != -1) { + // The split point is on the polyline + split_at_index(index, p1, p2); + point = p1->is_valid() ? p1->last_point().to_point() : p2->first_point().to_point(); + return; + } + + // Find the line to split at + size_t line_idx = 0; + Point p = this->first_point().to_point(); + double min = (p - point).cast().norm(); + Lines3 lines = this->lines(); + for (Lines3::const_iterator line = lines.begin(); line != lines.end(); ++line) { + Point p_tmp = point.projection_onto(line->to_line()); + if ((p_tmp - point).cast().norm() < min) { + p = p_tmp; + min = (p - point).cast().norm(); + line_idx = line - lines.begin(); + } + } + + // Judge whether the closest point is one vertex of polyline + index = this->find_point(p); + if (index != -1) { + this->split_at_index(index, p1, p2); + } else { + Polyline3 temp; + this->split_at_index(line_idx, p1, &temp); + p1->append_before(Point3(point, p1->last_point().z())); + this->split_at_index(line_idx + 1, &temp, p2); + p2->append_before(Point3(point, p2->first_point().z())); + } + point = p; +} + +void Polyline3::split_at(Point3 &point, Polyline3* p1, Polyline3* p2) const { + Point p = point.to_point(); + this->split_at(p, p1, p2); + point = Point3(p, point.z()); +} + +bool Polyline3::split_at_length(const double length, Polyline3 *p1, Polyline3 *p2) const { + if (this->points.empty()) return false; + if (length < 0 || length > this->length()) { return false; } + + if (length < SCALED_EPSILON) { + p1->clear(); + p1->append_before(this->first_point()); + *p2 = *this; + } else if (is_approx(length, this->length(), SCALED_EPSILON)) { + p2->clear(); + p2->append_before(this->last_point()); + *p1 = *this; + } else { + // Find the line to split at + size_t line_idx = 0; + double acc_length = 0; + Point p = this->first_point().to_point(); + for (const auto &l : this->lines()) { + p = l.b.to_point(); + + const double current_length = l.length(); + if (acc_length + current_length >= length) { + p = lerp(l.a.to_point(), l.b.to_point(), (length - acc_length) / current_length); + break; + } + acc_length += current_length; + line_idx++; + } + + // Judge whether the closest point is one vertex of polyline + int index = this->find_point(p); + if (index != -1) { + this->split_at_index(index, p1, p2); + } else { + Polyline3 temp; + this->split_at_index(line_idx, p1, &temp); + p1->append_before(Point3(p, p1->last_point().z())); + this->split_at_index(line_idx + 1, &temp, p2); + p2->append_before(Point3(p, p2->first_point().z())); + } + } + return true; +} + } diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index bff65f7a2f..6d409d3983 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -251,6 +251,7 @@ bool remove_degenerate(Polylines &polylines); // Returns index of a segment of a polyline and foot point of pt on polyline. std::pair foot_pt(const Points &polyline, const Point &pt); +std::pair foot_pt(const Points3 &polyline, const Point3 &pt); class ThickPolyline : public Polyline { public: @@ -290,7 +291,68 @@ inline ThickPolylines to_thick_polylines(Polylines&& polylines, const coordf_t w class Polyline3 : public MultiPoint3 { public: + Polyline3() {} + explicit Polyline3(const Points3 &points) { this->points = points; } + explicit Polyline3(const Polyline &poly, coord_t z = 0) { + this->points.reserve(poly.points.size()); + for (const Point &pt : poly.points) { + this->points.emplace_back(pt.x(), pt.y(), z); + } + } + virtual Lines3 lines() const; + + // Convert to 2D Polyline by dropping Z coordinates + Polyline to_polyline() const; + + // Clip the end of the polyline by a distance + void clip_end(double distance); + + // Simplify polyline using Douglas-Peucker algorithm + void simplify(double tolerance); + + // Simplify by arc fitting (for ZAA arc fitting support) + void simplify_by_fitting_arc(double tolerance); + + // Reverse the polyline + using MultiPoint3::reverse; + + // Split polyline at given index + bool split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const; + + // Split polyline at a given point (2D) + void split_at(Point &point, Polyline3* p1, Polyline3* p2) const; + + // Split polyline at a given point (3D) + void split_at(Point3 &point, Polyline3* p1, Polyline3* p2) const; + + // Split polyline at a given length + bool split_at_length(const double length, Polyline3 *p1, Polyline3 *p2) const; + + // Append a single point + void append(const Point3& point); + + // Append another Polyline3 + void append(const Polyline3& src); + + // Append before (prepend) + void append_before(const Point3& point); + + // Arc fitting support - fitting_result stores arc path data + // This is populated by simplify_by_fitting_arc() + // Uses the global PathFittingData from ArcFitter.hpp + std::vector fitting_result; + +private: + // Helper methods for split_at_index + bool split_fitting_result_before_index(size_t index, Point3 &new_endpoint, std::vector &result) const { + // Simplified stub - full implementation would handle arc fitting data + return false; + } + bool split_fitting_result_after_index(size_t index, Point3 &new_startpoint, std::vector &result) const { + // Simplified stub - full implementation would handle arc fitting data + return false; + } }; typedef std::vector Polylines3; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 4656665a18..e8b393b81f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -953,6 +953,8 @@ static std::vector s_Preset_print_options { "enable_wrapping_detection", "seam_slope_type", "seam_slope_conditional", "scarf_angle_threshold", "scarf_joint_speed", "scarf_joint_flow_ratio", "seam_slope_start_height", "seam_slope_entire_loop", "seam_slope_min_length", "seam_slope_steps", "seam_slope_inner_walls", "scarf_overhang_threshold", "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width","calib_flowrate_topinfill_special_order", + // Z Anti-Aliasing (ZAA) + "zaa_enabled", "zaa_minimize_perimeter_height", "zaa_dont_alternate_fill_direction", "zaa_min_z", "zaa_region_disable", "ironing_expansion", }; static std::vector s_Preset_filament_options {/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "pellet_flow_coefficient", "volumetric_speed_coefficients", "filament_type", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b7ab12ee0d..88119e4983 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2148,6 +2148,17 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) } } + // Z-Contouring + for (PrintObject *obj : m_objects) { + bool need_contouring = need_slicing_objects.count(obj) != 0 && obj->config().zaa_enabled; + if (need_contouring) { + obj->contour_z(); + } else { + if (obj->set_started(posContouring)) + obj->set_done(posContouring); + } + } + tbb::parallel_for(tbb::blocked_range(0, int(m_objects.size())), [this, need_slicing_objects](const tbb::blocked_range& range) { for (int i = range.begin(); i < range.end(); i++) { @@ -2186,6 +2197,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) obj->set_done(posInfill); if (obj->set_started(posIroning)) obj->set_done(posIroning); + if (obj->set_started(posContouring)) + obj->set_done(posContouring); if (obj->set_started(posSupportMaterial)) obj->set_done(posSupportMaterial); if (obj->set_started(posDetectOverhangsForLift)) @@ -2565,7 +2578,7 @@ void Print::_make_skirt() flow.width(), (float)initial_layer_print_height // this will be overridden at G-code export time ))); - eloop.paths.back().polyline = loop.split_at_first_point(); + eloop.paths.back().polyline = Polyline3(loop.split_at_first_point()); m_skirt.append(eloop); if (m_config.min_skirt_length.value > 0) { // The skirt length is limited. Sum the total amount of filament length extruded, in mm. @@ -2623,7 +2636,7 @@ void Print::_make_skirt() flow.width(), (float)initial_layer_print_height // this will be overridden at G-code export time ))); - eloop.paths.back().polyline = loop.split_at_first_point(); + eloop.paths.back().polyline = Polyline3(loop.split_at_first_point()); object->m_skirt.append(std::move(eloop)); if (m_config.min_skirt_length.value > 0) { // The skirt length is limited. Sum the total amount of filament length extruded, in mm. @@ -4018,7 +4031,8 @@ static void from_json(const json& j, Polyline& poly_line) { } static void from_json(const json& j, ExtrusionPath& extrusion_path) { - extrusion_path.polyline = j[JSON_EXTRUSION_POLYLINE]; + Polyline temp_polyline = j[JSON_EXTRUSION_POLYLINE]; + extrusion_path.polyline = Polyline3(temp_polyline); extrusion_path.mm3_per_mm = j[JSON_EXTRUSION_MM3_PER_MM]; extrusion_path.width = j[JSON_EXTRUSION_WIDTH]; extrusion_path.height = j[JSON_EXTRUSION_HEIGHT]; @@ -4852,8 +4866,9 @@ ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const paths.reserve(it->second.size()); for (auto &polyline : it->second) { ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, layer_heights[index]); - path.polyline = polyline; - for (auto &p : path.polyline.points) p += trans; + path.polyline = Polyline3(polyline); + Point3 trans3(trans, 0); + for (auto &p : path.polyline.points) p += trans3; paths.push_back(path); } el.paths = std::move(paths); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index dafca39387..0871ac3c1d 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -92,7 +92,7 @@ enum PrintStep { enum PrintObjectStep { posSlice, posPerimeters,posEstimateCurledExtrusions, posPrepareInfill, - posInfill, posIroning, posSupportMaterial, posSimplifyPath, posSimplifySupportPath, + posInfill, posIroning, posContouring, posSupportMaterial, posSimplifyPath, posSimplifySupportPath, // BBS posDetectOverhangsForLift, posSimplifyWall, posSimplifyInfill, @@ -496,6 +496,7 @@ private: void prepare_infill(); void infill(); void ironing(); + void contour_z(); void generate_support_material(); void estimate_curled_extrusions(); void simplify_extrusion_path(); @@ -642,14 +643,14 @@ struct FakeWipeTower std::vector paths; for (float h = 0.f; h < height; h += layer_height) { ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, layer_height); - path.polyline = {minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner}; + path.polyline = Polyline3(Polyline{{minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner}}); paths.push_back({path}); if (h == 0.f) { // add brim ExtrusionPath fakeBrim(ExtrusionRole::erBrim, 0.0, 0.0, layer_height); Point wtbminCorner = {minCorner - Point{bd, bd}}; Point wtbmaxCorner = {maxCorner + Point{bd, bd}}; - fakeBrim.polyline = {wtbminCorner, {wtbmaxCorner.x(), wtbminCorner.y()}, wtbmaxCorner, {wtbminCorner.x(), wtbmaxCorner.y()}, wtbminCorner}; + fakeBrim.polyline = Polyline3(Polyline{{wtbminCorner, {wtbmaxCorner.x(), wtbminCorner.y()}, wtbmaxCorner, {wtbminCorner.x(), wtbmaxCorner.y()}, wtbminCorner}}); paths.back().push_back(fakeBrim); } } @@ -686,13 +687,13 @@ struct FakeWipeTower ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, lh); - path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner }; + path.polyline = Polyline3(Polyline{{ minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner }}); paths.push_back({ path }); // We added the border, now add several parallel lines so we can detect an object that is fully inside the tower. // For now, simply use fixed spacing of 3mm. for (coord_t y=minCorner.y()+scale_(3.); ymode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("ironing_expansion", coFloat); + def->label = L("Ironing expansion"); + def->category = L("Quality"); + def->tooltip = L("Expand or contract the ironing area."); + def->sidetext = L("mm"); + def->min = -100; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("zaa_region_disable", coBool); + def->label = L("Disable Z contouring for region"); + def->category = L("Quality"); + def->tooltip = L("Disable Z contouring for this specific region"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("zaa_enabled", coBool); + def->label = L("Z contouring enabled"); + def->category = L("Quality"); + def->tooltip = L("Enable Z-layer contouring (aka Z-layer anti-aliasing)"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("zaa_minimize_perimeter_height", coFloat); + def->label = L("Minimize wall height angle"); + def->category = L("Quality"); + def->tooltip = L("Reduce top surface perimeter heights to match height of edge for perimeters less than this angle. Set 0 to disable."); + def->sidetext = L("°"); + def->min = 0; + def->max = 90; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(35)); + + def = this->add("zaa_dont_alternate_fill_direction", coBool); + def->label = L("Don't alternate fill direction"); + def->category = L("Quality"); + def->tooltip = L("Disable alternating fill direction when using Z contouring"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("zaa_min_z", coFloat); + def->label = L("Minimum z height"); + def->category = L("Quality"); + def->tooltip = L("Minimum z layer height. Also controls slicing plane"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.05)); + def = this->add("layer_change_gcode", coString); def->label = L("Layer change G-code"); def->tooltip = L("This G-code is inserted at every layer change after the Z lift."); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index edc759d7d6..26cabab92b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1027,6 +1027,11 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, interlocking_depth)) ((ConfigOptionInt, interlocking_boundary_avoidance)) + // Z Anti-Aliasing (aka Z Contouring) + ((ConfigOptionBool, zaa_enabled)) + ((ConfigOptionBool, zaa_dont_alternate_fill_direction)) + ((ConfigOptionFloat, zaa_min_z)) + // Orca: internal use only ((ConfigOptionBool, calib_flowrate_topinfill_special_order)) // ORCA: special flag for flow rate calibration ) @@ -1101,6 +1106,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, ironing_speed)) ((ConfigOptionFloat, ironing_angle)) ((ConfigOptionBool, ironing_angle_fixed)) + ((ConfigOptionFloat, ironing_expansion)) // Filament Ironing ((ConfigOptionPercentsNullable, filament_ironing_flow)) ((ConfigOptionFloatsNullable, filament_ironing_spacing)) @@ -1189,6 +1195,10 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, scarf_joint_speed)) ((ConfigOptionFloat, scarf_joint_flow_ratio)) ((ConfigOptionPercent, scarf_overhang_threshold)) + + // Z Anti-Aliasing (aka Z Contouring) + ((ConfigOptionBool, zaa_region_disable)) + ((ConfigOptionFloat, zaa_minimize_perimeter_height)) ) PRINT_CONFIG_CLASS_DEFINE( diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index fc68fce76b..d9f41a69a8 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -8,6 +8,7 @@ #include "Layer.hpp" #include "MutablePolygon.hpp" #include "PrintConfig.hpp" +#include "SLA/IndexedMesh.hpp" #include "Support/SupportMaterial.hpp" #include "Support/SupportSpotsGenerator.hpp" #include "Support/TreeSupport.hpp" @@ -23,6 +24,10 @@ #include "AABBTreeLines.hpp" #include +#include +#include +#include +#include #include #include #include @@ -709,6 +714,46 @@ void PrintObject::ironing() } } +void PrintObject::contour_z() +{ + if (!this->set_started(posContouring)) { + return; + } + + m_print->set_status(40, L("Z contouring")); + BOOST_LOG_TRIVIAL(debug) << "Contouring in parallel - start"; + + TriangleMesh mesh = this->m_model_object->raw_mesh(); + if (m_model_object->instances.size() != 1) { + throw RuntimeError("ContourZ: unexpected number of instances"); + } + + m_model_object->instances.front()->transform_mesh(&mesh, true); + sla::IndexedMesh imesh(mesh); + + std::mutex mtx; + size_t completed = 0; + tbb::parallel_for( + // Contouring starting with layer second layer to avoid build plate collision + 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(); + m_layers[layer_idx]->make_contour_z(imesh); + + std::scoped_lock lock(mtx); + completed++; + std::string msg = (boost::format("Z contoured layer %d/%d (%d%%)") % (completed) % m_layers.size() % int(double(completed) / m_layers.size() * 100)).str(); + m_print->set_status(40, msg); + } + } + ); + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Contouring in parallel - end"; + + this->set_done(posContouring); +} + // BBS void PrintObject::clear_overhangs_for_lift() { @@ -1351,15 +1396,15 @@ bool PrintObject::invalidate_step(PrintObjectStep step) // propagate to dependent steps if (step == posPerimeters) { - invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSimplifyPath, posSimplifyInfill }); + invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posContouring, posSimplifyPath, posSimplifyInfill }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); } else if (step == posPrepareInfill) { - invalidated |= this->invalidate_steps({ posInfill, posIroning, posSimplifyPath, posSimplifyInfill }); + invalidated |= this->invalidate_steps({ posInfill, posIroning, posContouring, posSimplifyPath, posSimplifyInfill }); } else if (step == posInfill) { - invalidated |= this->invalidate_steps({ posIroning, posSimplifyInfill }); + invalidated |= this->invalidate_steps({ posIroning, posContouring, posSimplifyInfill }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); } else if (step == posSlice) { - invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial, posSimplifyPath, posSimplifyInfill }); + invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posContouring, posSupportMaterial, posSimplifyPath, posSimplifyInfill }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); m_slicing_params.valid = false; } else if (step == posSupportMaterial) { diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 532307414d..5d51cf3cf7 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -4,6 +4,7 @@ #include "ClipperUtils.hpp" #include "ElephantFootCompensation.hpp" +#include "Exception.hpp" #include "I18N.hpp" #include "Layer.hpp" #include "MultiMaterialSegmentation.hpp" @@ -34,6 +35,13 @@ LayerPtrs new_layers( coordf_t lo = object_layers[i_layer]; coordf_t hi = object_layers[i_layer + 1]; coordf_t slice_z = 0.5 * (lo + hi); + if (print_object->config().zaa_enabled) { + coordf_t z_offset = print_object->config().zaa_min_z; + slice_z = lo + z_offset; + if (slice_z < lo || slice_z > hi) { + throw RuntimeError("Bad min Z value"); + } + } Layer *layer = new Layer(id ++, print_object, hi - lo, hi + zmin, slice_z); out.emplace_back(layer); if (prev != nullptr) { diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 2b017b709b..c1325a4264 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1000,7 +1000,7 @@ std::vector> chain_segments_greedy2(SegmentEndPointFunc std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near) { - auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); }; + auto segment_end_point = [&entities](size_t idx, bool first_point) -> Point { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); }; auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); }; std::vector> out = chain_segments_greedy_constrained_reversals(segment_end_point, could_reverse, entities.size(), start_near); for (std::pair &segment : out) { @@ -1028,6 +1028,11 @@ void reorder_extrusion_entities(std::vector &entities, const s entities.swap(out); } +void chain_and_reorder_extrusion_entities(std::vector &entities, const Point &start_near) +{ + chain_and_reorder_extrusion_entities(entities, &start_near); +} + void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near) { // this function crashes if there are empty elements in entities @@ -1038,7 +1043,7 @@ void chain_and_reorder_extrusion_entities(std::vector &entitie std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near) { - auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); }; + auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> Point { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); }; return chain_segments_greedy(segment_end_point, extrusion_paths.size(), start_near); } diff --git a/src/libslic3r/ShortestPath.hpp b/src/libslic3r/ShortestPath.hpp index f8956afaa3..88389b20de 100644 --- a/src/libslic3r/ShortestPath.hpp +++ b/src/libslic3r/ShortestPath.hpp @@ -20,6 +20,7 @@ std::vector chain_expolygons(const ExPolygons &input_exploy); std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near = nullptr); void reorder_extrusion_entities(std::vector &entities, const std::vector> &chain); +void chain_and_reorder_extrusion_entities(std::vector &entities, const Point &start_near); void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near = nullptr); std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near = nullptr); diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 9159de617f..ff7cfdb5c8 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -1163,7 +1163,7 @@ static void modulate_extrusion_by_overlapping_layers( for (ExtrusionEntity *ee : extrusions_in_out) { ExtrusionPath *path = dynamic_cast(ee); assert(path != nullptr); - polylines.emplace_back(Polyline(std::move(path->polyline))); + polylines.emplace_back(path->polyline.to_polyline()); path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); delete path; } @@ -1288,9 +1288,10 @@ static void modulate_extrusion_by_overlapping_layers( if (! path->polyline.points.empty()) path->polyline.points.pop_back(); // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. - path->polyline.append(std::move(frag_polyline)); + path->polyline.append(Polyline3(std::move(frag_polyline))); frag_polyline.points.clear(); - pt_current = path->polyline.points.back(); + const Point3 &pt_back3 = path->polyline.points.back(); + pt_current = Point(pt_back3.x(), pt_back3.y()); if (pt_current == pt_end) { // End of the path. break; diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index d1fa5d8627..2392908a0b 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1181,7 +1181,9 @@ namespace SupportMaterialInternal { // This is a complete loop. // Add the outer contour first. Polygon poly; - poly.points = ep.polyline.points; + // Convert Points3 to Points + for (const Point3 &p3 : ep.polyline.points) + poly.points.emplace_back(p3.x(), p3.y()); poly.points.pop_back(); if (poly.area() < 0) poly.reverse(); diff --git a/src/libslic3r/VariableWidth.cpp b/src/libslic3r/VariableWidth.cpp index e01050e4a4..c00fc13bdf 100644 --- a/src/libslic3r/VariableWidth.cpp +++ b/src/libslic3r/VariableWidth.cpp @@ -16,11 +16,11 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyl if (line_len < SCALED_EPSILON) { // The line is so tiny that we don't care about its width when we connect it to another line. if (!path.empty()) - path.polyline.points.back() = line.b; // If the variable path is non-empty, connect this tiny line to it. + path.polyline.points.back() = Point3(line.b); // If the variable path is non-empty, connect this tiny line to it. else if (i + 1 < (int)lines.size()) // If there is at least one following line, connect this tiny line to it. lines[i + 1].a = line.a; else if (!multi_path.paths.empty()) - multi_path.paths.back().polyline.points.back() = line.b; // Connect this tiny line to the last finished path. + multi_path.paths.back().polyline.points.back() = Point3(line.b); // Connect this tiny line to the last finished path. // If any of the above isn't satisfied, then remove this tiny line. continue; @@ -65,8 +65,8 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyl const double w = fmax(line.a_width, line.b_width); const Flow new_flow = (role == erOverhangPerimeter && flow.bridge()) ? flow : flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI)); if (path.polyline.points.empty()) { - path.polyline.append(line.a); - path.polyline.append(line.b); + path.polyline.append(Point3(line.a)); + path.polyline.append(Point3(line.b)); // Convert from spacing to extrusion width based on the extrusion model // of a square extrusion ended with semi circles. #ifdef SLIC3R_DEBUG @@ -81,7 +81,7 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyl if (thickness_delta <= merge_tolerance) { // the width difference between this line and the current flow // (of the previous line) width is within the accepted tolerance - path.polyline.append(line.b); + path.polyline.append(Point3(line.b)); } else { // we need to initialize a new line multi_path.paths.emplace_back(std::move(path)); @@ -124,13 +124,13 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths_2(const ThickPolyline& t path = ExtrusionPath(role); double length = lines[start_index].length(); double sum = lines[start_index].length() * 0.5 * (lines[start_index].a_width + lines[start_index].b_width); - path.polyline.append(lines[start_index].a); + path.polyline.append(Point3(lines[start_index].a)); for (int idx = start_index + 1; idx < i; idx++) { length += lines[idx].length(); sum += lines[idx].length() * 0.5 * (lines[idx].a_width + lines[idx].b_width); - path.polyline.append(lines[idx].a); + path.polyline.append(Point3(lines[idx].a)); } - path.polyline.append(lines[i].a); + path.polyline.append(Point3(lines[i].a)); if (length > SCALED_EPSILON) { double w = sum / length; Flow new_flow = flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI)); @@ -193,13 +193,13 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths_2(const ThickPolyline& t path = ExtrusionPath(role); double length = lines[start_index].length(); double sum = lines[start_index].length() * lines[start_index].a_width; - path.polyline.append(lines[start_index].a); + path.polyline.append(Point3(lines[start_index].a)); for (int idx = start_index + 1; idx < final_size; idx++) { length += lines[idx].length(); sum += lines[idx].length() * lines[idx].a_width; - path.polyline.append(lines[idx].a); + path.polyline.append(Point3(lines[idx].a)); } - path.polyline.append(lines[final_size - 1].b); + path.polyline.append(Point3(lines[final_size - 1].b)); if (length > SCALED_EPSILON) { double w = sum / length; Flow new_flow = flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI)); diff --git a/src/libslic3r/libslic3r_version.h.in b/src/libslic3r/libslic3r_version.h.in index 750e092d28..df2a92f75b 100644 --- a/src/libslic3r/libslic3r_version.h.in +++ b/src/libslic3r/libslic3r_version.h.in @@ -5,6 +5,7 @@ #define SLIC3R_APP_KEY "@SLIC3R_APP_KEY@" #define SLIC3R_VERSION "@SLIC3R_VERSION@" #define SoftFever_VERSION "@SoftFever_VERSION@" +#define ZAA_VERSION "@ZAA_VERSION@" #ifndef GIT_COMMIT_HASH #define GIT_COMMIT_HASH "0000000" // 0000000 means uninitialized #endif diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 6cc33dc169..716056049d 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1991,7 +1991,7 @@ void _3DScene::thick_lines_to_verts( // Fill in the qverts and tverts with quads and triangles for the extrusion_path. void _3DScene::extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) { - Polyline polyline = extrusion_path.polyline; + Polyline polyline = extrusion_path.polyline.to_polyline(); polyline.remove_duplicate_points(); polyline.translate(copy); const Lines lines = polyline.lines(); @@ -2007,7 +2007,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, flo std::vector widths; std::vector heights; for (const ExtrusionPath& extrusion_path : extrusion_loop.paths) { - Polyline polyline = extrusion_path.polyline; + Polyline polyline = extrusion_path.polyline.to_polyline(); polyline.remove_duplicate_points(); polyline.translate(copy); const Lines lines_this = polyline.lines(); @@ -2025,7 +2025,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_mult std::vector widths; std::vector heights; for (const ExtrusionPath& extrusion_path : extrusion_multi_path.paths) { - Polyline polyline = extrusion_path.polyline; + Polyline polyline = extrusion_path.polyline.to_polyline(); polyline.remove_duplicate_points(); polyline.translate(copy); const Lines lines_this = polyline.lines(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 6c8dd5f421..cbf930155a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2567,13 +2567,32 @@ void GUI_App::init_single_instance_checker(const std::string &name, const std::s m_single_instance_checker = std::make_unique(boost::nowide::widen(name), boost::nowide::widen(path)); } +bool GUI_App::CallOnInit() +{ + // Override wxApp::CallOnInit to catch exceptions from ~wxMacAutoreleasePool + try { + return wxApp::CallOnInit(); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(fatal) << "Exception in CallOnInit: " << e.what(); + return false; + } catch (...) { + // The app was initialized, just the autorelease pool cleanup threw. + // Return true to let the app continue. + return m_initialized; + } +} + bool GUI_App::OnInit() { try { return on_init_inner(); } catch (const std::exception& e) { BOOST_LOG_TRIVIAL(fatal) << "OnInit Got Fatal error: " << e.what(); - generic_exception_handle(); + flush_logs(); + return false; + } catch (...) { + BOOST_LOG_TRIVIAL(fatal) << "OnInit caught non-std exception"; + flush_logs(); return false; } } @@ -3384,8 +3403,16 @@ bool GUI_App::on_init_network(bool try_backup) Slic3r::NetworkAgentFactory::register_all_agents(); // m_agent = new Slic3r::NetworkAgent(data_directory); - std::unique_ptr agent_ptr = Slic3r::create_agent_from_config(data_directory, app_config); - m_agent = agent_ptr.release(); + try { + std::unique_ptr agent_ptr = Slic3r::create_agent_from_config(data_directory, app_config); + m_agent = agent_ptr.release(); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to create network agent: " << e.what(); + m_agent = nullptr; + } catch (...) { + BOOST_LOG_TRIVIAL(error) << "Failed to create network agent: unknown exception (code signing?)"; + m_agent = nullptr; + } if (!m_device_manager) m_device_manager = new Slic3r::DeviceManager(m_agent); @@ -5683,6 +5710,7 @@ std::string GUI_App::format_display_version() if (!version_display.empty()) return version_display; version_display = SoftFever_VERSION; + version_display += " / ZAA v" + std::string(ZAA_VERSION); return version_display; } @@ -6514,7 +6542,8 @@ void GUI_App::update_mode() mainframe->m_param_dialog->panel()->update_mode(); if (mainframe->m_printer_view) mainframe->m_printer_view->update_mode(); - mainframe->m_webview->update_mode(); + if (mainframe->m_webview) + mainframe->m_webview->update_mode(); #ifdef _MSW_DARK_MODE if (!wxGetApp().tabs_as_menu()) @@ -6526,8 +6555,6 @@ void GUI_App::update_mode() for (auto tab : model_tabs_list) tab->update_mode(); - //BBS plater()->update_menus(); - plater()->canvas3D()->update_gizmos_on_off_state(); } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index c879b1e5bb..4ca02e5db7 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -324,6 +324,7 @@ public: void on_start_subscribe_again(std::string dev_id); std::string get_local_models_path(); bool OnInit() override; + bool CallOnInit() override; int OnExit() override; bool initialized() const { return m_initialized; } inline bool is_enable_multi_machine() { return this->app_config&& this->app_config->get("enable_multi_machine") == "true"; } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index acc53d35b8..5431836158 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -102,7 +102,9 @@ std::map> SettingsFactory::OBJECT_C std::map> SettingsFactory::PART_CATEGORY_SETTINGS= { - { L("Quality"), {{"ironing_type", "",8},{"ironing_flow", "",9},{"ironing_spacing", "",10},{"ironing_inset", "", 11},{"bridge_flow", "",11},{"make_overhang_printable", "",11},{"bridge_density", "", 1} + { L("Quality"), {{"ironing_type", "",8},{"ironing_flow", "",9},{"ironing_spacing", "",10},{"ironing_inset", "", 11},{"bridge_flow", "",11},{"make_overhang_printable", "",11},{"bridge_density", "", 1}, + {"ironing_expansion", "", 14}, + {"zaa_enabled", "", 1}, {"zaa_region_disable", "", 2}, {"zaa_minimize_perimeter_height", "", 3}, {"zaa_dont_alternate_fill_direction", "", 4}, {"zaa_min_z", "", 5} }}, { L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", L("Top Solid Layers"),1},{"top_shell_thickness", L("Top Minimum Shell Thickness"),1},{"top_surface_density", L("Top Surface Density"),1}, {"bottom_shell_layers", L("Bottom Solid Layers"),1}, {"bottom_shell_thickness", L("Bottom Minimum Shell Thickness"),1},{"bottom_surface_density", L("Bottom Surface Density"),1}, diff --git a/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp index a966a391f3..8c0e86f4ad 100644 --- a/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp +++ b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp @@ -303,7 +303,7 @@ static void convert_lines_to_vertices(const Slic3r::Lines& lines, const std::vec static void convert_to_vertices(const Slic3r::ExtrusionPath& extrusion_path, float print_z, size_t layer_id, size_t extruder_id, size_t color_id, EGCodeExtrusionRole extrusion_role, const Slic3r::Point& shift, std::vector& vertices) { - Slic3r::Polyline polyline = extrusion_path.polyline; + Slic3r::Polyline polyline = extrusion_path.polyline.to_polyline(); polyline.remove_duplicate_points(); polyline.translate(shift); const Slic3r::Lines lines = polyline.lines(); @@ -319,7 +319,7 @@ static void convert_to_vertices(const Slic3r::ExtrusionMultiPath& extrusion_mult std::vector widths; std::vector heights; for (const Slic3r::ExtrusionPath& extrusion_path : extrusion_multi_path.paths) { - Slic3r::Polyline polyline = extrusion_path.polyline; + Slic3r::Polyline polyline = extrusion_path.polyline.to_polyline(); polyline.remove_duplicate_points(); polyline.translate(shift); const Slic3r::Lines lines_this = polyline.lines(); @@ -337,7 +337,7 @@ static void convert_to_vertices(const Slic3r::ExtrusionLoop& extrusion_loop, flo std::vector widths; std::vector heights; for (const Slic3r::ExtrusionPath& extrusion_path : extrusion_loop.paths) { - Slic3r::Polyline polyline = extrusion_path.polyline; + Slic3r::Polyline polyline = extrusion_path.polyline.to_polyline(); polyline.remove_duplicate_points(); polyline.translate(shift); const Slic3r::Lines lines_this = polyline.lines(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 8e74eda926..f0fdd9af94 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1,5 +1,7 @@ #include "MainFrame.hpp" +#include +#include #include #include #include @@ -23,6 +25,7 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Utils.hpp" #include "Tab.hpp" #include "ProgressStatusBar.hpp" @@ -2451,6 +2454,17 @@ void MainFrame::init_menubar_as_editor() open_recent_project(file_id, filename); }, wxID_FILE1, wxID_FILE1 + 49); // [5050, 5100) + std::vector non_planar_projects; + for (auto &&entry : fs::directory_iterator(resources_dir() + "/nonplanar")) { + if (fs::is_regular_file(entry) && entry.path().extension() == ".3mf") { + non_planar_projects.push_back(entry.path().string()); + } + } + std::sort(non_planar_projects.begin(), non_planar_projects.end()); + for (auto &&path : non_planar_projects) { + m_recent_projects.AddFileToHistory(from_u8(path)); + } + std::vector recent_projects = wxGetApp().app_config->get_recent_projects(); std::reverse(recent_projects.begin(), recent_projects.end()); for (const std::string& project : recent_projects) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 5b4f9d3504..eecbe61452 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -621,7 +621,7 @@ void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost:: Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index /*= -1*/) { if (!m_config->has(opt_key)) { - std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.\n"; + // Option not in config — may be newly added (e.g. ZAA options) } std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7aa9a24cd3..f12eb94983 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3816,7 +3816,7 @@ static std::vector get_search_inputs(ConfigOptionMode mode) auto& tabs_list = wxGetApp().tabs_list; auto print_tech = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); for (auto tab : tabs_list) - if (tab->supports_printer_technology(print_tech)) + if (tab && tab->supports_printer_technology(print_tech) && tab->get_config()) ret.emplace_back(Search::InputInfo {tab->get_config(), tab->type(), mode}); return ret; @@ -3840,7 +3840,9 @@ void Sidebar::update_mode() //obj_list()->get_sizer()->Show(m_mode > comSimple); obj_list()->unselect_objects(); - obj_list()->update_selections(); + // Guard: during startup the 3D canvas selection may not be fully initialized + if (wxGetApp().initialized()) + obj_list()->update_selections(); // obj_list()->update_object_menu(); Layout(); @@ -10384,12 +10386,13 @@ void Plater::priv::set_project_name(const wxString& project_name) { BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << __LINE__ << " project is:" << project_name; m_project_name = project_name; + wxString name = project_name + " - OrcaSlicer-ZAA"; //update topbar title #ifdef __WINDOWS__ - wxGetApp().mainframe->SetTitle(m_project_name + " - OrcaSlicer"); - wxGetApp().mainframe->topbar()->SetTitle(m_project_name); + wxGetApp().mainframe->SetTitle(name); + wxGetApp().mainframe->topbar()->SetTitle(name); #else - wxGetApp().mainframe->SetTitle(m_project_name); + wxGetApp().mainframe->SetTitle(name); if (!m_project_name.IsEmpty()) wxGetApp().mainframe->update_title_colour_after_set_title(); #endif diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 1aef329297..db885c4310 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -70,6 +70,7 @@ static std::string get_key(const std::string &opt_key, Preset::Type type) { retu void OptionsSearcher::append_options(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode) { + if (!config) return; auto emplace = [this, type](const std::string key, const wxString &label) { const GroupAndCategory &gc = groups_and_categories[key]; if (gc.group.IsEmpty() || gc.category.IsEmpty()) return; @@ -89,13 +90,19 @@ void OptionsSearcher::append_options(DynamicPrintConfig *config, Preset::Type ty }; for (std::string opt_key : config->keys()) { - const ConfigOptionDef &opt = config->def()->options.at(opt_key); + auto def_it = config->def()->options.find(opt_key); + if (def_it == config->def()->options.end()) { + continue; + } + const ConfigOptionDef &opt = def_it->second; if (opt.mode > mode) continue; int cnt = 0; - if ((type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER) && opt_key != "printable_area") - switch (config->option(opt_key)->type()) { + if ((type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER || type == Preset::TYPE_PRINT) && opt_key != "printable_area") { + const ConfigOption *opt_ptr = config->option(opt_key); + if (!opt_ptr) continue; + switch (opt_ptr->type()) { case coInts: change_opt_key(opt_key, config, cnt); break; case coBools: change_opt_key(opt_key, config, cnt); break; case coFloats: change_opt_key(opt_key, config, cnt); break; @@ -106,6 +113,7 @@ void OptionsSearcher::append_options(DynamicPrintConfig *config, Preset::Type ty case coEnums: change_opt_key(opt_key, config, cnt); break; default: break; } + } wxString label = opt.full_label.empty() ? opt.label : opt.full_label; @@ -222,7 +230,7 @@ bool OptionsSearcher::search(const std::string &search, bool force /* = false*/, if (full_list) { std::string label = into_u8(get_label(opt)); //all - if (type == Preset::TYPE_INVALID) { + if (type == Preset::TYPE_INVALID) { found.emplace_back(FoundOption{label, label, into_u8(get_tooltip(opt)), i, 0}); } else if (type == opt.type){ found.emplace_back(FoundOption{label, label, into_u8(get_tooltip(opt)), i, 0}); @@ -289,7 +297,9 @@ OptionsSearcher::~OptionsSearcher() {} void OptionsSearcher::init(std::vector input_values) { options.clear(); - for (auto i : input_values) append_options(i.config, i.type, i.mode); + for (size_t idx = 0; idx < input_values.size(); ++idx) { + append_options(input_values[idx].config, input_values[idx].type, input_values[idx].mode); + } sort_options(); search(search_line, true, search_type); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 566aaa93cd..fbb134fd09 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2322,6 +2322,14 @@ void TabPrint::build() optgroup->append_single_option_line("ironing_angle", "quality_settings_ironing#angle-offset"); optgroup->append_single_option_line("ironing_angle_fixed", "quality_settings_ironing#fixed-angle"); + optgroup = page->new_optgroup("Z Contouring", L"param_advanced"); + optgroup->append_single_option_line("zaa_enabled"); + optgroup->append_single_option_line("zaa_region_disable"); + optgroup->append_single_option_line("zaa_minimize_perimeter_height"); + optgroup->append_single_option_line("zaa_dont_alternate_fill_direction"); + optgroup->append_single_option_line("zaa_min_z"); + optgroup->append_single_option_line("ironing_expansion"); + optgroup = page->new_optgroup(L("Wall generator"), L"param_wall_generator"); optgroup->append_single_option_line("wall_generator", "quality_settings_wall_generator"); optgroup->append_single_option_line("wall_transition_angle", "quality_settings_wall_generator#arachne"); diff --git a/version.inc b/version.inc index d6c6b293b3..86d3759e73 100644 --- a/version.inc +++ b/version.inc @@ -8,6 +8,7 @@ if(NOT DEFINED BBL_INTERNAL_TESTING) set(BBL_INTERNAL_TESTING "0") endif() set(SoftFever_VERSION "2.3.2-dev") +set(ZAA_VERSION "1.0.3") string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" SoftFever_VERSION_MATCH ${SoftFever_VERSION}) set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})