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

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,7 @@ private:
void prepare_infill();
void infill();
void ironing();
void contour_z();
void generate_support_material();
void estimate_curled_extrusions();
void simplify_extrusion_path();
@@ -642,14 +643,14 @@ struct FakeWipeTower
std::vector<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 +687,13 @@ struct FakeWipeTower
ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, lh);
path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner };
path.polyline = Polyline3(Polyline{{ minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner }});
paths.push_back({ path });
// We added the border, now add several parallel lines so we can detect an object that is fully inside the tower.
// For now, simply use fixed spacing of 3mm.
for (coord_t y=minCorner.y()+scale_(3.); 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);
}