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

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