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

@@ -2148,6 +2148,17 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
}
// Z-Contouring
for (PrintObject *obj : m_objects) {
bool need_contouring = need_slicing_objects.count(obj) != 0 && obj->config().zaa_enabled;
if (need_contouring) {
obj->contour_z();
} else {
if (obj->set_started(posContouring))
obj->set_done(posContouring);
}
}
tbb::parallel_for(tbb::blocked_range<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++) {
@@ -2186,6 +2197,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
obj->set_done(posInfill);
if (obj->set_started(posIroning))
obj->set_done(posIroning);
if (obj->set_started(posContouring))
obj->set_done(posContouring);
if (obj->set_started(posSupportMaterial))
obj->set_done(posSupportMaterial);
if (obj->set_started(posDetectOverhangsForLift))
@@ -2565,7 +2578,7 @@ void Print::_make_skirt()
flow.width(),
(float)initial_layer_print_height // this will be overridden at G-code export time
)));
eloop.paths.back().polyline = loop.split_at_first_point();
eloop.paths.back().polyline = Polyline3(loop.split_at_first_point());
m_skirt.append(eloop);
if (m_config.min_skirt_length.value > 0) {
// The skirt length is limited. Sum the total amount of filament length extruded, in mm.
@@ -2623,7 +2636,7 @@ void Print::_make_skirt()
flow.width(),
(float)initial_layer_print_height // this will be overridden at G-code export time
)));
eloop.paths.back().polyline = loop.split_at_first_point();
eloop.paths.back().polyline = Polyline3(loop.split_at_first_point());
object->m_skirt.append(std::move(eloop));
if (m_config.min_skirt_length.value > 0) {
// The skirt length is limited. Sum the total amount of filament length extruded, in mm.
@@ -4018,7 +4031,8 @@ static void from_json(const json& j, Polyline& poly_line) {
}
static void from_json(const json& j, ExtrusionPath& extrusion_path) {
extrusion_path.polyline = j[JSON_EXTRUSION_POLYLINE];
Polyline temp_polyline = j[JSON_EXTRUSION_POLYLINE];
extrusion_path.polyline = Polyline3(temp_polyline);
extrusion_path.mm3_per_mm = j[JSON_EXTRUSION_MM3_PER_MM];
extrusion_path.width = j[JSON_EXTRUSION_WIDTH];
extrusion_path.height = j[JSON_EXTRUSION_HEIGHT];
@@ -4852,8 +4866,9 @@ ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const
paths.reserve(it->second.size());
for (auto &polyline : it->second) {
ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, layer_heights[index]);
path.polyline = polyline;
for (auto &p : path.polyline.points) p += trans;
path.polyline = Polyline3(polyline);
Point3 trans3(trans, 0);
for (auto &p : path.polyline.points) p += trans3;
paths.push_back(path);
}
el.paths = std::move(paths);