mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-06-19 02:13:27 +00:00
feat: Add Z Anti-Aliasing (ZAA) contouring support
Port Z Anti-Aliasing from BambuStudio-ZAA (https://github.com/adob/BambuStudio-ZAA) to OrcaSlicer. ZAA eliminates stair-stepping on curved and sloped top surfaces by raycasting each extrusion point against the original 3D mesh and micro-adjusting Z height to follow the actual surface geometry. Key changes: - Add ContourZ.cpp raycasting algorithm (~330 lines) - Extend geometry with 3D support (Point3, Line3, Polyline3, MultiPoint3) - Template arc fitting for 2D/3D compatibility - Change ExtrusionPath::polyline from Polyline to Polyline3 - Add 5 ZAA config options (zaa_enabled, zaa_min_z, etc.) - Add posContouring pipeline step in PrintObject - Update GCode writer for 3D coordinate output - Add ZAA settings UI in Print Settings > Quality - Add docs/ZAA.md with usage and implementation details ZAA is opt-in and disabled by default. When disabled, the slicing pipeline is unchanged.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,16 +431,16 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths,
|
||||
: ExtrusionLoop(role)
|
||||
{
|
||||
// create slopes
|
||||
const auto add_slop = [this, slope_max_segment_length, seam_gap](const ExtrusionPath &path, const Polyline &poly, double ratio_begin, double ratio_end) {
|
||||
const auto add_slop = [this, slope_max_segment_length, seam_gap](const ExtrusionPath &path, const Polyline3 &poly, double ratio_begin, double ratio_end) {
|
||||
if (poly.empty()) { return; }
|
||||
|
||||
// Ensure `slope_max_segment_length`
|
||||
Polyline detailed_poly;
|
||||
Polyline3 detailed_poly;
|
||||
{
|
||||
detailed_poly.append(poly.first_point());
|
||||
|
||||
// Recursively split the line into half until no longer than `slope_max_segment_length`
|
||||
const std::function<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 +461,8 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths,
|
||||
const auto seg_length = detailed_poly.length();
|
||||
if (seg_length > seam_gap) {
|
||||
// Split the segment and remove the last `seam_gap` bit
|
||||
const Polyline orig = detailed_poly;
|
||||
Polyline tmp;
|
||||
const Polyline3 orig = detailed_poly;
|
||||
Polyline3 tmp;
|
||||
orig.split_at_length(seg_length - seam_gap, &detailed_poly, &tmp);
|
||||
|
||||
ratio_end = lerp(ratio_begin, ratio_end, (seg_length - seam_gap) / seg_length);
|
||||
@@ -464,8 +484,8 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths,
|
||||
const double path_len = unscale_(path->length());
|
||||
if (path_len > remaining_length) {
|
||||
// Split current path into slope and non-slope part
|
||||
Polyline slope_path;
|
||||
Polyline flat_path;
|
||||
Polyline3 slope_path;
|
||||
Polyline3 flat_path;
|
||||
path->polyline.split_at_length(scale_(remaining_length), &slope_path, &flat_path);
|
||||
|
||||
add_slop(*path, slope_path, start_ratio, 1);
|
||||
@@ -631,4 +651,28 @@ ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role)
|
||||
return erNone;
|
||||
}
|
||||
|
||||
// ExtrusionPathContoured implementation
|
||||
ExtrusionEntity *ExtrusionPathContoured::clone() const {
|
||||
return new ExtrusionPathContoured(*this);
|
||||
}
|
||||
|
||||
ExtrusionEntity *ExtrusionPathContoured::clone_move() {
|
||||
return new ExtrusionPathContoured(std::move(*this));
|
||||
}
|
||||
|
||||
void ExtrusionPathContoured::simplify(double tolerance) {
|
||||
// Do not simplify contoured paths
|
||||
return;
|
||||
}
|
||||
|
||||
void ExtrusionPathContoured::simplify_by_fitting_arc(double tolerance) {
|
||||
// Do not simplify contoured paths
|
||||
return;
|
||||
}
|
||||
|
||||
void ExtrusionPathContoured::reverse() {
|
||||
this->polyline.reverse();
|
||||
std::reverse(this->z_diffs.begin(), this->z_diffs.end());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user