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

@@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "Config.hpp"
#include "GCodeWriter.hpp"
#include "Polygon.hpp"
#include "PrintConfig.hpp"
#include "libslic3r.h"
@@ -22,10 +23,12 @@
#include "Time.hpp"
#include "GCode/ExtrusionProcessor.hpp"
#include <algorithm>
#include <cfloat>
#include <cmath>
#include <cstdlib>
#include <chrono>
#include <iostream>
#include <iterator>
#include <math.h>
#include <stdlib.h>
#include <string>
@@ -5521,12 +5524,12 @@ void GCode::set_extruders(const std::vector<unsigned int> &extruder_ids)
void GCode::set_origin(const Vec2d &pointf)
{
// if origin increases (goes towards right), last_pos decreases because it goes towards left
const Point translate(
const Point3 translate(
scale_(m_origin(0) - pointf(0)),
scale_(m_origin(1) - pointf(1))
);
m_last_pos += translate;
m_wipe.path.translate(translate);
m_wipe.path.translate(translate.to_point());
m_origin = pointf;
}
@@ -5603,11 +5606,15 @@ static std::unique_ptr<EdgeGrid::Grid> calculate_layer_edge_grid(const Layer& la
return out;
}
std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters, const Point* start_point)
std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref,
const std::string& description,
double speed,
const ExtrusionEntitiesPtr& region_perimeters,
const Point* start_point)
{
// get a copy; don't modify the orientation of the original loop object otherwise
// next copies (if any) would not detect the correct orientation
ExtrusionLoop loop = loop_ref;
bool is_hole = (loop.loop_role() & elrHole) == elrHole;
@@ -5682,13 +5689,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
const double nozzle_diam = nozzle_diameter;
// note: previous & next are inverted to extrude "in the opposite direction, and we are "rewinding"
Point previous_point = paths.front().polyline.points[1];
Point current_point = paths.front().polyline.points.front();
Point next_point = paths.back().polyline.points.back();
Point previous_point = Point(paths.front().polyline.points[1].x(), paths.front().polyline.points[1].y());
Point current_point = Point(paths.front().polyline.points.front().x(), paths.front().polyline.points.front().y());
Point next_point = Point(paths.back().polyline.points.back().x(), paths.back().polyline.points.back().y());
// can happen if seam_gap is null
if (next_point == current_point) {
next_point = paths.back().polyline.points[paths.back().polyline.points.size() - 2];
const Point3 &p3 = paths.back().polyline.points[paths.back().polyline.points.size() - 2];
next_point = Point(p3.x(), p3.y());
}
Point a = next_point; // second point
@@ -5735,7 +5743,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// inside the model
if(discoveredTouchingLines > 1){
// use extrude instead of travel_to_xy to trigger the unretract
ExtrusionPath fake_path_wipe(Polyline{pt, current_point}, paths.front());
ExtrusionPath fake_path_wipe(Polyline3(Points3{Point3(pt), Point3(current_point)}), paths.front());
fake_path_wipe.set_force_no_extrusion(true);
fake_path_wipe.mm3_per_mm = 0;
//fake_path_wipe.set_extrusion_role(erExternalPerimeter);
@@ -5771,7 +5779,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
if (!enable_seam_slope) {
for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
gcode += this->_extrude(*path, description, speed_for_path(*path));
// Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed
// Orca: Adaptive PA - dont adapt PA after the first multipath extrusion is completed
// as we have already set the PA value to the average flow over the totality of the path
// in the first extrude move
// TODO: testing is needed with slope seams and adaptive PA.
@@ -5829,10 +5837,12 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
for (ExtrusionPath &path : paths) {
//BBS: Don't need to save duplicated point into wipe path
if (!m_wipe.path.empty() && !path.empty() &&
m_wipe.path.last_point() == path.first_point())
m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end());
else
m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path
m_wipe.path.last_point() == Point(path.first_point().x(), path.first_point().y())) {
// Convert Points3 to Points
for (auto it = path.polyline.points.begin() + 1; it != path.polyline.points.end(); ++it)
m_wipe.path.append(Point(it->x(), it->y()));
} else
m_wipe.path.append(path.polyline.to_polyline()); // TODO: don't limit wipe to last path
}
}
@@ -5842,8 +5852,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// the side depends on the original winding order of the polygon (inwards for contours, outwards for holes)
//FIXME improve the algorithm in case the loop is tiny.
//FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query).
Point a = paths.front().polyline.points[1]; // second point
Point b = *(paths.back().polyline.points.end()-3); // second to last point
const Point3 &a3 = paths.front().polyline.points[1]; // second point
Point a = Point(a3.x(), a3.y());
const Point3 &b3 = *(paths.back().polyline.points.end()-3); // second to last point
Point b = Point(b3.x(), b3.y());
if (is_hole == loop.is_counter_clockwise()) {
// swap points
Point c = a; a = b; b = c;
@@ -5857,8 +5869,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// create the destination point along the first segment and rotate it
// we make sure we don't exceed the segment length because we don't know
// the rotation of the second segment so we might cross the object boundary
Vec2d p1 = paths.front().polyline.points.front().cast<double>();
Vec2d p2 = paths.front().polyline.points[1].cast<double>();
Vec2d p1 = paths.front().polyline.points.front().cast<double>().head<2>();
Vec2d p2 = paths.front().polyline.points[1].cast<double>().head<2>();
Vec2d v = p2 - p1;
double nd = scale_(EXTRUDER_CONFIG(nozzle_diameter));
double l2 = v.squaredNorm();
@@ -5870,19 +5882,20 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
if (nd * nd < l2)
pt = (p1 + threshold * v * (nd / sqrt(l2))).cast<coord_t>();
//Point pt = ((nd * nd >= l2) ? (p1+v*0.4): (p1 + 0.2 * v * (nd / sqrt(l2)))).cast<coord_t>();
pt.rotate(angle, paths.front().polyline.points.front());
const Point3 &center3 = paths.front().polyline.points.front();
pt.rotate(angle, Point(center3.x(), center3.y()));
// generate the travel move
gcode += m_writer.extrude_to_xy(this->point_to_gcode(pt), 0,"move inwards before travel",true);
gcode += m_writer.extrude_to_xy(this->point_to_gcode(pt), 0, "move inwards before travel", true);
}
return gcode;
}
std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed)
std::string GCode::extrude_multi_path(const ExtrusionMultiPath& multipath, const std::string& description, double speed)
{
// extrude along the path
std::string gcode;
//Orca: calculate multipath average mm3_per_mm value over the length of the path.
//This is used for adaptive PA
m_multi_flow_segment_path_pa_set = false; // always emit PA on the first path of the multi-path
@@ -5899,8 +5912,8 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string
if (total_multipath_length > 0.0)
m_multi_flow_segment_path_average_mm3_per_mm = weighted_sum_mm3_per_mm / total_multipath_length;
// Orca: end of multipath average mm3_per_mm value calculation
for (ExtrusionPath path : multipath.paths){
for (const ExtrusionPath &path : multipath.paths){
gcode += this->_extrude(path, description, speed);
// Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed
// as we have already set the PA value to the average flow over the totality of the path
@@ -5911,13 +5924,15 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string
// BBS
if (m_wipe.enable && FILAMENT_CONFIG(wipe)) {
m_wipe.path = Polyline();
for (ExtrusionPath &path : multipath.paths) {
for (const ExtrusionPath &path : multipath.paths) {
//BBS: Don't need to save duplicated point into wipe path
if (!m_wipe.path.empty() && !path.empty() &&
m_wipe.path.last_point() == path.first_point())
m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end());
else
m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path
m_wipe.path.last_point() == Point(path.first_point().x(), path.first_point().y())) {
// Convert Points3 to Points
for (auto it = path.polyline.points.begin() + 1; it != path.polyline.points.end(); ++it)
m_wipe.path.append(Point(it->x(), it->y()));
} else
m_wipe.path.append(path.polyline.to_polyline()); // TODO: don't limit wipe to last path
}
m_wipe.path.reverse();
}
@@ -5925,7 +5940,10 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string
return gcode;
}
std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters)
std::string GCode::extrude_entity(const ExtrusionEntity& entity,
const std::string& description,
double speed,
const ExtrusionEntitiesPtr& region_perimeters)
{
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(&entity))
return this->extrude_path(*path, description, speed);
@@ -5938,7 +5956,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
return "";
}
std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed)
std::string GCode::extrude_path(const ExtrusionPath& path, const std::string& description, double speed)
{
// Orca: Reset average multipath flow as this is a single line, single extrude volumetric speed path
m_multi_flow_segment_path_pa_set = false;
@@ -5946,17 +5964,17 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string description, dou
// description += ExtrusionEntity::role_to_string(path.role());
std::string gcode = this->_extrude(path, description, speed);
if (m_wipe.enable && FILAMENT_CONFIG(wipe)) {
m_wipe.path = path.polyline;
m_wipe.path = path.polyline.to_polyline();
if (is_tree(this->config().support_type) && (path.role() == erSupportMaterial || path.role() == erSupportMaterialInterface || path.role() == erSupportTransition)) {
if ((m_wipe.path.first_point() - m_wipe.path.last_point()).cast<double>().norm() > scale_(0.2)) {
double min_dist = scale_(0.2);
int i = 0;
for (; i < path.polyline.points.size(); i++) {
double dist = (path.polyline.points[i] - path.last_point()).cast<double>().norm();
double dist = (path.polyline.points[i] - path.last_point3()).cast<double>().norm();
if (dist < min_dist) min_dist = dist;
if (min_dist < scale_(0.2) && dist > min_dist) break;
}
m_wipe.path = Polyline(Points(path.polyline.points.begin() + i - 1, path.polyline.points.end()));
m_wipe.path = Polyline3(Points3(path.polyline.points.begin() + i - 1, path.polyline.points.end())).to_polyline();
}
} else
m_wipe.path.reverse();
@@ -5999,11 +6017,11 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
extrusions.emplace_back(ee);
if (! extrusions.empty()) {
m_config.apply(print.get_print_region(&region - &by_region.front()).config());
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
for (const ExtrusionEntity *fill : extrusions) {
auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
if (eec) {
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos.to_point()).entities)
gcode += this->extrude_entity(*ee, extrusion_name);
} else
gcode += this->extrude_entity(*fill, extrusion_name);
@@ -6034,7 +6052,7 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
if (extrusions.empty())
return gcode;
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
const double support_speed = m_config.support_speed.value;
const double support_interface_speed = m_config.get_abs_value("support_interface_speed");
@@ -6200,14 +6218,20 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Move to first point of extrusion path
// path is 2D. But in slope lift case, lift z is done in travel_to function.
// Add m_need_change_layer_lift_z when change_layer in case of no lift if m_last_pos is equal to path.first_point() by chance
if (!m_last_pos_defined || m_last_pos != path.first_point() || m_need_change_layer_lift_z || slope_need_z_travel) {
Point first_point = path.first_point();
if (!m_last_pos_defined || m_last_pos.to_point() != first_point || m_need_change_layer_lift_z || slope_need_z_travel) {
const bool _last_pos_undefined = !m_last_pos_defined;
gcode += this->travel_to(
path.first_point(),
path.role(),
"move to first " + description + " point",
sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio)
);
double z = DBL_MAX;
if (sloped != nullptr) {
z = get_sloped_z(sloped->slope_begin.z_ratio);
} else if (path.z_contoured && !path.polyline.lines().empty()) {
z = unscale_(path.polyline.lines().begin()->a.z()) + m_nominal_z;
}
gcode += this->travel_to(first_point, path.role(), "move to first " + description + " point", z);
m_need_change_layer_lift_z = false;
// Orca: ensure Z matches planned layer height
if (!slope_need_z_travel && (_last_pos_undefined || m_need_change_layer_lift_z)) {
const std::string z_sync_comment = _last_pos_undefined ?
@@ -6217,6 +6241,19 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
m_need_change_layer_lift_z = false;
}
if (path.z_contoured && !path.polyline.lines().empty()) {
double current_z = m_writer.get_position().z();
double first_z = unscale_(path.polyline.lines().begin()->a.z()) + m_nominal_z;
if (GCodeFormatter::quantize_xyzf(first_z) != GCodeFormatter::quantize_xyzf(current_z)) {
gcode += m_writer.travel_to_z(first_z, "set Z for contouring", true);
}
}
if (!path.z_contoured && sloped == nullptr) {
double current_z = m_writer.get_position().z();
if (GCodeFormatter::quantize_xyzf(current_z) != GCodeFormatter::quantize_xyzf(m_nominal_z)) {
gcode += this->writer().travel_to_z(m_nominal_z, "reset Z after contouring", true);
}
}
// if needed, write the gcode_label_objects_end then gcode_label_objects_start
// should be already done by travel_to, but just in case
@@ -6803,10 +6840,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
// BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode or we are doing sloped extrusion
// Attention: G2 and G3 is not supported in spiral_mode mode
if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode || sloped != nullptr) {
if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode || sloped != nullptr || path.z_contoured) {
double path_length = 0.;
double total_length = sloped == nullptr ? 0. : path.polyline.length() * SCALING_FACTOR;
for (const Line& line : path.polyline.lines()) {
double saved_z = m_writer.get_position().z();
for (const Line3& line : path.polyline.lines()) {
std::string tempDescription = description;
const double line_length = line.length() * SCALING_FACTOR;
if (line_length < EPSILON)
@@ -6821,16 +6860,35 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length);
}
}
if (sloped == nullptr) {
if (path.z_contoured) {
// ZAA: Z anti-aliased extrusion with variable Z per point
Vec2d dest2d = this->point_to_gcode(line.b.to_point());
coordf_t z_diff = unscale_(line.b.z());
double extrusion_ratio = 1;
if (path.role() != erIroning) {
extrusion_ratio = (path.height + z_diff) / path.height;
}
double e = dE * extrusion_ratio;
double z = m_nominal_z + z_diff;
if (z < 0.1) {
throw RuntimeError("GCode: very low z");
}
gcode += m_writer.extrude_to_xyz(Vec3d(dest2d.x(), dest2d.y(), z), e,
GCodeWriter::full_gcode_comment ? tempDescription : "");
} else if (sloped == nullptr) {
// Normal extrusion
gcode += m_writer.extrude_to_xy(
this->point_to_gcode(line.b),
this->point_to_gcode(line.b.to_point()),
dE,
GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion());
} else {
// Sloped extrusion
const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length);
Vec2d dest2d = this->point_to_gcode(line.b);
Vec2d dest2d = this->point_to_gcode(line.b.to_point());
Vec3d dest3d(dest2d(0), dest2d(1), get_sloped_z(z_ratio));
gcode += m_writer.extrude_to_xyz(
dest3d,
@@ -6849,7 +6907,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
size_t end_index = fitting_result[fitting_index].end_point_index;
for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) {
tempDescription = description;
const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]);
const Line line = Line(path.polyline.points[point_index - 1].to_point(), path.polyline.points[point_index].to_point());
const double line_length = line.length() * SCALING_FACTOR;
if (line_length < EPSILON)
continue;
@@ -6907,14 +6965,14 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
double total_length = 0;
if (sloped != nullptr) {
// Calculate total extrusion length
Points p;
Points3 p;
p.reserve(new_points.size());
std::transform(new_points.begin(), new_points.end(), std::back_inserter(p), [](const ProcessedPoint& pp) { return pp.p; });
Polyline l(p);
Polyline3 l(p);
total_length = l.length() * SCALING_FACTOR;
}
gcode += m_writer.set_speed(last_set_speed, "", comment);
Vec2d prev = this->point_to_gcode_quantized(new_points[0].p);
Vec3d prev = this->point_to_gcode_quantized(new_points[0].p);
bool pre_fan_enabled = false;
bool cur_fan_enabled = false;
if( m_enable_cooling_markers && enable_overhang_bridge_fan)
@@ -6928,7 +6986,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
std::string tempDescription = description;
const ProcessedPoint &processed_point = new_points[i];
const ProcessedPoint &pre_processed_point = new_points[i-1];
Vec2d p = this->point_to_gcode_quantized(processed_point.p);
Vec3d p = this->point_to_gcode_quantized(processed_point.p);
if (m_enable_cooling_markers) {
if (enable_overhang_bridge_fan) {
cur_fan_enabled = check_overhang_fan(processed_point.overlap, path.role());
@@ -7010,9 +7068,26 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length);
}
}
if (sloped == nullptr) {
if (path.z_contoured) {
Vec2d dest2d = p.head<2>();
coordf_t z_diff = unscale_(processed_point.p.z());
double extrusion_ratio = 1;
if (path.role() != erIroning) {
extrusion_ratio = (path.height + z_diff) / path.height;
}
double e = dE * extrusion_ratio;
double z = m_nominal_z + z_diff;
if (z < 0.1) {
throw RuntimeError("GCode: very low z");
}
gcode += m_writer.extrude_to_xyz(Vec3d(dest2d.x(), dest2d.y(), z), e,
GCodeWriter::full_gcode_comment ? tempDescription : "");
} else if (sloped == nullptr) {
// Normal extrusion
gcode += m_writer.extrude_to_xy(p, dE, GCodeWriter::full_gcode_comment ? tempDescription : "");
gcode += m_writer.extrude_to_xy(p.head<2>(), dE, GCodeWriter::full_gcode_comment ? tempDescription : "");
} else {
// Sloped extrusion
const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length);
@@ -7893,6 +7968,13 @@ Vec2d GCode::point_to_gcode(const Point &point) const
return unscale(point) + m_origin - extruder_offset;
}
Vec3d GCode::point_to_gcode(const Point3& point) const
{
Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset);
Vec2d xy = unscale(point.to_point()) + m_origin - extruder_offset;
return Vec3d(xy.x(), xy.y(), unscale_(point.z()));
}
// convert a model-space scaled point into G-code coordinates
Point GCode::gcode_to_point(const Vec2d &point) const
{
@@ -7910,6 +7992,11 @@ Vec2d GCode::point_to_gcode_quantized(const Point& point) const
return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) };
}
Vec3d GCode::point_to_gcode_quantized(const Point3& point) const
{
Vec3d p = this->point_to_gcode(point);
return {GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()), GCodeFormatter::quantize_xyzf(p.z())};
}
// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed
// during infill/perimeter wiping, or normally (depends on wiping_entities parameter)