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:
Matthias Nott
2026-02-09 20:38:46 +01:00
parent cae1567726
commit 963f8d86b7
57 changed files with 1817 additions and 204 deletions

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

@@ -0,0 +1,333 @@
#include "Exception.hpp"
#include "ExtrusionEntity.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "Layer.hpp"
#include "Point.hpp"
#include "Print.hpp"
#include "SLA/IndexedMesh.hpp"
#include "libslic3r.h"
#include <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 lowest_z_within_distance(const Vec3d &normal, double dist) {
// const Vec3d p(0.0, 0.0, 0.0);
// Eigen::Vector3d n_unit = normal.normalized();
// Eigen::Vector3d z_hat(0.0, 0.0, 1.0);
// // Project the negative z-direction into the tangent plane
// Eigen::Vector3d v_dir = -z_hat + (z_hat.dot(n_unit)) * n_unit;
// double norm_v = v_dir.norm();
// if (norm_v == 0.0) {
// // Surface is horizontal, cannot go lower in z within tangent plane
// return p.z();
// }
// Eigen::Vector3d v = dist * v_dir / norm_v;
// Eigen::Vector3d q = p + v;
// return q.z();
// }
static double follow_slope_down(double angle_rad, double dist) {
return -dist * std::sin(angle_rad);
}
static double slope_from_normal(const Eigen::Vector3d& normal) {
// Ensure the normal is normalized
Eigen::Vector3d n = normal.normalized();
// Compute angle between normal and z-axis
double angle_rad = std::acos(std::abs(n.z())); // angle between normal and vertical
return angle_rad;
// calculate fall over dist
// double dist = 0.2;
// double z_dist = lowest_z(angle_rad, dist);
// printf("fall %f vs %f\n", z_dist, lowest_z_within_distance(normal, dist));
// double angle_deg = angle_rad * 180.0 / M_PI;
// return angle_deg;
}
// const int LINE = 180;
static bool contour_extrusion_path(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionPath &path) {
if (region->region().config().zaa_region_disable) {
return false;
}
if (path.role() != erTopSolidInfill && path.role() != erIroning && path.role() != erExternalPerimeter && path.role() != erPerimeter) {
return false;
}
Layer *layer = region->layer();
coordf_t mesh_z = layer->print_z + mesh.ground_level();
coordf_t min_z = layer->object()->config().zaa_min_z;
const Points3 &points = path.polyline.points;
double resolution_mm = 0.1;
coordf_t height = layer->height;
// std::cout << "LAYER " << (layer->id()+1) << std::endl;
// std::cout << "PRINT Z " << layer->print_z << std::endl;
// std::cout << "LAYER HEIGHT " << height << std::endl;
// std::cout << "EXTRUSION HEIGHT " << path.height << std::endl;
// std::cout << "EXTRUSION WIDTH " << path.width << std::endl;
// std::cout << "EXTRUSION ROLE: " << ExtrusionEntity::role_to_string(path.role()) << std::endl;
// std::cout << "FIRST POINT: " << path.polyline.first_point() << std::endl;
double minimize_perimeter_height_angle = region->region().config().zaa_minimize_perimeter_height;
Pointf3s contoured_points;
bool was_contoured = false;
// bool is_perimeter = path.role() == erExternalPerimeter || path.role() == erPerimeter || path.role() == erOverhangPerimeter;
for (Points3::const_iterator it = points.begin(); it != points.end()-1; ++it) {
Vec2d p1d(unscale_(it->x()), unscale_(it->y()));
Vec2d p2d(unscale_((it+1)->x()), unscale_((it+1)->y()));
Linef line(p1d, p2d);
double length_mm = line.length();
int num_segments = int(std::ceil(length_mm / resolution_mm));
Vec2d delta = line.vector();
for (int i = 0; i < num_segments+1; i++) {
Vec2d p = p1d + delta*i/num_segments;
coordf_t x = p.x();
coordf_t y = p.y();
sla::IndexedMesh::hit_result hit_up = mesh.query_ray_hit({x, y, mesh_z}, {0.0, 0.0, 1.0});
sla::IndexedMesh::hit_result hit_down = mesh.query_ray_hit({x, y, mesh_z}, {0.0, 0.0, -1.0});
double up = hit_up.distance();
double down = hit_down.distance();
double d = up < down ? up : -down;
const Vec3d &normal = (up < down ? hit_up : hit_down).normal();
double max_up = min_z;
double min_down = -(height - min_z);
double half_width = path.width / 2.0;
if (path.role() == erIroning) {
max_up = height;
min_down = -(height + 0.1);
}
double slope_rad = slope_from_normal(normal);
double slope_degrees = slope_rad * 180.0 / M_PI;
if (d > min_down && minimize_perimeter_height_angle > 0 && minimize_perimeter_height_angle < slope_degrees && path.role() == erExternalPerimeter) {
double adjustment = follow_slope_down(slope_rad, half_width);
if (adjustment > 0) {
throw RuntimeError("ContourZ: got positive adjustment");
}
d += adjustment;
if (d < min_down) {
d = min_down;
}
}
if (d > max_up + 0.03 || d < min_down) {
d = 0;
} else {
if (d > max_up) {
d = max_up;
}
}
if (path.role() == erExternalPerimeter && d > 0) {
// do not increase height of external perimeters as this may create an appearance of a seam
d = 0;
}
if (std::abs(d) > EPSILON) {
was_contoured = true;
}
Vec3d new_point = {p.x(), p.y(), d};
if (contoured_points.size() > 2) {
double dist = Linef3::distance_to_infinite_squared(
contoured_points[contoured_points.size() - 2],
contoured_points[contoured_points.size() - 1],
new_point);
if (dist < EPSILON) {
contoured_points[contoured_points.size() - 1] = new_point;
continue;
}
}
contoured_points.push_back(new_point);
}
}
if (!was_contoured) {
return false;
}
Polyline3 polyline;
for (const Vec3d &point : contoured_points) {
polyline.append(Point3(scale_(point.x()), scale_(point.y()), scale_(point.z())));
}
path.polyline = std::move(polyline);
path.z_contoured = true;
return true;
}
static void contour_extrusion_multipath(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionMultiPath &multipath)
{
for (ExtrusionPath &path : multipath.paths) {
contour_extrusion_path(region, mesh, path);
}
}
static void contour_extrusion_loop(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionLoop &loop)
{
for (ExtrusionPath &path : loop.paths) {
contour_extrusion_path(region, mesh, path);
}
}
static void contour_extrusion_entitiy_collection(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntityCollection &collection) {
for (ExtrusionEntity *entity : collection.entities) {
contour_extrusion_entity(region, mesh, entity);
}
}
static void contour_extrusion_entity(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntity *extr) {
const ExtrusionPathSloped *sloped = dynamic_cast<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) {
// printf("handling extrusion collection %p %p\n", &collection, extr);
if (!contains(roles, extr->role())) {
continue;
}
contour_extrusion_entity(region, mesh, extr);
}
}
// static void find_point(ExtrusionPath &path, const std::string &path_info) {
// Points3 &points = path.polyline.points;
// size_t i = 0;
// for (Points3::const_iterator it = points.begin(); it != points.end()-1; ++it) {
// if (it->x() == -883971 && it->y() == 979001) {
// std::cout << "FOUND POINT " << ExtrusionEntity::role_to_string(path.role()) << " at path " << path_info << "[" + std::to_string(i) + "]" << std::endl;
// }
// i++;
// }
// }
// static void find_point(ExtrusionLoop &loop, const std::string &path_info) {
// size_t i = 0;
// for (ExtrusionPath &path : loop.paths) {
// find_point(path, path_info + "[" + std::to_string(i) + "]");
// i++;
// }
// }
// static void find_point(ExtrusionEntity &extr, const std::string &path);
// static void find_point(ExtrusionEntityCollection &collection, const std::string &path) {
// size_t i = 0;
// for (ExtrusionEntity *extr : collection.entities) {
// find_point(*extr, path + "[" + std::to_string(i) + "]");
// i++;
// }
// }
// static void find_point(ExtrusionEntity &extr, const std::string &path_info) {
// const ExtrusionPathSloped *sloped = dynamic_cast<const ExtrusionPathSloped*>(&extr);
// if (sloped != nullptr) {
// throw RuntimeError("ExtrusionPathSloped not implemented");
// return;
// }
// ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(&extr);
// if (path != nullptr) {
// find_point(*path, path_info + " as ExtrusionPath " + ExtrusionEntity::role_to_string(extr.role()));
// return;
// }
// ExtrusionLoop *loop = dynamic_cast<ExtrusionLoop*>(&extr);
// if (loop != nullptr) {
// find_point(*loop, path_info + " as ExtrusionLoop " + ExtrusionEntity::role_to_string(extr.role()));
// 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) {
// find_point(*collection, path_info + " as ExtrusionEntityCollection " + ExtrusionEntity::role_to_string(extr.role()));
// return;
// }
// throw RuntimeError("ContourZ: ExtrusionEntity type not implemented");
// return;
// }
void Layer::make_contour_z(const sla::IndexedMesh &mesh)
{
// printf("make_contour_z() called\n");
for (LayerRegion *region : this->regions()) {
// printf("processing layer region %p\n", region);
// find_point(region->fills, "fills");
// find_point(region->perimeters, "perimeters");
handle_extrusion_collection(region, mesh, region->fills, {erTopSolidInfill, erIroning, erExternalPerimeter, erMixed});
handle_extrusion_collection(region, mesh, region->perimeters, {erExternalPerimeter, erMixed});
}
}
} // namespace Slic3r