feat: Add Z Anti-Aliasing (ZAA) contouring support (updated) (#12736)

This PR supersedes #12225, which originally proposed this feature but
appears inactive.

The feature originated from work I developed earlier in
[BambuStudio-ZAA](https://github.com/adob/BambuStudio-ZAA), a private
fork of Bambu Studio

Compared to #12225, I updated the implementation for current upstream
and fixed the following issues:
 - fixed broken tests
 - removed references to nonplanar directory

Reviewers may want to compare against #12225 for earlier
discussion/context.

## Summary

Port of **Z Anti-Aliasing (ZAA)** from
[BambuStudio-ZAA](https://github.com/adob/BambuStudio-ZAA) to
OrcaSlicer.

ZAA eliminates visible stair-stepping on curved and sloped top surfaces
by raycasting each extrusion point against the original 3D mesh and
micro-adjusting its Z height to follow the actual surface geometry. The
result is visibly smoother domes, chamfers, and shallow slopes — without
post-processing.

## How It Works

1. The slicer runs normally, then a **posContouring** step processes
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 with per-point Z coordinates
4. The G-code writer emits the adjusted Z values, so the printer follows
the true surface

## Configuration

Five new 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; controls
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 |
| `zaa_region_disable` | bool | off | Disable ZAA for a specific print
region/material |

## Key Changes

- **Core algorithm**: New `src/libslic3r/ContourZ.cpp` (~330 lines) —
raycasting engine
- **3D geometry**: `Point3`, `Line3`, `Polyline3`, `MultiPoint3` extend
existing 2D types
- **Arc fitting**: Templated to work with both 2D and 3D geometry
- **Pipeline**: `ExtrusionPath::polyline` changed from `Polyline` to
`Polyline3`; new `posContouring` step in `PrintObject.cpp`
- **G-code**: `GCode.cpp` writes per-point Z when `path.z_contoured` is
set
- **UI**: ZAA settings exposed in Print Settings > Quality panel
- **Documentation**: `docs/ZAA.md` with usage and implementation details

57 files changed, ~1800 insertions, ~200 deletions.

## Test Plan

- [ ] Load a model with curved top surfaces (sphere, dome, chamfered
box)
- [ ] Enable **Z contouring** in Print Settings > Quality
- [ ] Slice and verify G-code has varying Z values within contoured
layers
- [ ] Build on macOS (verified), test on Linux and Windows
This commit is contained in:
SoftFever
2026-05-02 11:03:00 +08:00
committed by GitHub
52 changed files with 2337 additions and 391 deletions

View File

@@ -161,10 +161,11 @@ namespace AABBTreeLines {
// on centroids of the lines.
// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies
// during tree traversal.
template <typename LineType>
inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(const std::vector<LineType>& lines)
template<typename LineType>
inline AABBTreeIndirect::Tree<LineType::Dim, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(
const std::vector<LineType>& lines)
{
using TreeType = AABBTreeIndirect::Tree<2, typename LineType::Scalar>;
using TreeType = AABBTreeIndirect::Tree<LineType::Dim, typename LineType::Scalar>;
// using CoordType = typename TreeType::CoordType;
using VectorType = typename TreeType::VectorType;
using BoundingBox = typename TreeType::BoundingBox;
@@ -303,7 +304,7 @@ namespace AABBTreeLines {
private:
std::vector<LineType> lines;
AABBTreeIndirect::Tree<2, Scalar> tree;
AABBTreeIndirect::Tree<LineType::Dim, Scalar> tree;
public:
explicit LinesDistancer(const std::vector<LineType>& lines)
@@ -321,15 +322,15 @@ namespace AABBTreeLines {
LinesDistancer() = default;
// 1 true, -1 false, 0 cannot determine
int outside(const Vec<2, Scalar>& point) const { return point_outside_closed_contours(lines, tree, point); }
int outside(const Vec<LineType::Dim, Scalar>& point) const { return point_outside_closed_contours(lines, tree, point); }
// negative sign means inside
template <bool SIGNED_DISTANCE>
std::tuple<Floating, size_t, Vec<2, Floating>> distance_from_lines_extra(const Vec<2, Scalar>& point) const
template<bool SIGNED_DISTANCE>
std::tuple<Floating, size_t, Vec<LineType::Dim, Floating>> distance_from_lines_extra(const Vec<LineType::Dim, Scalar>& point) const
{
size_t nearest_line_index_out = size_t(-1);
Vec<2, Floating> nearest_point_out = Vec<2, Floating>::Zero();
Vec<2, Floating> p = point.template cast<Floating>();
Vec<LineType::Dim, Floating> nearest_point_out = Vec<LineType::Dim, Floating>::Zero();
Vec<LineType::Dim, Floating> p = point.template cast<Floating>();
auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, nearest_line_index_out, nearest_point_out);
if (distance < 0) {
@@ -344,22 +345,20 @@ namespace AABBTreeLines {
return { distance, nearest_line_index_out, nearest_point_out };
}
template <bool SIGNED_DISTANCE>
Floating distance_from_lines(const Vec<2, typename LineType::Scalar>& point) const
template<bool SIGNED_DISTANCE> Floating distance_from_lines(const Vec<LineType::Dim, typename LineType::Scalar>& point) const
{
auto [dist, idx, np] = distance_from_lines_extra<SIGNED_DISTANCE>(point);
return dist;
}
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius)
{
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
}
template <bool sorted>
std::vector<std::pair<Vec<2, Scalar>, size_t>> intersections_with_line(const LineType& line) const
std::vector<size_t> all_lines_in_radius(const Vec<LineType::Dim, Scalar>& point, Floating radius)
{
return get_intersections_with_line<sorted, Vec<2, Scalar>>(lines, tree, line);
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
}
template<bool sorted> std::vector<std::pair<Vec<LineType::Dim, Scalar>, size_t>> intersections_with_line(const LineType& line) const
{
return get_intersections_with_line<sorted, Vec<LineType::Dim, Scalar>>(lines, tree, line);
}
const LineType& get_line(size_t line_idx) const { return lines[line_idx]; }

View File

@@ -236,7 +236,18 @@ class BoundingBox3 : public BoundingBox3Base<Vec3crd>
public:
BoundingBox3() : BoundingBox3Base<Vec3crd>() {}
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {}
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {}
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>() {
if (!points.empty()) {
this->min = points.front();
this->max = points.front();
for (const auto &p : points) {
this->min = this->min.cwiseMin(static_cast<const Vec3crd&>(p));
this->max = this->max.cwiseMax(static_cast<const Vec3crd&>(p));
}
this->defined = true;
}
}
BoundingBox3(const std::vector<Vec3crd>& points) : BoundingBox3Base<Vec3crd>(points) {}
};
class BoundingBoxf : public BoundingBoxBase<Vec2d>

View File

@@ -100,6 +100,7 @@ set(lisbslic3r_sources
CommonDefs.hpp
Config.cpp
Config.hpp
ContourZ.cpp
CustomGCode.cpp
CustomGCode.hpp
CutUtils.cpp

View File

@@ -3,6 +3,7 @@
#include <cmath>
#include <cassert>
#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)
{

View File

@@ -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.

View File

@@ -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<ClipperLib::Paths>(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<ClipperLib::Paths>(
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<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit, end_type))); }

View File

@@ -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)
{

236
src/libslic3r/ContourZ.cpp Normal file
View File

@@ -0,0 +1,236 @@
#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 <cfloat>
#include <cmath>
#include <initializer_list>
#include <string>
namespace Slic3r {
static void contour_extrusion_entity(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntity *extr);
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;
}
static bool contour_extrusion_path(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionPath &path)
{
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 = region->region().config().zaa_min_z;
const Points3 &points = path.polyline.points;
double resolution_mm = 0.1;
coordf_t height = layer->height;
double minimize_perimeter_height_angle = region->region().config().zaa_minimize_perimeter_height;
Pointf3s contoured_points;
bool was_contoured = false;
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();
if (num_segments == 0) {
continue;
}
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);
}
if (is_perimeter(path.role())) {
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) {
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 < -height || d > max_up + 0.03) {
// this point is too far from the mesh edge, probably because this is not a top surface. Do not contour it.
d = 0;
}
if (d < min_down) {
d = min_down;
} else if (d > max_up) {
d = max_up;
}
if (is_perimeter(path.role()) && d > 0) {
// do not increase height of 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(new_point, contoured_points[contoured_points.size() - 2],
contoured_points[contoured_points.size() - 1]);
if (dist < EPSILON * 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<const ExtrusionPathSloped*>(extr);
if (sloped != nullptr) {
throw RuntimeError("ExtrusionPathSloped not implemented");
return;
}
ExtrusionMultiPath *multipath = dynamic_cast<ExtrusionMultiPath*>(extr);
if (multipath != nullptr) {
contour_extrusion_multipath(region, mesh, *multipath);
return;
}
ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(extr);
if (path != nullptr) {
contour_extrusion_path(region, mesh, *path);
return;
}
ExtrusionLoop *loop = dynamic_cast<ExtrusionLoop*>(extr);
if (loop != nullptr) {
contour_extrusion_loop(region, mesh, *loop);
return;
}
const ExtrusionLoopSloped *loop_sloped = dynamic_cast<const ExtrusionLoopSloped*>(extr);
if (loop_sloped != nullptr) {
throw RuntimeError("ExtrusionLoopSloped not implemented");
return;
}
ExtrusionEntityCollection *collection = dynamic_cast<ExtrusionEntityCollection*>(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<ExtrusionRole> roles) {
for (ExtrusionEntity* extr : collection.entities) {
if (!contains(roles, extr->role())) {
continue;
}
contour_extrusion_entity(region, mesh, extr);
}
}
void Layer::make_contour_z(const sla::IndexedMesh &mesh)
{
for (LayerRegion *region : this->regions()) {
if (!region->region().config().zaa_enabled)
continue;
handle_extrusion_collection(region, mesh, region->fills, {erTopSolidInfill, erIroning, erPerimeter, erExternalPerimeter, erMixed});
handle_extrusion_collection(region, mesh, region->perimeters, {erPerimeter, erExternalPerimeter, erMixed});
}
}
} // namespace Slic3r

View File

@@ -192,6 +192,25 @@ inline Linesf to_unscaled_linesf(const ExPolygons &src)
return lines;
}
inline Linesf3 to_unscaled_linesf3(const ExPolygons& src)
{
Linesf3 lines;
lines.reserve(count_points(src));
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++it_expoly) {
for (size_t i = 0; i <= it_expoly->holes.size(); ++i) {
const Points& points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points;
Vec2d unscaled_a = unscaled(points.front());
Vec2d unscaled_b = unscaled_a;
for (Points::const_iterator it = points.begin() + 1; it != points.end(); ++it) {
unscaled_b = unscaled(*(it));
lines.push_back(Linef3(unscaled_a, unscaled_b, 0));
unscaled_a = unscaled_b;
}
lines.push_back(Linef3(unscaled_a, unscaled(points.front()), 0));
}
}
return lines;
}
inline Points to_points(const ExPolygons &src)
{

View File

@@ -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<double>::max();
for (const ExtrusionPath &path : this->paths) {
std::pair<int, Point> foot_pt_ = foot_pt(path.polyline.points, point);
double d2 = (foot_pt_.second - point).cast<double>().squaredNorm();
std::pair<int, Point3> 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<double>().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<double>().squaredNorm();
double d2_2 = (point - *p2).cast<double>().squaredNorm();
Point p1_2d = Point(p1->x(), p1->y());
Point p2_2d = Point(p2->x(), p2->y());
double d2_1 = (point - p1_2d).cast<double>().squaredNorm();
double d2_2 = (point - p2_2d).cast<double>().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;
}
}
@@ -266,8 +286,9 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang, const
const ExtrusionPath &path = this->paths[path_idx];
ExtrusionPath p1(path.role(), path.mm3_per_mm, path.width, path.height);
ExtrusionPath p2(path.role(), path.mm3_per_mm, path.width, path.height);
p1.z_contoured = p2.z_contoured = path.z_contoured;
path.polyline.split_at(p, &p1.polyline, &p2.polyline);
if (this->paths.size() == 1) {
if (!p1.polyline.is_valid()) {
std::swap(this->paths.front().polyline.points, p2.polyline.points);
@@ -411,16 +432,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<void(const Line &)> handle_line = [slope_max_segment_length, &detailed_poly, &handle_line](const Line &line) {
const std::function<void(const Line3 &)> 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 +462,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 +485,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 +652,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());
}
}

View File

@@ -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<ExtrusionEntity*> 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<double>(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<double> z_diffs;
ExtrusionPathContoured(Polyline3 &&polyline, const ExtrusionPath &rhs, std::vector<double> &&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<double>(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);
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -1218,6 +1218,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id();
{
const auto &rcfg = m_regions[surface_fill.region_id]->region().config();
f->dont_alternate_fill_direction = rcfg.zaa_enabled && rcfg.zaa_dont_alternate_fill_direction;
}
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle;
@@ -1418,6 +1422,10 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(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.
{
const auto &rcfg = m_regions[surface_fill.region_id]->region().config();
f->dont_alternate_fill_direction = rcfg.zaa_enabled && rcfg.zaa_dont_alternate_fill_direction;
}
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle;
@@ -1595,6 +1603,7 @@ void Layer::make_ironing()
for (size_t i = 0; i < by_extruder.size();) {
// Find span of regions equivalent to the ironing operation.
IroningParams &ironing_params = by_extruder[i];
f->dont_alternate_fill_direction = ironing_params.layerm->region().config().zaa_enabled && ironing_params.layerm->region().config().zaa_dont_alternate_fill_direction;
// Create the filler object.
if( f_pattern != ironing_params.pattern )
{

View File

@@ -308,7 +308,9 @@ std::pair<float, Point> 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");
}

View File

@@ -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;

View File

@@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "Config.hpp"
#include "GCodeWriter.hpp"
#include "Polygon.hpp"
#include "PrintConfig.hpp"
#include "libslic3r.h"
@@ -22,10 +23,12 @@
#include "Time.hpp"
#include "GCode/ExtrusionProcessor.hpp"
#include <algorithm>
#include <cfloat>
#include <cmath>
#include <cstdlib>
#include <chrono>
#include <iostream>
#include <iterator>
#include <math.h>
#include <stdlib.h>
#include <string>
@@ -5521,12 +5524,12 @@ void GCode::set_extruders(const std::vector<unsigned int> &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;
}
@@ -5603,11 +5606,15 @@ static std::unique_ptr<EdgeGrid::Grid> 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,
const 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;
@@ -5682,13 +5689,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
@@ -5735,7 +5743,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);
@@ -5771,7 +5779,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
if (!enable_seam_slope) {
for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
gcode += this->_extrude(*path, description, speed_for_path(*path));
// Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed
// Orca: Adaptive PA - dont adapt PA after the first multipath extrusion is completed
// as we have already set the PA value to the average flow over the totality of the path
// in the first extrude move
// TODO: testing is needed with slope seams and adaptive PA.
@@ -5829,10 +5837,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
}
}
@@ -5842,8 +5852,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;
@@ -5857,8 +5869,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<double>();
Vec2d p2 = paths.front().polyline.points[1].cast<double>();
Vec2d p1 = paths.front().polyline.points.front().cast<double>().head<2>();
Vec2d p2 = paths.front().polyline.points[1].cast<double>().head<2>();
Vec2d v = p2 - p1;
double nd = scale_(EXTRUDER_CONFIG(nozzle_diameter));
double l2 = v.squaredNorm();
@@ -5870,19 +5882,20 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
if (nd * nd < l2)
pt = (p1 + threshold * v * (nd / sqrt(l2))).cast<coord_t>();
//Point pt = ((nd * nd >= l2) ? (p1+v*0.4): (p1 + 0.2 * v * (nd / sqrt(l2)))).cast<coord_t>();
pt.rotate(angle, paths.front().polyline.points.front());
const Point3 &center3 = 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);
gcode += m_writer.extrude_to_xy(this->point_to_gcode(pt), 0, "move inwards before travel", true);
}
return gcode;
}
std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed)
std::string GCode::extrude_multi_path(const ExtrusionMultiPath& multipath, const 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
@@ -5899,8 +5912,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
@@ -5911,13 +5924,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();
}
@@ -5925,7 +5940,10 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string
return gcode;
}
std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters)
std::string GCode::extrude_entity(const ExtrusionEntity& entity,
const std::string& description,
double speed,
const ExtrusionEntitiesPtr& region_perimeters)
{
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(&entity))
return this->extrude_path(*path, description, speed);
@@ -5938,7 +5956,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, const 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;
@@ -5946,17 +5964,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<double>().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<double>().norm();
double dist = (path.polyline.points[i] - path.last_point3()).cast<double>().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();
@@ -5999,11 +6017,11 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
extrusions.emplace_back(ee);
if (! extrusions.empty()) {
m_config.apply(print.get_print_region(&region - &by_region.front()).config());
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
for (const ExtrusionEntity *fill : extrusions) {
auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(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);
@@ -6034,7 +6052,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");
@@ -6200,14 +6218,20 @@ 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) {
Point first_point = path.first_point();
if (!m_last_pos_defined || m_last_pos.to_point() != first_point || m_need_change_layer_lift_z || slope_need_z_travel) {
const bool _last_pos_undefined = !m_last_pos_defined;
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)
);
double z = DBL_MAX;
if (sloped != nullptr) {
z = get_sloped_z(sloped->slope_begin.z_ratio);
} else if (path.z_contoured && !path.polyline.lines().empty()) {
z = unscale_(path.polyline.lines().begin()->a.z()) + m_nominal_z;
}
gcode += this->travel_to(first_point, path.role(), "move to first " + description + " point", z);
m_need_change_layer_lift_z = false;
// Orca: ensure Z matches planned layer height
if (!slope_need_z_travel && (_last_pos_undefined || m_need_change_layer_lift_z)) {
const std::string z_sync_comment = _last_pos_undefined ?
@@ -6217,6 +6241,19 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
m_need_change_layer_lift_z = false;
}
if (path.z_contoured && !path.polyline.lines().empty()) {
double current_z = m_writer.get_position().z();
double first_z = unscale_(path.polyline.lines().begin()->a.z()) + m_nominal_z;
if (GCodeFormatter::quantize_xyzf(first_z) != GCodeFormatter::quantize_xyzf(current_z)) {
gcode += m_writer.travel_to_z(first_z, "set Z for contouring", true);
}
}
if (!path.z_contoured && sloped == nullptr) {
double current_z = m_writer.get_position().z();
if (GCodeFormatter::quantize_xyzf(current_z) != GCodeFormatter::quantize_xyzf(m_nominal_z)) {
gcode += this->writer().travel_to_z(m_nominal_z, "reset Z after contouring", true);
}
}
// if needed, write the gcode_label_objects_end then gcode_label_objects_start
// should be already done by travel_to, but just in case
@@ -6803,10 +6840,12 @@ 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()) {
double saved_z = m_writer.get_position().z();
for (const Line3& line : path.polyline.lines()) {
std::string tempDescription = description;
const double line_length = line.length() * SCALING_FACTOR;
if (line_length < EPSILON)
@@ -6821,16 +6860,35 @@ 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,
GCodeWriter::full_gcode_comment ? tempDescription : "");
} 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,
@@ -6849,7 +6907,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;
@@ -6907,14 +6965,14 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
double total_length = 0;
if (sloped != nullptr) {
// Calculate total extrusion length
Points p;
Points3 p;
p.reserve(new_points.size());
std::transform(new_points.begin(), new_points.end(), std::back_inserter(p), [](const ProcessedPoint& pp) { return pp.p; });
Polyline l(p);
Polyline3 l(p);
total_length = l.length() * SCALING_FACTOR;
}
gcode += m_writer.set_speed(last_set_speed, "", comment);
Vec2d prev = this->point_to_gcode_quantized(new_points[0].p);
Vec3d prev = this->point_to_gcode_quantized(new_points[0].p);
bool pre_fan_enabled = false;
bool cur_fan_enabled = false;
if( m_enable_cooling_markers && enable_overhang_bridge_fan)
@@ -6928,7 +6986,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
std::string tempDescription = description;
const ProcessedPoint &processed_point = new_points[i];
const ProcessedPoint &pre_processed_point = new_points[i-1];
Vec2d p = this->point_to_gcode_quantized(processed_point.p);
Vec3d p = this->point_to_gcode_quantized(processed_point.p);
if (m_enable_cooling_markers) {
if (enable_overhang_bridge_fan) {
cur_fan_enabled = check_overhang_fan(processed_point.overlap, path.role());
@@ -7010,9 +7068,26 @@ 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) {
Vec2d dest2d = p.head<2>();
coordf_t z_diff = unscale_(processed_point.p.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,
GCodeWriter::full_gcode_comment ? tempDescription : "");
} else if (sloped == nullptr) {
// Normal extrusion
gcode += m_writer.extrude_to_xy(p, dE, GCodeWriter::full_gcode_comment ? tempDescription : "");
gcode += m_writer.extrude_to_xy(p.head<2>(), dE, GCodeWriter::full_gcode_comment ? tempDescription : "");
} else {
// Sloped extrusion
const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length);
@@ -7893,6 +7968,13 @@ Vec2d GCode::point_to_gcode(const Point &point) const
return unscale(point) + m_origin - extruder_offset;
}
Vec3d GCode::point_to_gcode(const Point3& point) const
{
Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset);
Vec2d xy = unscale(point.to_point()) + m_origin - extruder_offset;
return Vec3d(xy.x(), xy.y(), unscale_(point.z()));
}
// convert a model-space scaled point into G-code coordinates
Point GCode::gcode_to_point(const Vec2d &point) const
{
@@ -7910,6 +7992,11 @@ Vec2d GCode::point_to_gcode_quantized(const Point& point) const
return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) };
}
Vec3d GCode::point_to_gcode_quantized(const Point3& point) const
{
Vec3d p = this->point_to_gcode(point);
return {GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()), GCodeFormatter::quantize_xyzf(p.z())};
}
// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed
// during infill/perimeter wiping, or normally (depends on wiping_entities parameter)

View File

@@ -221,10 +221,12 @@ 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;
Vec3d point_to_gcode(const Point3& point) const;
Point gcode_to_point(const Vec2d &point) const;
Vec2d point_to_gcode_quantized(const Point& point) const;
Vec3d point_to_gcode_quantized(const Point3& point) const;
const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; }
GCodeWriter& writer() { return m_writer; }
@@ -383,7 +385,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<unsigned int> &extruder_ids);
std::string preamble();
@@ -391,13 +394,20 @@ private:
std::string change_layer(coordf_t print_z);
// 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_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1., const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr());
std::string extrude_entity(const ExtrusionEntity& entity,
const 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,
const std::string& description,
double speed = -1.,
const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr(),
const Point* start_point = nullptr);
std::string extrude_multi_path(const ExtrusionMultiPath& multipath, const std::string& description = "", double speed = -1.);
std::string extrude_path(const ExtrusionPath& path, const std::string& description = "", double speed = -1.);
// Orca: Adaptive PA variables
// Used for adaptive PA when extruding paths with multiple, varying flow segments.
// This contains the sum of the mm3_per_mm values weighted by the length of each path segment.
@@ -584,7 +594,7 @@ private:
std::map<std::string, std::vector<std::string>> m_placeholder_error_messages;
#endif
Point m_last_pos;
Point3 m_last_pos;
bool m_last_pos_defined;
std::unique_ptr<CoolingBuffer> m_cooling_buffer;

View File

@@ -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()); }

View File

@@ -27,19 +27,19 @@
namespace Slic3r {
struct ExtendedPoint
template<int Dim> struct ExtendedPoint
{
Vec2d position;
Eigen::Matrix<double, Dim, 1, Eigen::DontAlign> position;
float distance;
float curvature;
};
template<bool SCALED_INPUT, bool ADD_INTERSECTIONS, bool PREV_LAYER_BOUNDARY_OFFSET, bool SIGNED_DISTANCE, typename POINTS, typename L>
std::vector<ExtendedPoint> estimate_points_properties(const POINTS &input_points,
const AABBTreeLines::LinesDistancer<L> &unscaled_prev_layer,
float flow_width,
float max_line_length = -1.0f,
float min_distance = -1.0f)
std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS& input_points,
const AABBTreeLines::LinesDistancer<L>& unscaled_prev_layer,
float flow_width,
float max_line_length = -1.0f,
float min_distance = -1.0f)
{
bool looped = input_points.front() == input_points.back();
std::function<size_t(size_t,size_t)> get_prev_index = [](size_t idx, size_t count) {
@@ -78,35 +78,36 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
double min_spacing = flow_width*0.25;
using AABBScalar = typename AABBTreeLines::LinesDistancer<L>::Scalar;
using Vec = Eigen::Matrix<double, L::Dim, 1, Eigen::DontAlign>;
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<double>(); };
auto maybe_unscale = [](const P& p) -> Vec { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
std::vector<ExtendedPoint> points;
std::vector<ExtendedPoint<L::Dim>> points;
points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1));
{
ExtendedPoint start_point{maybe_unscale(input_points.front())};
auto [distance, nearest_line,
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
ExtendedPoint<L::Dim> start_point{maybe_unscale(input_points.front())};
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
start_point.position.template cast<AABBScalar>());
start_point.distance = distance + boundary_offset;
points.push_back(start_point);
}
for (size_t i = 1; i < input_points.size(); i++) {
ExtendedPoint next_point{maybe_unscale(input_points[i])};
ExtendedPoint<L::Dim> next_point{maybe_unscale(input_points[i])};
auto [distance, nearest_line,
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.template cast<AABBScalar>());
next_point.distance = distance + boundary_offset;
// Intersection handling
if (ADD_INTERSECTIONS &&
((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) {
const ExtendedPoint &prev_point = points.back();
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(
L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
const ExtendedPoint<L::Dim>& prev_point = points.back();
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(
L{prev_point.position.template cast<AABBScalar>(), next_point.position.template cast<AABBScalar>()});
for (const auto &intersection : intersections) {
ExtendedPoint p{};
ExtendedPoint<L::Dim> p{};
p.position = intersection.first.template cast<double>();
p.distance = boundary_offset;
// ORCA: Filter out points that are introduced at intersections if their distance from the previous or next point is not meaningful
@@ -121,12 +122,12 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
// Segmentation handling
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
std::vector<ExtendedPoint> new_points;
std::vector<ExtendedPoint<L::Dim>> new_points;
new_points.reserve(points.size() * 2);
new_points.push_back(points.front());
for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) {
const ExtendedPoint &curr = points[point_idx];
const ExtendedPoint &next = points[point_idx + 1];
const ExtendedPoint<L::Dim>& curr = points[point_idx];
const ExtendedPoint<L::Dim>& next = points[point_idx + 1];
if ((curr.distance > -boundary_offset && curr.distance < boundary_offset + 2.0f) ||
(next.distance > -boundary_offset && next.distance < boundary_offset + 2.0f)) {
@@ -144,10 +145,10 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
double t1 = std::max(a0, a1);
if (t0 < 1.0) {
Vec2d p0 = curr.position + t0 * (next.position - curr.position);
auto [p0_dist, p0_near_l,
p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
ExtendedPoint new_p{};
Vec p0 = curr.position + t0 * (next.position - curr.position);
auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
p0.template cast<AABBScalar>());
ExtendedPoint<L::Dim> new_p{};
new_p.position = p0;
new_p.distance = float(p0_dist + boundary_offset);
// ORCA: only create a new point in the path if the new point overhang distance will be used to generate a speed change
@@ -161,10 +162,10 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
}
}
if (t1 > 0.0) {
Vec2d p1 = curr.position + t1 * (next.position - curr.position);
auto [p1_dist, p1_near_l,
p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
ExtendedPoint new_p{};
Vec p1 = curr.position + t1 * (next.position - curr.position);
auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
p1.template cast<AABBScalar>());
ExtendedPoint<L::Dim> new_p{};
new_p.position = p1;
new_p.distance = float(p1_dist + boundary_offset);
// ORCA: only create a new point in the path if the new point overhang distance will be used to generate a speed change
@@ -186,21 +187,21 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
// Maximum line length handling
if (max_line_length > 0) {
std::vector<ExtendedPoint> new_points;
std::vector<ExtendedPoint<L::Dim>> new_points;
new_points.reserve(points.size() * 2);
{
for (size_t i = 0; i + 1 < points.size(); i++) {
const ExtendedPoint &curr = points[i];
const ExtendedPoint &next = points[i + 1];
const ExtendedPoint<L::Dim>& curr = points[i];
const ExtendedPoint<L::Dim>& next = points[i + 1];
new_points.push_back(curr);
double len = (next.position - curr.position).squaredNorm();
double t = sqrt((max_line_length * max_line_length) / len);
size_t new_point_count = 1.0 / t;
for (size_t j = 1; j < new_point_count + 1; j++) {
Vec2d pos = curr.position * (1.0 - j * t) + next.position * (j * t);
Vec pos = curr.position * (1.0 - j * t) + next.position * (j * t);
auto [p_dist, p_near_l,
p_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(pos.cast<AABBScalar>());
ExtendedPoint new_p{};
p_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(pos.template cast<AABBScalar>());
ExtendedPoint<L::Dim> new_p{};
new_p.position = pos;
new_p.distance = float(p_dist + boundary_offset);
@@ -219,8 +220,8 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
float accumulated_distance = 0;
std::vector<float> distances_for_curvature(points.size());
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
const ExtendedPoint &a = points[point_idx];
const ExtendedPoint &b = points[get_prev_index(point_idx, points.size())];
const ExtendedPoint<L::Dim>& a = points[point_idx];
const ExtendedPoint<L::Dim>& b = points[get_prev_index(point_idx, points.size())];
distances_for_curvature[point_idx] = (b.position - a.position).norm();
accumulated_distance += distances_for_curvature[point_idx];
@@ -229,9 +230,9 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
if (accumulated_distance > EPSILON)
for (float window_size : {3.0f, 9.0f, 16.0f}) {
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
ExtendedPoint &current = points[point_idx];
ExtendedPoint<L::Dim>& current = points[point_idx];
Vec2d back_position = current.position;
Vec back_position = current.position;
{
size_t back_point_index = point_idx;
float dist_backwards = 0;
@@ -251,7 +252,7 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
}
}
Vec2d front_position = current.position;
Vec front_position = current.position;
{
size_t front_point_index = point_idx;
float dist_forwards = 0;
@@ -271,7 +272,9 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
}
}
float new_curvature = angle(current.position - back_position, front_position - current.position) / window_size;
float new_curvature = angle((current.position - back_position).template head<2>(),
(front_position - current.position).template head<2>()) /
window_size;
if (abs(current.curvature) < abs(new_curvature)) {
current.curvature = new_curvature;
}
@@ -283,15 +286,15 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
struct ProcessedPoint
{
Point p;
Point3 p;
float speed = 1.0f;
float overlap = 1.0f;
};
class ExtrusionQualityEstimator
{
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> prev_layer_boundaries;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> next_layer_boundaries;
std::unordered_map<const PrintObject*, AABBTreeLines::LinesDistancer<Linef3>> prev_layer_boundaries;
std::unordered_map<const PrintObject*, AABBTreeLines::LinesDistancer<Linef3>> next_layer_boundaries;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> prev_curled_extrusions;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> next_curled_extrusions;
const PrintObject *current_object;
@@ -304,7 +307,7 @@ public:
if (layer == nullptr) return;
const PrintObject *object = obj;
prev_layer_boundaries[object] = next_layer_boundaries[object];
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(layer->lslices)};
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef3>{to_unscaled_linesf3(layer->lslices)};
prev_curled_extrusions[object] = next_curled_extrusions[object];
next_curled_extrusions[object] = AABBTreeLines::LinesDistancer<CurledLine>{layer->curled_lines};
}
@@ -359,56 +362,54 @@ public:
smallest_distance_with_lower_speed=-1.f;
// Orca: Pass to the point properties estimator the smallest ovehang distance that triggers a slowdown (smallest_distance_with_lower_speed)
std::vector<ExtendedPoint> extended_points = estimate_points_properties<true, true, true, true>
(path.polyline.points,
prev_layer_boundaries[current_object],
path.width,
-1,
smallest_distance_with_lower_speed);
std::vector<ExtendedPoint<3>> extended_points =
estimate_points_properties<true, true, true, true>(path.polyline.points, prev_layer_boundaries[current_object], path.width, -1,
smallest_distance_with_lower_speed);
const auto width_inv = 1.0f / path.width;
std::vector<ProcessedPoint> processed_points;
processed_points.reserve(extended_points.size());
for (size_t i = 0; i < extended_points.size(); i++) {
const ExtendedPoint &curr = extended_points[i];
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
const ExtendedPoint<3>& curr = extended_points[i];
const ExtendedPoint<3>& next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
float artificial_distance_to_curled_lines = 0.0;
if(slowdown_for_curled_edges) {
// The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines
const double dist_limit = 10.0 * path.width;
{
Vec2d middle = 0.5 * (curr.position + next.position);
auto line_indices = prev_curled_extrusions[current_object].all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit));
if (!line_indices.empty()) {
double len = (next.position - curr.position).norm();
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle of this long line
Vec3d middle = 0.5 * (curr.position + next.position);
auto line_indices = prev_curled_extrusions[current_object].all_lines_in_radius(Point::new_scale(middle),
scale_(dist_limit));
if (!line_indices.empty()) {
double len = (next.position - curr.position).norm();
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle of this long line
// The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down.
// NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point
// TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge
if (len > 2) {
Vec2d dir = Vec2d(next.position - curr.position) / len;
Vec2d right = Vec2d(-dir.y(), dir.x());
Vec2d dir = Vec2d(next.position.head<2>() - curr.position.head<2>()) / len;
Vec2d right = Vec2d(-dir.y(), dir.x());
Polygon box_of_influence = {
scaled(Vec2d(curr.position + right * dist_limit)),
scaled(Vec2d(next.position + right * dist_limit)),
scaled(Vec2d(next.position - right * dist_limit)),
scaled(Vec2d(curr.position - right * dist_limit)),
};
Polygon box_of_influence = {
scaled(Vec2d(curr.position.head<2>() + right * dist_limit)),
scaled(Vec2d(next.position.head<2>() + right * dist_limit)),
scaled(Vec2d(next.position.head<2>() - right * dist_limit)),
scaled(Vec2d(curr.position.head<2>() - right * dist_limit)),
};
double projected_lengths_sum = 0;
for (size_t idx : line_indices) {
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
double projected_lengths_sum = 0;
for (size_t idx : line_indices) {
const CurledLine& line = prev_curled_extrusions[current_object].get_line(idx);
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
if (inside.empty())
continue;
double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast<double>()))));
projected_lengths_sum += projected_length;
}
if (projected_lengths_sum < 0.4 * len) {
line_indices.clear();
}
}
}
if (projected_lengths_sum < 0.4 * len) {
line_indices.clear();
}
}
for (size_t idx : line_indices) {
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
@@ -418,9 +419,9 @@ public:
(line.curled_height / (path.height * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
artificial_distance_to_curled_lines = std::max(artificial_distance_to_curled_lines, dist);
}
}
}
}
}
}
}
auto calculate_speed = [&speed_sections, &original_speed](float distance) {
float final_speed;
@@ -452,8 +453,8 @@ public:
}
float overlap = std::min(1 - (curr.distance+artificial_distance_to_curled_lines) * width_inv, 1 - (next.distance+artificial_distance_to_curled_lines) * width_inv);
processed_points.push_back({ scaled(curr.position), extrusion_speed, overlap });
processed_points.push_back({Point3(scaled(curr.position)), extrusion_speed, overlap});
}
return processed_points;
}

View File

@@ -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);

View File

@@ -1514,7 +1514,7 @@ 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];
current.foot_pt = loop.paths[current.path_idx].polyline.points[current.segment_idx].to_point();
return current;
};
@@ -1527,7 +1527,7 @@ 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]};
ExtrusionLoop::ClosestPathPoint closest_point{0, 0, loop.paths[0].polyline.points[0].to_point()};
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();
});

View File

@@ -929,6 +929,11 @@ std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& cent
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment, bool force_no_extrusion)
{
// Check if Z actually changes (at export precision) before emitting it.
// ZAA sloped extrusions call this for every segment, but many consecutive
// segments share the same quantized Z — emitting it every time is redundant.
bool z_changed = (GCodeG1Formatter::quantize_xyzf(point(2)) != GCodeG1Formatter::quantize_xyzf(m_pos(2)));
m_pos = point;
m_lifted = 0;
if (!force_no_extrusion)
@@ -938,7 +943,10 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) };
GCodeG1Formatter w;
w.emit_xyz(point_on_plate);
if (z_changed)
w.emit_xyz(point_on_plate);
else
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
if (!force_no_extrusion)
w.emit_e(filament()->E());
//BBS

View File

@@ -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;

View File

@@ -3,6 +3,7 @@
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "PerimeterGenerator.hpp"
#include "Point.hpp"
#include "Print.hpp"
#include "Surface.hpp"
#include "BoundingBox.hpp"

View File

@@ -224,17 +224,28 @@ using CurledLines = std::vector<CurledLine>;
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<double>().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()); }
static inline double distance_to_squared(const Point3& point, const Point3& a, const Point3& b)
{
return line_alg::distance_to_squared(Line3{a, b}, Vec<3, coord_t>{point});
}
Point3 a;
Point3 b;
static const constexpr int Dim = 3;
using Scalar = Vec3crd::Scalar;
using Scalar = coord_t;
};
class Linef
@@ -243,6 +254,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;
@@ -256,6 +271,7 @@ class Linef3
public:
Linef3() : a(Vec3d::Zero()), b(Vec3d::Zero()) {}
Linef3(const Vec3d& _a, const Vec3d& _b) : a(_a), b(_b) {}
Linef3(const Vec2d& _a, const Vec2d& _b, double z) : a(Vec3d(_a.x(), _a.y(), z)), b(Vec3d(_b.x(), _b.y(), z)) {}
Vec3d intersect_plane(double z) const;
void scale(double factor) { this->a *= factor; this->b *= factor; }
@@ -263,6 +279,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;
@@ -270,6 +312,8 @@ public:
using Scalar = Vec3d::Scalar;
};
using Linesf3 = std::vector<Linef3>;
BoundingBox get_extents(const Lines &lines);
} // namespace Slic3r

View File

@@ -422,32 +422,23 @@ Points MultiPoint::concave_hull_2d(const Points& pts, const double tolerence)
return min_distance;
}
void MultiPoint3::translate(double x, double y)
void MultiPoint3::translate(const Point3& v)
{
for (Vec3crd &p : points) {
p(0) += coord_t(x);
p(1) += coord_t(y);
}
}
void MultiPoint3::translate(const Point& vector)
{
this->translate(vector(0), vector(1));
for (Point3& pt : points)
pt += v;
}
double MultiPoint3::length() const
{
double len = 0.0;
for (const Line3& line : this->lines())
len += line.length();
const Lines3& lines = this->lines();
double len = 0;
for (auto it = lines.cbegin(); it != lines.cend(); ++it) {
len += it->length();
}
return len;
}
BoundingBox3 MultiPoint3::bounding_box() const
{
return BoundingBox3(points);
}
BoundingBox3 MultiPoint3::bounding_box() const { return BoundingBox3(this->points); }
bool MultiPoint3::remove_duplicate_points()
{
@@ -471,6 +462,75 @@ bool MultiPoint3::remove_duplicate_points()
return false;
}
// Douglas-Peucker simplification for 3D points
Points3 MultiPoint3::_douglas_peucker(const Points3& pts, double tolerance)
{
Points3 result_pts;
double tolerance_sq = tolerance * tolerance;
if (!pts.empty()) {
const Point3* anchor = &pts.front();
size_t anchor_idx = 0;
const Point3* floater = &pts.back();
size_t floater_idx = pts.size() - 1;
result_pts.reserve(pts.size());
result_pts.emplace_back(*anchor);
if (anchor_idx != floater_idx) {
assert(pts.size() > 1);
std::vector<size_t> dpStack;
dpStack.reserve(pts.size());
dpStack.emplace_back(floater_idx);
for (;;) {
double max_dist_sq = 0.0;
size_t furthest_idx = anchor_idx;
// find point furthest from line seg created by (anchor, floater) and note it
for (size_t i = anchor_idx + 1; i < floater_idx; ++i) {
double dist_sq = Line3::distance_to_squared(pts[i], *anchor, *floater);
if (dist_sq > max_dist_sq) {
max_dist_sq = dist_sq;
furthest_idx = i;
}
}
// remove point if less than tolerance
if (max_dist_sq <= tolerance_sq) {
result_pts.emplace_back(*floater);
anchor_idx = floater_idx;
anchor = floater;
assert(dpStack.back() == floater_idx);
dpStack.pop_back();
if (dpStack.empty())
break;
floater_idx = dpStack.back();
} else {
floater_idx = furthest_idx;
dpStack.emplace_back(floater_idx);
}
floater = &pts[floater_idx];
}
}
assert(result_pts.front() == pts.front());
assert(result_pts.back() == pts.back());
#if 0
{
static int iRun = 0;
BoundingBox bbox(pts);
BoundingBox bbox2(result_pts);
bbox.merge(bbox2);
SVG svg(debug_out_path("douglas_peucker_%d.svg", iRun ++).c_str(), bbox);
if (pts.front() == pts.back())
svg.draw(Polygon(pts), "black");
else
svg.draw(Polyline(pts), "black");
if (result_pts.front() == result_pts.back())
svg.draw(Polygon(result_pts), "green", scale_(0.1));
else
svg.draw(Polyline(result_pts), "green", scale_(0.1));
}
#endif
}
return result_pts;
}
BoundingBox get_extents(const MultiPoint &mp)
{
return BoundingBox(mp.points);
@@ -514,4 +574,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 &center)
{
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<double>::max();
auto eps2 = scaled_epsilon * scaled_epsilon;
int idx_min = -1;
for (const Point3 &pt : this->points) {
double d2 = (pt.to_point() - point).cast<double>().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<double>::max();
auto eps2 = scaled_epsilon * scaled_epsilon;
int idx_min = -1;
for (const Point3 &pt : this->points) {
double d2 = (pt - point).cast<double>().squaredNorm();
if (d2 < dist2_min) {
idx_min = int(&pt - &this->points.front());
dist2_min = d2;
}
}
return (dist2_min < eps2) ? idx_min : -1;
}
}

View File

@@ -119,18 +119,50 @@ 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 append(const Points3::const_iterator& begin, const Points3::const_iterator& end)
{
this->points.insert(this->points.end(), begin, end);
}
void translate(double x, double y, double z = 0) { this->translate(Point3(coord_t(x), coord_t(y), coord_t(z))); }
void translate(const Point3& 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 &center);
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(); }
void translate(double x, double y);
void translate(const Point& vector);
virtual Lines3 lines() const = 0;
double length() const;
bool is_valid() const { return this->points.size() >= 2; }
BoundingBox3 bounding_box() const;
// 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);

View File

@@ -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;
@@ -441,7 +442,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);
@@ -475,11 +476,13 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
};
std::unordered_map<Point, PointInfo, PointHash> point_occurrence;
for (const ExtrusionPath& path : paths) {
++point_occurrence[path.polyline.first_point()].occurrence;
++point_occurrence[path.polyline.last_point()].occurrence;
Point first_p = path.polyline.first_point().to_point();
Point last_p = path.polyline.last_point().to_point();
++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;
}
}
@@ -674,11 +677,17 @@ bool paths_touch(const ExtrusionPath &path_one, const ExtrusionPath &path_two, d
{
AABBTreeLines::LinesDistancer<Line> 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<false>(path_one.polyline.points[pt_idx]) < limit_distance) { return true; }
Point pt = path_one.polyline.points[pt_idx].to_point();
if (lines_two.distance_from_lines<false>(pt) < limit_distance) {
return true;
}
}
AABBTreeLines::LinesDistancer<Line> 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<false>(path_two.polyline.points[pt_idx]) < limit_distance) { return true; }
Point pt = path_two.polyline.points[pt_idx].to_point();
if (lines_one.distance_from_lines<false>(pt) < limit_distance) {
return true;
}
}
return false;
}
@@ -1032,7 +1041,7 @@ std::tuple<std::vector<ExtrusionPaths>, 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<true>(path.first_point()) <= 0 ||
@@ -1044,7 +1053,7 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
size_t min_dist_idx = 0;
double min_dist = std::numeric_limits<double>::max();
for (size_t i = 0; i < overhang_region.front().polyline.size(); i++) {
Point p = overhang_region.front().polyline[i];
Point p = overhang_region.front().polyline.points[i].to_point();
if (double d = lower_layer_aabb_tree.distance_from_lines<true>(p) < min_dist) {
min_dist = d;
min_dist_idx = i;

View File

@@ -1,6 +1,8 @@
#include "Point.hpp"
#include "Exception.hpp"
#include "Line.hpp"
#include "MultiPoint.hpp"
#include "Polyline.hpp"
#include "Int128.hpp"
#include "BoundingBox.hpp"
#include <algorithm>
@@ -257,4 +259,91 @@ 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.emplace_back(pt.to_point());
}
return points2;
}
Points3 to_points3(const Points& points)
{
Points3 points3;
points3.reserve(points.size());
for (const Point& pt : points) {
points3.emplace_back(pt);
}
return points3;
}
// Point3 method implementations
void Point3::rotate(double angle, const Point3 &center) {
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
throw RuntimeError("Point3::projection_onto(MultiPoint3) not implemented yet");
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<double>().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);
}
}

View File

@@ -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<BaseType>;
using Points = std::vector<Point, PointsAllocator<Point>>;
using PointPtrs = std::vector<Point*>;
using PointConstPtrs = std::vector<const Point*>;
using Points3 = std::vector<Vec3crd>;
using Points3 = std::vector<Point3, PointsAllocator<Point3>>;
using Pointfs = std::vector<Vec2d>;
using Vec2ds = std::vector<Vec2d>;
using Pointf3s = std::vector<Vec3d>;
@@ -79,6 +84,12 @@ using Transform2d = Eigen::Transform<double, 2, Eigen::Affine, Eigen::DontAli
using Transform3f = Eigen::Transform<float, 3, Eigen::Affine, Eigen::DontAlign>;
using Transform3d = Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAlign>;
// Utility functions for Point/Polyline conversion
Polyline to_polyline(const Points &points);
Polyline3 to_polyline(const Points3 &points);
Points to_points(const Points3 &points);
Points3 to_points3(const Points& points);
// using ColorRGBA = std::array<float, 4>;
// I don't know why Eigen::Transform::Identity() return a const object...
template<int N, class T> Transform<N, T> identity() { return Transform<N, T>::Identity(); }
@@ -258,6 +269,118 @@ 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 Vec2crd& vec2crd, coord_t z = 0) : Vec3crd(vec2crd.x(), vec2crd.y(), z) {}
explicit Point3(const Vec3crd &vec3crd) : Vec3crd(vec3crd) {}
// This constructor allows you to construct Point from Eigen expressions
template<typename OtherDerived>
explicit Point3(const Eigen::MatrixBase<OtherDerived> &other) : Vec3crd(other) {}
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<typename OtherDerived>
Point3& operator=(const Eigen::MatrixBase<OtherDerived> &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 &center);
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 &center) 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<double>().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());
@@ -300,6 +423,12 @@ inline Point lerp(const Point &a, const Point &b, double t)
return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
}
inline Point3 lerp(const Point3& a, const Point3& b, double t)
{
assert((t >= -EPSILON) && (t <= 1. + EPSILON));
return Point3(((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>());
}
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
template<bool IncludeBoundary = false>

View File

@@ -612,6 +612,28 @@ std::pair<int, Point> foot_pt(const Points &polyline, const Point &pt)
return std::make_pair(int(it_proj - polyline.begin()) - 1, foot_pt_min);
}
std::pair<int, Point3> 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<double>::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<double>().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;
@@ -639,15 +661,383 @@ void ThickPolyline::start_at_index(int index)
Lines3 Polyline3::lines() const
{
Lines3 lines;
if (points.size() >= 2)
{
lines.reserve(points.size() - 1);
for (Points3::const_iterator it = points.begin(); it != points.end() - 1; ++it)
{
if (this->points.size() >= 2) {
lines.reserve(this->points.size() - 1);
for (Points3::const_iterator it = this->points.begin(); it != this->points.end() - 1; ++it) {
lines.emplace_back(*it, *(it + 1));
}
}
return lines;
}
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)
{
bool last_point_inserted = false;
size_t remove_after_index = MultiPoint3::size();
while (distance > 0) {
Vec3d last_point = this->last_point().cast<double>();
this->points.pop_back();
remove_after_index--;
if (this->points.empty()) {
this->fitting_result.clear();
return;
}
Vec3d v = this->last_point().cast<double>() - last_point;
double lsqr = v.squaredNorm();
if (lsqr > distance * distance) {
this->points.emplace_back((last_point + v * (distance / sqrt(lsqr))).cast<coord_t>());
last_point_inserted = true;
break;
}
distance -= sqrt(lsqr);
}
// BBS: don't need to clip fitting result if it's empty
if (fitting_result.empty())
return;
while (!fitting_result.empty() && fitting_result.back().start_point_index >= remove_after_index)
fitting_result.pop_back();
if (!fitting_result.empty()) {
// BBS: last remaining segment is arc move, then clip the arc at last point
if (fitting_result.back().path_type == EMovePathType::Arc_move_ccw ||
fitting_result.back().path_type == EMovePathType::Arc_move_cw) {
if (fitting_result.back().arc_data.clip_end(this->last_point().to_point()))
// BBS: succeed to clip arc, then update the last point
// TODO: fix z parameter
this->points.back() = Point3(fitting_result.back().arc_data.end_point, this->points.back().z());
else
// BBS: Failed to clip arc, then back to linear move
fitting_result.back().path_type = EMovePathType::Linear_move;
}
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)
{
// BBS: do arc fit first, then use DP simplify to handle the straight part to reduce point.
Points points_2d = to_points(this->points);
ArcFitter::do_arc_fitting_and_simplify(points_2d, this->fitting_result, tolerance);
this->points = to_points3(points_2d);
}
void Polyline3::reverse()
{
// BBS: reverse points
MultiPoint3::reverse();
// BBS: reverse the fitting_result
if (!this->fitting_result.empty()) {
for (size_t i = 0; i < this->fitting_result.size(); i++) {
std::swap(fitting_result[i].start_point_index, fitting_result[i].end_point_index);
fitting_result[i].start_point_index = MultiPoint3::size() - 1 - fitting_result[i].start_point_index;
fitting_result[i].end_point_index = MultiPoint3::size() - 1 - fitting_result[i].end_point_index;
if (fitting_result[i].is_arc_move())
fitting_result[i].reverse_arc_path();
}
std::reverse(this->fitting_result.begin(), this->fitting_result.end());
}
}
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 {
// BBS: spilit 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;
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);
// append_fitting_result_after_append_points();
// BBS: don't need to append same point
if (!this->empty() && this->last_point() == point)
return;
MultiPoint3::append(point);
append_fitting_result_after_append_points();
}
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 {
// BBS: append the first point to create connection first, update the fitting date as well
this->append(src.points[0]);
// BBS: append a polyline which has fitting data to a polyline without fitting data.
// Then create a fake fitting data first, so that we can keep the fitting data in last polyline
if (this->fitting_result.empty() && !src.fitting_result.empty()) {
this->fitting_result.emplace_back(PathFittingData{0, this->points.size() - 1, EMovePathType::Linear_move, ArcSegment()});
}
// BBS: then append the remain points
MultiPoint3::append(src.points.begin() + 1, src.points.end());
// BBS: finally append the fitting data
append_fitting_result_after_append_polyline(src);
}
}
void Polyline3::append_before(const Point3& point)
{
// BBS: don't need to append same point
if (!this->empty() && this->first_point() == point)
return;
if (this->size() == 1) {
this->fitting_result.clear();
MultiPoint3::append(point);
MultiPoint3::reverse();
} else {
this->reverse();
this->append(point);
this->reverse();
}
}
void Polyline3::split_at(Point& point, Polyline3* p1, Polyline3* p2) const
{
if (this->points.empty()) return;
// 0 judge whether the point is on the polyline
int index = this->find_point(point);
if (index != -1) {
// BBS: the spilit point is on the polyline, then easy
split_at_index(index, p1, p2);
point = p1->is_valid() ? p1->last_point().to_point() : p2->first_point().to_point();
return;
}
// 1 find the line to split at
size_t line_idx = 0;
Point p = this->first_point().to_point();
double min = (p - point).cast<double>().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<double>().norm() < min) {
p = p_tmp;
min = (p - point).cast<double>().norm();
line_idx = line - lines.begin();
}
}
// 2 judge whether the cloest point is one vertex of polyline.
// and spilit the polyline at different index
index = this->find_point(p);
if (index != -1) {
this->split_at_index(index, p1, p2);
p1->append(Point3(point, p1->last_point().z()));
p2->append_before(Point3(point, p2->first_point().z()));
} else {
Polyline3 temp;
this->split_at_index(line_idx, p1, &temp);
p1->append(Point3(point, p1->last_point().z()));
this->split_at_index(line_idx + 1, &temp, p2);
p2->append_before(Point3(point, p2->first_point().z()));
}
}
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(this->first_point());
*p2 = *this;
} else if (is_approx(length, this->length(), SCALED_EPSILON)) {
p2->clear();
p2->append(this->last_point());
*p1 = *this;
} else {
// 1 find the line to split at
size_t line_idx = 0;
double acc_length = 0;
Point3 p = this->first_point();
for (const auto& l : this->lines()) {
p = l.b;
const double current_length = l.length();
if (acc_length + current_length >= length) {
p = lerp(l.a, l.b, (length - acc_length) / current_length);
break;
}
acc_length += current_length;
line_idx++;
}
// 2 judge whether the cloest point is one vertex of polyline.
// and spilit the polyline at different index
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(p);
this->split_at_index(line_idx + 1, &temp, p2);
p2->append_before(p);
}
}
return true;
}
bool Polyline3::split_fitting_result_before_index(size_t index, Point3& new_endpoint, std::vector<PathFittingData>& data) const
{
data.clear();
new_endpoint = this->points[index];
if (!this->fitting_result.empty()) {
// BBS: max size
data.reserve(this->fitting_result.size());
// BBS: save fitting result before index
for (size_t i = 0; i < this->fitting_result.size(); i++) {
if (this->fitting_result[i].start_point_index < index)
data.push_back(this->fitting_result[i]);
else
break;
}
if (!data.empty()) {
// BBS: need to clip the arc and generate new end point
if (data.back().is_arc_move() && data.back().end_point_index > index) {
if (!data.back().arc_data.clip_end(this->points[index].to_point()))
// BBS: failed to clip arc, then return to be linear move
data.back().path_type = EMovePathType::Linear_move;
else
// BBS: succeed to clip arc, then update and return the new end point
new_endpoint = Point3(data.back().arc_data.end_point, 0);
}
data.back().end_point_index = index;
}
data.shrink_to_fit();
return true;
}
return false;
}
bool Polyline3::split_fitting_result_after_index(size_t index, Point3& new_startpoint, std::vector<PathFittingData>& data) const
{
data.clear();
new_startpoint = this->points[index];
if (!this->fitting_result.empty()) {
data.reserve(this->fitting_result.size());
for (size_t i = 0; i < this->fitting_result.size(); i++) {
if (this->fitting_result[i].end_point_index > index)
data.push_back(this->fitting_result[i]);
}
if (!data.empty()) {
for (size_t i = 0; i < data.size(); i++) {
if (i != 0) {
data[i].start_point_index -= index;
data[i].end_point_index -= index;
} else {
data[i].end_point_index -= index;
// BBS: need to clip the arc and generate new start point
if (data.front().is_arc_move() && data.front().start_point_index < index) {
if (!data.front().arc_data.clip_start(this->points[index].to_point()))
// BBS: failed to clip arc, then return to be linear move
data.front().path_type = EMovePathType::Linear_move;
else
// BBS: succeed to clip arc, then update and return the new start point
new_startpoint = Point3(data.front().arc_data.start_point, 0);
}
data[i].start_point_index = 0;
}
}
}
data.shrink_to_fit();
return true;
}
return false;
}
void Polyline3::append_fitting_result_after_append_points()
{
if (!fitting_result.empty()) {
if (fitting_result.back().is_linear_move()) {
fitting_result.back().end_point_index = this->points.size() - 1;
} else {
size_t new_start = fitting_result.back().end_point_index;
size_t new_end = this->points.size() - 1;
if (new_start != new_end)
fitting_result.emplace_back(PathFittingData{new_start, new_end, EMovePathType::Linear_move, ArcSegment()});
}
}
}
void Polyline3::append_fitting_result_after_append_polyline(const Polyline3& src)
{
if (!this->fitting_result.empty()) {
// BBS: offset and save the fitting_result from src polyline
if (!src.fitting_result.empty()) {
size_t old_size = this->fitting_result.size();
size_t index_offset = this->fitting_result.back().end_point_index;
this->fitting_result.insert(this->fitting_result.end(), src.fitting_result.begin(), src.fitting_result.end());
for (size_t i = old_size; i < this->fitting_result.size(); i++) {
this->fitting_result[i].start_point_index += index_offset;
this->fitting_result[i].end_point_index += index_offset;
}
} else {
// BBS: the append polyline has no fitting data, then append as linear move directly
size_t new_start = this->fitting_result.back().end_point_index;
size_t new_end = this->size() - 1;
if (new_start != new_end)
this->fitting_result.emplace_back(PathFittingData{new_start, new_end, EMovePathType::Linear_move, ArcSegment()});
}
}
}
}

View File

@@ -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<int, Point> foot_pt(const Points &polyline, const Point &pt);
std::pair<int, Point3> foot_pt(const Points3 &polyline, const Point3 &pt);
class ThickPolyline : public Polyline {
public:
@@ -290,7 +291,63 @@ 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
void 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<PathFittingData> fitting_result;
private:
void append_fitting_result_after_append_points();
void append_fitting_result_after_append_polyline(const Polyline3& src);
bool split_fitting_result_before_index(size_t index, Point3& new_endpoint, std::vector<PathFittingData>& result) const;
bool split_fitting_result_after_index(size_t index, Point3& new_startpoint, std::vector<PathFittingData>& result) const;
};
typedef std::vector<Polyline3> Polylines3;

View File

@@ -974,16 +974,74 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle)
return false;
}
static std::vector<std::string> s_Preset_print_options {
"layer_height", "initial_layer_print_height", "wall_loops", "alternate_extra_wall", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "spiral_starting_flow_ratio", "spiral_finishing_flow_ratio", "slicing_mode",
"top_shell_layers", "top_shell_thickness", "top_surface_density", "bottom_surface_density", "bottom_shell_layers", "bottom_shell_thickness",
"extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only", "wall_direction",
"seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density","fill_multiline", "sparse_infill_pattern", "lateral_lattice_angle_1", "lateral_lattice_angle_2", "infill_overhang_angle", "top_surface_pattern", "bottom_surface_pattern",
"infill_direction", "solid_infill_direction", "counterbore_hole_bridging","infill_shift_step", "sparse_infill_rotate_template", "solid_infill_rotate_template", "symmetric_infill_y_axis","skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density",
"align_infill_direction_to_model", "extra_solid_infills",
"minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern","gap_fill_target",
"ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing", "ironing_angle", "ironing_angle_fixed", "ironing_inset",
"support_ironing", "support_ironing_pattern", "support_ironing_flow", "support_ironing_spacing",
static std::vector<std::string> s_Preset_print_options{
"layer_height",
"initial_layer_print_height",
"wall_loops",
"alternate_extra_wall",
"slice_closing_radius",
"spiral_mode",
"spiral_mode_smooth",
"spiral_mode_max_xy_smoothing",
"spiral_starting_flow_ratio",
"spiral_finishing_flow_ratio",
"slicing_mode",
"top_shell_layers",
"top_shell_thickness",
"top_surface_density",
"bottom_surface_density",
"bottom_shell_layers",
"bottom_shell_thickness",
"extra_perimeters_on_overhangs",
"ensure_vertical_shell_thickness",
"reduce_crossing_wall",
"detect_thin_wall",
"detect_overhang_wall",
"overhang_reverse",
"overhang_reverse_threshold",
"overhang_reverse_internal_only",
"wall_direction",
"seam_position",
"staggered_inner_seams",
"wall_sequence",
"is_infill_first",
"sparse_infill_density",
"fill_multiline",
"sparse_infill_pattern",
"lateral_lattice_angle_1",
"lateral_lattice_angle_2",
"infill_overhang_angle",
"top_surface_pattern",
"bottom_surface_pattern",
"infill_direction",
"solid_infill_direction",
"counterbore_hole_bridging",
"infill_shift_step",
"sparse_infill_rotate_template",
"solid_infill_rotate_template",
"symmetric_infill_y_axis",
"skeleton_infill_density",
"infill_lock_depth",
"skin_infill_depth",
"skin_infill_density",
"align_infill_direction_to_model",
"extra_solid_infills",
"minimum_sparse_infill_area",
"reduce_infill_retraction",
"internal_solid_infill_pattern",
"gap_fill_target",
"ironing_type",
"ironing_pattern",
"ironing_flow",
"ironing_speed",
"ironing_spacing",
"ironing_angle",
"ironing_angle_fixed",
"ironing_inset",
"support_ironing",
"support_ironing_pattern",
"support_ironing_flow",
"support_ironing_spacing",
"max_travel_detour_distance",
"fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", "fuzzy_skin_first_layer", "fuzzy_skin_noise_type", "fuzzy_skin_mode", "fuzzy_skin_scale", "fuzzy_skin_octaves", "fuzzy_skin_persistence",
"max_volumetric_extrusion_rate_slope", "max_volumetric_extrusion_rate_slope_segment_length","extrusion_rate_smoothing_external_perimeter_only",
@@ -995,54 +1053,220 @@ static std::vector<std::string> s_Preset_print_options {
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
"support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style",
// BBS
"print_extruder_id", "print_extruder_variant",
"print_extruder_id",
"print_extruder_variant",
"independent_support_layer_height",
"support_angle", "support_interface_top_layers", "support_interface_bottom_layers",
"support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern",
"support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges","enable_extra_bridge_layer", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang",
"filename_format", "wall_filament", "support_bottom_z_distance",
"sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body",
"ooze_prevention", "standby_temperature_delta", "preheat_time","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width",
"outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width",
"skin_infill_line_width","skeleton_infill_line_width",
"top_surface_line_width", "support_line_width", "infill_wall_overlap","top_bottom_infill_wall_overlap", "bridge_flow", "internal_bridge_flow",
"elefant_foot_compensation", "elefant_foot_compensation_layers", "elefant_foot_layers_density", "xy_contour_compensation", "xy_hole_compensation", "resolution", "enable_prime_tower", "prime_tower_enable_framework",
"prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "prime_volume",
"support_angle",
"support_interface_top_layers",
"support_interface_bottom_layers",
"support_interface_pattern",
"support_interface_spacing",
"support_interface_loop_pattern",
"support_top_z_distance",
"support_on_build_plate_only",
"support_critical_regions_only",
"bridge_no_support",
"thick_bridges",
"thick_internal_bridges",
"dont_filter_internal_bridges",
"enable_extra_bridge_layer",
"max_bridge_length",
"print_sequence",
"print_order",
"support_remove_small_overhang",
"filename_format",
"wall_filament",
"support_bottom_z_distance",
"sparse_infill_filament",
"solid_infill_filament",
"support_filament",
"support_interface_filament",
"support_interface_not_for_body",
"ooze_prevention",
"standby_temperature_delta",
"preheat_time",
"preheat_steps",
"interface_shells",
"line_width",
"initial_layer_line_width",
"inner_wall_line_width",
"outer_wall_line_width",
"sparse_infill_line_width",
"internal_solid_infill_line_width",
"skin_infill_line_width",
"skeleton_infill_line_width",
"top_surface_line_width",
"support_line_width",
"infill_wall_overlap",
"top_bottom_infill_wall_overlap",
"bridge_flow",
"internal_bridge_flow",
"elefant_foot_compensation",
"elefant_foot_compensation_layers",
"elefant_foot_layers_density",
"xy_contour_compensation",
"xy_hole_compensation",
"resolution",
"enable_prime_tower",
"prime_tower_enable_framework",
"prime_tower_width",
"prime_tower_brim_width",
"prime_tower_skip_points",
"prime_volume",
"prime_tower_infill_gap",
"prime_tower_flat_ironing",
"enable_tower_interface_features",
"enable_tower_interface_cooldown_during_tower",
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits",
"flush_into_infill", "flush_into_objects", "flush_into_support",
"tree_support_branch_angle", "tree_support_angle_slow", "tree_support_wall_count", "tree_support_top_rate", "tree_support_branch_distance", "tree_support_tip_diameter",
"tree_support_branch_diameter", "tree_support_branch_diameter_angle",
"detect_narrow_internal_solid_infill",
"gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination","infill_combination_max_layer_height", /*"adaptive_layer_height",*/
"support_bottom_interface_spacing", "enable_overhang_speed", "slowdown_for_curled_perimeters", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed",
"initial_layer_infill_speed", "only_one_wall_top",
"timelapse_type",
"wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
"wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", "process_change_extrusion_role_gcode",
"min_length_factor", "wall_maximum_resolution", "wall_maximum_deviation",
"small_perimeter_speed", "small_perimeter_threshold","bridge_angle","internal_bridge_angle", "filter_out_gap_fill", "travel_acceleration","inner_wall_acceleration", "min_width_top_surface",
"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk","travel_jerk","default_junction_deviation",
"top_solid_infill_flow_ratio","bottom_solid_infill_flow_ratio","only_one_wall_first_layer", "print_flow_ratio", "seam_gap",
"set_other_flow_ratios", "first_layer_flow_ratio", "outer_wall_flow_ratio", "inner_wall_flow_ratio", "overhang_flow_ratio", "sparse_infill_flow_ratio", "internal_solid_infill_flow_ratio", "gap_fill_flow_ratio", "support_flow_ratio", "support_interface_flow_ratio",
"role_based_wipe_speed", "wipe_speed", "accel_to_decel_enable", "accel_to_decel_factor", "wipe_on_loops", "wipe_before_external_loop",
"bridge_density","internal_bridge_density", "precise_outer_wall", "bridge_acceleration",
"sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_auto_brim",
"tree_support_brim_width", "gcode_comments", "gcode_label_objects",
"initial_layer_travel_speed", "initial_layer_travel_acceleration", "initial_layer_travel_jerk", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max","initial_layer_min_bead_width",
"make_overhang_printable", "make_overhang_printable_angle", "make_overhang_printable_hole_size" ,"notes",
"wipe_tower_cone_angle", "wipe_tower_extra_spacing","wipe_tower_max_purge_speed",
"wipe_tower_wall_type", "wipe_tower_extra_rib_length", "wipe_tower_rib_width", "wipe_tower_fillet_wall",
"wipe_tower_filament", "wiping_volumes_extruders","wipe_tower_bridging", "wipe_tower_extra_flow","single_extruder_multi_material_priming",
"wipe_tower_rotation_angle", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic", "tree_support_branch_angle_organic",
"hole_to_polyhole", "hole_to_polyhole_threshold", "hole_to_polyhole_twisted", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth",
"small_area_infill_flow_compensation", "small_area_infill_flow_compensation_model",
"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",
"wipe_tower_no_sparse_layers",
"compatible_printers",
"compatible_printers_condition",
"inherits",
"flush_into_infill",
"flush_into_objects",
"flush_into_support",
"tree_support_branch_angle",
"tree_support_angle_slow",
"tree_support_wall_count",
"tree_support_top_rate",
"tree_support_branch_distance",
"tree_support_tip_diameter",
"tree_support_branch_diameter",
"tree_support_branch_diameter_angle",
"detect_narrow_internal_solid_infill",
"gcode_add_line_number",
"enable_arc_fitting",
"precise_z_height",
"infill_combination",
"infill_combination_max_layer_height", /*"adaptive_layer_height",*/
"support_bottom_interface_spacing",
"enable_overhang_speed",
"slowdown_for_curled_perimeters",
"overhang_1_4_speed",
"overhang_2_4_speed",
"overhang_3_4_speed",
"overhang_4_4_speed",
"initial_layer_infill_speed",
"only_one_wall_top",
"timelapse_type",
"wall_generator",
"wall_transition_length",
"wall_transition_filter_deviation",
"wall_transition_angle",
"wall_distribution_count",
"min_feature_size",
"min_bead_width",
"post_process",
"process_change_extrusion_role_gcode",
"min_length_factor",
"wall_maximum_resolution",
"wall_maximum_deviation",
"small_perimeter_speed",
"small_perimeter_threshold",
"bridge_angle",
"internal_bridge_angle",
"filter_out_gap_fill",
"travel_acceleration",
"inner_wall_acceleration",
"min_width_top_surface",
"default_jerk",
"outer_wall_jerk",
"inner_wall_jerk",
"infill_jerk",
"top_surface_jerk",
"initial_layer_jerk",
"travel_jerk",
"default_junction_deviation",
"top_solid_infill_flow_ratio",
"bottom_solid_infill_flow_ratio",
"only_one_wall_first_layer",
"print_flow_ratio",
"seam_gap",
"set_other_flow_ratios",
"first_layer_flow_ratio",
"outer_wall_flow_ratio",
"inner_wall_flow_ratio",
"overhang_flow_ratio",
"sparse_infill_flow_ratio",
"internal_solid_infill_flow_ratio",
"gap_fill_flow_ratio",
"support_flow_ratio",
"support_interface_flow_ratio",
"role_based_wipe_speed",
"wipe_speed",
"accel_to_decel_enable",
"accel_to_decel_factor",
"wipe_on_loops",
"wipe_before_external_loop",
"bridge_density",
"internal_bridge_density",
"precise_outer_wall",
"bridge_acceleration",
"sparse_infill_acceleration",
"internal_solid_infill_acceleration",
"tree_support_auto_brim",
"tree_support_brim_width",
"gcode_comments",
"gcode_label_objects",
"initial_layer_travel_speed",
"initial_layer_travel_acceleration",
"initial_layer_travel_jerk",
"exclude_object",
"slow_down_layers",
"infill_anchor",
"infill_anchor_max",
"initial_layer_min_bead_width",
"make_overhang_printable",
"make_overhang_printable_angle",
"make_overhang_printable_hole_size",
"notes",
"wipe_tower_cone_angle",
"wipe_tower_extra_spacing",
"wipe_tower_max_purge_speed",
"wipe_tower_wall_type",
"wipe_tower_extra_rib_length",
"wipe_tower_rib_width",
"wipe_tower_fillet_wall",
"wipe_tower_filament",
"wiping_volumes_extruders",
"wipe_tower_bridging",
"wipe_tower_extra_flow",
"single_extruder_multi_material_priming",
"wipe_tower_rotation_angle",
"tree_support_branch_distance_organic",
"tree_support_branch_diameter_organic",
"tree_support_branch_angle_organic",
"hole_to_polyhole",
"hole_to_polyhole_threshold",
"hole_to_polyhole_twisted",
"mmu_segmented_region_max_width",
"mmu_segmented_region_interlocking_depth",
"small_area_infill_flow_compensation",
"small_area_infill_flow_compensation_model",
"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",
"ironing_expansion",
};
static std::vector<std::string> s_Preset_filament_options {/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "pellet_flow_coefficient", "volumetric_speed_coefficients", "filament_type",

View File

@@ -2213,6 +2213,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->need_z_contouring();
if (need_contouring) {
obj->contour_z();
} else {
if (obj->set_started(posContouring))
obj->set_done(posContouring);
}
}
tbb::parallel_for(tbb::blocked_range<int>(0, int(m_objects.size())),
[this, need_slicing_objects](const tbb::blocked_range<int>& range) {
for (int i = range.begin(); i < range.end(); i++) {
@@ -2251,6 +2262,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))
@@ -2630,7 +2643,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.
@@ -2688,7 +2701,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.
@@ -4093,7 +4106,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];
@@ -4946,8 +4960,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);

View File

@@ -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,8 @@ private:
void prepare_infill();
void infill();
void ironing();
bool need_z_contouring() const;
void contour_z();
void generate_support_material();
void estimate_curled_extrusions();
void simplify_extrusion_path();
@@ -642,14 +644,14 @@ struct FakeWipeTower
std::vector<ExtrusionPaths> 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 +688,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.); y<maxCorner.y(); y+=scale_(3.)) {
path.polyline = { {minCorner.x(), y}, {maxCorner.x(), y} };
path.polyline = Polyline3(Polyline{{ {minCorner.x(), y}, {maxCorner.x(), y} }});
paths.back().emplace_back(path);
}

View File

@@ -4111,6 +4111,51 @@ void PrintConfigDef::init_fff_params()
def->mode = 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 = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
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 = comExpert;
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 height of top surface perimeters to match height of model edge. "
"Effects perimeters whose slope is less than this angle in degrees. Reasonable value is 35. Set 0 to disable.");
def->sidetext = L("°");
def->min = 0;
def->max = 90;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
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 = comExpert;
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 = comExpert;
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.");

View File

@@ -1198,7 +1198,13 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloatOrPercent, scarf_joint_speed))
((ConfigOptionFloat, scarf_joint_flow_ratio))
((ConfigOptionPercent, scarf_overhang_threshold))
)
// Orca: Z Anti-Aliasing (aka Z Contouring)
((ConfigOptionBool, zaa_enabled))
((ConfigOptionBool, zaa_dont_alternate_fill_direction))
((ConfigOptionFloat, zaa_min_z))
((ConfigOptionFloat, zaa_minimize_perimeter_height))
)
PRINT_CONFIG_CLASS_DEFINE(
MachineEnvelopeConfig,

View File

@@ -1,4 +1,6 @@
#include "Exception.hpp"
#include "Model.hpp"
#include "Point.hpp"
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
@@ -8,6 +10,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"
@@ -22,7 +25,11 @@
#include "format.hpp"
#include "AABBTreeLines.hpp"
#include <cstddef>
#include <float.h>
#include <iterator>
#include <mutex>
#include <string>
#include <oneapi/tbb/blocked_range.h>
#include <oneapi/tbb/concurrent_vector.h>
#include <oneapi/tbb/parallel_for.h>
@@ -709,6 +716,65 @@ void PrintObject::ironing()
}
}
bool PrintObject::need_z_contouring() const
{
size_t num_regions = this->num_printing_regions();
for (size_t region_id = 0; region_id < num_regions; region_id++) {
if (this->printing_region(region_id).config().zaa_enabled)
return true;
}
return false;
}
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");
}
ModelInstance *inst = m_model_object->instances.front();
Point center_offset = this->center_offset();
Geometry::Transformation trans = inst->get_transformation();
double z = this->m_model_object->min_z();
trans.set_offset(Vec3d(-unscale<double>(center_offset.x()), -unscale<double>(center_offset.y()), 0));
mesh.transform(trans.get_matrix());
sla::IndexedMesh imesh(mesh);
imesh.ground_level_offset(-z);
std::mutex mtx;
size_t completed = 0;
tbb::parallel_for(
// Contouring starting with layer second layer to avoid build plate collision
tbb::blocked_range<size_t>(1, m_layers.size()),
[&, this](const tbb::blocked_range<size_t>& 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()
{
@@ -1370,15 +1436,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) {

View File

@@ -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,23 @@ 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);
bool zaa_active = false;
coordf_t z_offset = 0.0;
size_t num_regions = print_object->num_printing_regions();
for (size_t rid = 0; rid < num_regions; ++rid) {
const auto &rcfg = print_object->printing_region(rid).config();
if (rcfg.zaa_enabled) {
if (!zaa_active || rcfg.zaa_min_z < z_offset)
z_offset = rcfg.zaa_min_z;
zaa_active = true;
}
}
if (zaa_active) {
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) {

View File

@@ -1000,7 +1000,7 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy2(SegmentEndPointFunc
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &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<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(segment_end_point, could_reverse, entities.size(), start_near);
for (std::pair<size_t, bool> &segment : out) {
@@ -1028,6 +1028,11 @@ void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const s
entities.swap(out);
}
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point &start_near)
{
chain_and_reorder_extrusion_entities(entities, &start_near);
}
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &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<ExtrusionEntity*> &entitie
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &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<Point, decltype(segment_end_point)>(segment_end_point, extrusion_paths.size(), start_near);
}

View File

@@ -20,6 +20,7 @@ std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy);
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain);
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point &start_near);
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);

View File

@@ -1163,7 +1163,7 @@ static void modulate_extrusion_by_overlapping_layers(
for (ExtrusionEntity *ee : extrusions_in_out) {
ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(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<Point, Point>(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;

View File

@@ -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();

View File

@@ -165,8 +165,8 @@ void estimate_malformations(LayerPtrs &layers, const Params &params)
flow_width,
params.bridge_distance);
for (size_t i = 0; i < annotated_points.size(); ++i) {
const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
const ExtendedPoint &b = annotated_points[i];
const ExtendedPoint<2>& a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
const ExtendedPoint<2>& b = annotated_points[i];
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
extrusion};

View File

@@ -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<float>(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<float>(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<float>(w) + flow.height() * float(1. - 0.25 * PI));