Belt printer: retire the redundant and unused options

Removed, with the keys added to handle_legacy()'s ignore list so saved
profiles and 3MFs keep loading:

- belt_slice_rotation_global and preslice_remap_global. Both were only
  consulted when belt_preslice_global ("Global mesh transforms") was off,
  which no profile does; belt_preslice_global is now the single global
  mode and is presumed on everywhere the old flags were ORed in
  (PrintObjectSlice, BeltBackTransform, BeltGCode, Print::process,
  PrintApply). The Belt tilt row is axis + angle only.
- preslice_remap_x/y/z. No profile used the pre-slice axis remap; the belt
  tilt axis plus the G-code axis remap cover the machines that exist, and
  its implementation only agreed with itself for a plain swap (matrix
  columns vs remap_bbox rows). BeltTransformPipeline::build_preslice_remap,
  remap_bbox and has_preslice_remap are gone, the forward transform is the
  rotation, and the G-code header no longer carries the remap.
- belt_support_z_offset_mode. Saved and invalidated steps, but no support
  generator read it.
- first_layer_plane and first_layer_plane_offset, with FirstLayerPlane.cpp.
  On every shipped configuration the band is measured from the belt
  surface (GCode::belt_height_above_floor) and the evaluator was only
  reached for an explicit XY/YZ/XZ choice or a non-zero offset, which
  nobody set. first_layer_plane_thickness stays as the band unit,
  relabelled "First layer band thickness".

UI: the Machine frame transforms group is five single-option rows (G-code
remap X / Y / Z, Decouple machine-frame tilt, Machine-frame tilt angle;
the angle row is shown only when decoupled) instead of two multi-column
lines, and the remap fields carry full labels.

Also carries the phong.fs struct fix from #16226 so the worktree build
links its shaders.

libslic3r_tests and fff_print_tests pass; clang-tidy diff check clean;
orca_profile_tool.py check clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
harrierpigeon
2026-10-06 18:14:34 -05:00
co-authored by Claude Fable 5.1
parent 199758f67f
commit 8039d4d2ac
32 changed files with 125 additions and 964 deletions
@@ -88,7 +88,6 @@
"belt_printer": "1",
"belt_slice_rotation": "x",
"belt_slice_rotation_angle": "45",
"belt_slice_rotation_global": "1",
"build_plate_tilt_x": "45",
"purge_in_prime_tower": "0",
"scan_first_layer": "0",
@@ -91,7 +91,6 @@
"belt_printer": "1",
"belt_slice_rotation": "x",
"belt_slice_rotation_angle": "45",
"belt_slice_rotation_global": "1",
"build_plate_tilt_x": "45",
"purge_in_prime_tower": "0",
"scan_first_layer": "0",
@@ -91,7 +91,6 @@
"belt_printer": "1",
"belt_slice_rotation": "x",
"belt_slice_rotation_angle": "45",
"belt_slice_rotation_global": "1",
"build_plate_tilt_x": "45",
"purge_in_prime_tower": "0",
"scan_first_layer": "0",
+1
View File
@@ -41,6 +41,7 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform vec4 uniform_color;
+1
View File
@@ -44,6 +44,7 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform vec4 uniform_color;
+6 -25
View File
@@ -25,12 +25,6 @@ void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print)
// for the physical tilt the G-code viewer uses to enable belt view.
file.write_format("; belt_slice_rotation = %s\n", full_cfg.opt_serialize("belt_slice_rotation").c_str());
file.write_format("; belt_slice_rotation_angle = %.1f\n", print.config().belt_slice_rotation_angle.value);
file.write_format("; belt_slice_rotation_global = %d\n", print.config().belt_slice_rotation_global.value ? 1 : 0);
// Pre-slice remap configs
file.write_format("; preslice_remap_x = %s\n", full_cfg.opt_serialize("preslice_remap_x").c_str());
file.write_format("; preslice_remap_y = %s\n", full_cfg.opt_serialize("preslice_remap_y").c_str());
file.write_format("; preslice_remap_z = %s\n", full_cfg.opt_serialize("preslice_remap_z").c_str());
file.write_format("; preslice_remap_global = %d\n", print.config().preslice_remap_global.value ? 1 : 0);
file.write_format("; belt_preslice_global = %d\n", print.config().belt_preslice_global.value ? 1 : 0);
// Machine-frame transform: shear (tan) + scale (1/cos) derived from the belt
// tilt angle (or belt_frame_tilt_angle when decoupled).
@@ -40,25 +34,12 @@ void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print)
void BeltGCode::on_set_origin(const PrintObject * /*obj*/, const Point & /*inst_shift*/)
{
// Global pre-slice mode: adjust origin using computed correction.
// Transform the origin through the belt pipeline so that
// back_transform(T * origin) = origin (correct machine position).
//
// Flags that trigger this path:
// belt_preslice_global — full pipeline (rotation * remap) is global
// preslice_remap_global — only the pre-slice remap is global
// belt_slice_rotation_global — slicing rotation treated as global (matches
// the per-instance Z-offset added in PrintObjectSlice.cpp)
// The XY origin adjustment uses the FULL forward transform, because the
// back_transform applied during G-code emission is always the inverse of
// the full pipeline.
bool use_global = m_config.belt_preslice_global.value
|| (m_config.preslice_remap_global.value
&& BeltTransformPipeline::has_preslice_remap(m_config))
|| (m_config.belt_slice_rotation_global.value
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None
&& std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON);
if (!use_global)
// Global pre-slice mode (matches the per-instance Z-offset added in
// PrintObjectSlice.cpp): transform the origin through the belt pipeline so
// that back_transform(T * origin) = origin (correct machine position). The
// back_transform applied during G-code emission is the inverse of the
// forward transform.
if (!m_config.belt_preslice_global.value)
return;
// Adjust origin: transform through belt forward pipeline so that
+3 -8
View File
@@ -14,12 +14,7 @@ void BeltSliceStrategy::apply_preslice_transforms(Transform3d &trafo,
const ModelVolumePtrs &model_volumes,
double *out_belt_min_z)
{
// 1. Standalone pre-slice axis remap (works without belt mode).
const bool has_remap = BeltTransformPipeline::has_preslice_remap(config);
if (has_remap)
trafo = BeltTransformPipeline::build_preslice_remap(config) * trafo;
// 2. Belt rotation — the sole mesh-side belt transform (matching
// 1. Belt rotation — the sole mesh-side belt transform (matching
// BeltTransformPipeline::build_forward_transform). Only active in
// belt-printer mode.
bool has_rotation = false;
@@ -32,10 +27,10 @@ void BeltSliceStrategy::apply_preslice_transforms(Transform3d &trafo,
}
}
if (!has_remap && !has_rotation)
if (!has_rotation)
return;
// 3. Z-shift — detect if the mesh clips below the build plate after the
// 2. Z-shift — detect if the mesh clips below the build plate after the
// transforms and lift it. Each mesh vertex must be brought into object space
// via mv->get_matrix() before applying the full trafo (which is in object
// space). Missing this on assemblies (where per-volume get_matrix() positions
+7 -90
View File
@@ -16,50 +16,6 @@ namespace Slic3r {
// ---- Matrix builders ------------------------------------------------------
Transform3d BeltTransformPipeline::build_preslice_remap(const PrintConfig &config)
{
Transform3d pre_remap = Transform3d::Identity();
if (!has_preslice_remap(config))
return pre_remap;
int pre_rx = int(config.preslice_remap_x.value);
int pre_ry = int(config.preslice_remap_y.value);
int pre_rz = int(config.preslice_remap_z.value);
// Each remap value selects a source axis and sign.
auto remap_column = [](int r) -> Vec3d {
int axis = r % 3;
Vec3d col = Vec3d::Zero();
if (r < 3) col[axis] = 1.0; // +axis
else if (r < 6) col[axis] = -1.0; // -axis
else col[axis] = -1.0; // Rev: max - pos = -(pos - max)
return col;
};
Matrix3d remap_lin;
remap_lin.col(0) = remap_column(pre_rx);
remap_lin.col(1) = remap_column(pre_ry);
remap_lin.col(2) = remap_column(pre_rz);
pre_remap.linear() = remap_lin;
// Translation for Rev modes (needs build volume extents).
if (pre_rx >= 6 || pre_ry >= 6 || pre_rz >= 6) {
BoundingBoxf bbox_bed(config.printable_area.values);
Vec3d vol_max(bbox_bed.max.x(), bbox_bed.max.y(),
config.printable_height.value);
Vec3d remap_trans = Vec3d::Zero();
auto add_rev = [&](int r, int out) {
if (r >= 6) remap_trans[out] = vol_max[r % 3];
};
add_rev(pre_rx, 0);
add_rev(pre_ry, 1);
add_rev(pre_rz, 2);
pre_remap.translation() = remap_trans;
}
return pre_remap;
}
Matrix3d BeltTransformPipeline::build_rotation_matrix(const PrintConfig &config, bool *has_rot_out)
{
BeltRotationAxis axis = config.belt_slice_rotation.value;
@@ -81,52 +37,13 @@ Matrix3d BeltTransformPipeline::build_rotation_matrix(const PrintConfig &config,
Transform3d BeltTransformPipeline::build_forward_transform(const PrintConfig &config)
{
// Mesh-side belt transform: rotation applied after the pre-slice axis remap.
// (Shear & scale are a g-code-side stage, not part of the mesh transform.)
Transform3d pre_remap = build_preslice_remap(config);
Matrix3d rot = build_rotation_matrix(config);
// Mesh-side belt transform: the rotation. (Shear & scale are a g-code-side
// stage, not part of the mesh transform.)
Transform3d combined = Transform3d::Identity();
combined.linear() = rot;
combined = combined * pre_remap;
combined.linear() = build_rotation_matrix(config);
return combined;
}
// ---- Bounding box remap ---------------------------------------------------
BoundingBoxf3 BeltTransformPipeline::remap_bbox(const BoundingBoxf3 &bb, const PrintConfig &config)
{
if (!has_preslice_remap(config))
return bb; // Identity remap, or belt mode off.
int pre_rx = int(config.preslice_remap_x.value);
int pre_ry = int(config.preslice_remap_y.value);
int pre_rz = int(config.preslice_remap_z.value);
auto remap_coord = [](int r, const Vec3d &v) -> double {
int axis = r % 3;
if (r < 3) return v[axis];
return -v[axis];
};
Vec3d mn = bb.min.cast<double>(), mx = bb.max.cast<double>();
BoundingBoxf3 rbb;
for (int i = 0; i < 8; ++i) {
Vec3d c((i & 1) ? mx.x() : mn.x(),
(i & 2) ? mx.y() : mn.y(),
(i & 4) ? mx.z() : mn.z());
Vec3d rc(remap_coord(pre_rx, c), remap_coord(pre_ry, c), remap_coord(pre_rz, c));
if (i == 0) rbb = BoundingBoxf3(rc, rc);
else rbb.merge(rc);
}
return rbb;
}
BoundingBoxf3 BeltTransformPipeline::remap_bbox(const ModelObject &model_object, const PrintConfig &config)
{
return remap_bbox(model_object.raw_bounding_box(), config);
}
// ---- Belt floor parameters ------------------------------------------------
// Shared implementation for both PrintConfig and DynamicPrintConfig.
@@ -216,15 +133,15 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
} // anonymous namespace
BeltTransformPipeline::BeltHeightResult BeltTransformPipeline::compute_belt_height_and_floor(
const PrintConfig &config, const BoundingBoxf3 &remapped_bbox, double original_height)
const PrintConfig &config, const BoundingBoxf3 &bbox, double original_height)
{
return compute_belt_height_and_floor_impl(config, remapped_bbox, original_height);
return compute_belt_height_and_floor_impl(config, bbox, original_height);
}
BeltTransformPipeline::BeltHeightResult BeltTransformPipeline::compute_belt_height_and_floor(
const DynamicPrintConfig &config, const BoundingBoxf3 &remapped_bbox, double original_height)
const DynamicPrintConfig &config, const BoundingBoxf3 &bbox, double original_height)
{
return compute_belt_height_and_floor_impl(config, remapped_bbox, original_height);
return compute_belt_height_and_floor_impl(config, bbox, original_height);
}
} // namespace Slic3r
+10 -48
View File
@@ -16,7 +16,7 @@ class ModelObject;
// Shared belt-printer transform math.
//
// The pre-slice pipeline applied in PrintObjectSlice.cpp is:
// trafo_out = z_shift * rotation * pre_remap * trafo_in
// trafo_out = z_shift * rotation * trafo_in
//
// Rotation is the sole mesh-side belt transform; shear & scale are applied
// to the g-code instead (see MachineFrameTransform). This class provides the
@@ -47,39 +47,11 @@ class BeltTransformPipeline
public:
// ---- Identity checks --------------------------------------------------
// Whether the axis remaps (preslice_remap_* and gcode_remap_*) apply at all.
// The remap fields are only offered in the belt printer group, so a value
// left in a profile must not change a non-belt print: with belt mode off every
// belt-only key is a no-op. This is the one place to widen if a non-belt use
// ever needs them.
// Whether the G-code axis remap applies at all. The remap fields are only
// offered in the belt printer group, so a value left in a profile must not
// change a non-belt print: with belt mode off every belt-only key is a no-op.
// This is the one place to widen if a non-belt use ever needs them.
static bool axis_remap_enabled(const PrintConfig &config) { return config.belt_printer.value; }
static bool axis_remap_enabled(const DynamicPrintConfig &config)
{
auto *opt = config.option<ConfigOptionBool>("belt_printer");
return opt != nullptr && opt->value;
}
static bool has_preslice_remap(const PrintConfig &config)
{
return axis_remap_enabled(config) &&
(int(config.preslice_remap_x.value) != int(RemapAxis::PosX) ||
int(config.preslice_remap_y.value) != int(RemapAxis::PosY) ||
int(config.preslice_remap_z.value) != int(RemapAxis::PosZ));
}
// Overload accepting DynamicPrintConfig (used in static slicing_parameters).
static bool has_preslice_remap(const DynamicPrintConfig &config)
{
if (! axis_remap_enabled(config))
return false;
auto get_int = [&](const char *key) -> int {
auto *opt = config.option<ConfigOptionEnum<RemapAxis>>(key);
return opt ? int(opt->value) : 0;
};
return get_int("preslice_remap_x") != int(RemapAxis::PosX) ||
get_int("preslice_remap_y") != int(RemapAxis::PosY) ||
get_int("preslice_remap_z") != int(RemapAxis::PosZ);
}
static bool has_rotation(const PrintConfig &config)
{
@@ -118,26 +90,16 @@ public:
// ---- Matrix builders --------------------------------------------------
// Build the pre-slice axis remap transform (includes Rev-mode translation).
static Transform3d build_preslice_remap(const PrintConfig &config);
// Build the 3x3 rotation matrix from belt_slice_rotation* config.
// Returns Identity if rotation axis is None or angle is ~0.
// Also sets has_rot_out if non-null.
static Matrix3d build_rotation_matrix(const PrintConfig &config, bool *has_rot_out = nullptr);
// Combined forward transform (rotation * pre_remap) — the mesh-side belt
// transform that BeltSliceStrategy applies and BeltBackTransform inverts.
// Forward transform (the rotation) — the mesh-side belt transform that
// BeltSliceStrategy applies and BeltBackTransform inverts.
// Does NOT include the per-object Z-shift.
static Transform3d build_forward_transform(const PrintConfig &config);
// ---- Bounding box remap -----------------------------------------------
// Remap a bounding box through the pre-slice axis remap.
// Returns the original bbox if remap is identity.
static BoundingBoxf3 remap_bbox(const BoundingBoxf3 &bb, const PrintConfig &config);
static BoundingBoxf3 remap_bbox(const ModelObject &model_object, const PrintConfig &config);
// ---- Belt floor parameters --------------------------------------------
struct BeltFloorParams {
@@ -153,15 +115,15 @@ public:
};
// Compute effective object height and belt floor parameters from config
// and pre-remapped bounding box. original_height is the input height
// and the object's bounding box. original_height is the input height
// (bb.size().z() or model_object.max_z()).
static BeltHeightResult compute_belt_height_and_floor(
const PrintConfig &config, const BoundingBoxf3 &remapped_bbox,
const PrintConfig &config, const BoundingBoxf3 &bbox,
double original_height);
// Overload for DynamicPrintConfig (used by static slicing_parameters).
static BeltHeightResult compute_belt_height_and_floor(
const DynamicPrintConfig &config, const BoundingBoxf3 &remapped_bbox,
const DynamicPrintConfig &config, const BoundingBoxf3 &bbox,
double original_height);
};
-2
View File
@@ -94,8 +94,6 @@ set(lisbslic3r_sources
BeltSliceStrategy.hpp
BeltTransform.cpp
BeltTransform.hpp
FirstLayerPlane.cpp
FirstLayerPlane.hpp
Brim.cpp
BrimEarsPoint.hpp
Brim.hpp
-229
View File
@@ -1,229 +0,0 @@
#include "FirstLayerPlane.hpp"
#include "BeltTransform.hpp"
#include "BoundingBox.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "libslic3r.h"
#include <algorithm>
#include <climits>
#include <cmath>
namespace Slic3r {
namespace {
// Build the row of the gcode-axis-remap matrix R that produces machine_Z,
// AS A FUNCTION OF a slicing-frame point in the GCode generator's coordinate
// space. Without back-transform this is just R.row(2). With back-transform
// the writer applies F^-1 before R, so the effective row is (R * F^-1).row(2).
//
// Returns a pair (gradient, constant) such that:
// machine_Z(p_slicing) = gradient.dot(p_slicing) + constant
struct MachineZAffine {
Vec3d gradient = Vec3d::UnitZ();
double constant = 0.0;
};
MachineZAffine compute_machine_z_affine(const PrintConfig &config)
{
MachineZAffine out;
// R is the matrix form of GCodeWriter::apply_axis_remap. Each output axis
// i picks one slicing-frame component (with sign + optional Rev mode
// translation) based on m_remap_{x,y,z}. We only need row 2 (the z output)
// since machine_Z is what defines the first-layer plane.
int rz = int(config.gcode_remap_z.value);
int axis = rz % 3;
double sign;
double trans;
if (rz < int(RemapAxis::NegX)) { // 0..2 = PosX/Y/Z
sign = 1.0;
trans = 0.0;
} else if (rz < int(RemapAxis::RevX)) { // 3..5 = NegX/Y/Z
sign = -1.0;
trans = 0.0;
} else { // 6..8 = RevX/Y/Z
sign = -1.0;
BoundingBoxf bbox_bed(config.printable_area.values);
Vec3d vol_max(bbox_bed.max.x(),
bbox_bed.max.y(),
config.printable_height.value);
trans = vol_max[axis];
}
Vec3d r_row = Vec3d::Zero();
r_row[axis] = sign;
// Without back-transform, machine_Z(slicing) = r_row · slicing + trans.
out.gradient = r_row;
out.constant = trans;
if (config.gcode_back_transform.value && config.belt_printer.value) {
// BeltKinematics applies F^-1 before R when back-transform is on.
// So machine_Z(slicing) = r_row · (F^-1 · slicing) + trans
// = (r_row^T · F^-1) · slicing + trans
// We need to compose r_row with F^-1 from the LEFT (treating r_row as
// a row vector). Eigen makes this easy: it's just F^-1.transpose() * r_row.
Transform3d forward = BeltTransformPipeline::build_forward_transform(config);
Transform3d inverse = forward.inverse();
// Note: forward.translation() is normally zero (per-print transforms
// don't add a translation; the per-object z_shift is added separately
// in PrintObjectSlice). We still incorporate inverse.translation() in
// case a Rev-mode preslice_remap puts a translation in F.
Vec3d composed_grad = inverse.linear().transpose() * r_row;
double composed_trans =
r_row.dot(inverse.translation()) + trans;
out.gradient = composed_grad;
out.constant = composed_trans;
}
return out;
}
} // namespace
FirstLayerPlane::FirstLayerPlane(const PrintConfig &config)
{
// -------- Resolve Auto -------------------------------------------------
FirstLayerPlaneMode mode = config.first_layer_plane.value;
if (mode == FirstLayerPlaneMode::Auto) {
bool belt_affine_active = config.belt_printer.value &&
config.belt_slice_rotation.value != BeltRotationAxis::None &&
std::abs(config.belt_slice_rotation_angle.value) > EPSILON;
mode = belt_affine_active ? FirstLayerPlaneMode::BeltAffine
: FirstLayerPlaneMode::XY;
}
m_mode = mode;
// -------- Band thickness ----------------------------------------------
// Note: layer_height lives in PrintObjectConfig, not PrintConfig, so we
// can't fall back to it from here. initial_layer_print_height is in
// PrintConfig and is the right default anyway (the legacy first-layer
// semantics used initial_layer_print_height, not the regular one).
double thickness = config.first_layer_plane_thickness.value;
if (thickness <= 0.0)
thickness = config.initial_layer_print_height.value;
if (thickness <= 0.0)
thickness = 0.2;
m_thickness_mm = thickness;
const double user_offset = config.first_layer_plane_offset.value;
// -------- Build the plane ---------------------------------------------
auto set_axis_aligned = [&](const Vec3d &n_unit, double offset_along_n) {
m_normal = n_unit;
m_offset = offset_along_n;
};
switch (mode) {
case FirstLayerPlaneMode::XY:
// Legacy XY plane. Inactive: short-circuit to layer-index path.
set_axis_aligned(Vec3d::UnitZ(), user_offset);
m_active = false;
return;
case FirstLayerPlaneMode::YZ:
set_axis_aligned(Vec3d::UnitX(), user_offset);
m_active = true;
return;
case FirstLayerPlaneMode::XZ:
set_axis_aligned(Vec3d::UnitY(), user_offset);
m_active = true;
return;
case FirstLayerPlaneMode::BeltAffine: {
// Compute the slicing-frame plane that maps to machine_Z = user_offset
// under the gcode axis remap (and optional back-transform).
MachineZAffine mz = compute_machine_z_affine(config);
double cmag = mz.gradient.norm();
if (cmag < EPSILON) {
// Degenerate: slicing point doesn't affect machine_Z. Fall back.
set_axis_aligned(Vec3d::UnitZ(), user_offset);
m_active = false;
return;
}
// Plane equation: gradient · slicing = user_offset - constant
const double K = user_offset - mz.constant;
m_normal = mz.gradient / cmag;
m_offset = K / cmag;
m_active = true;
return;
}
case FirstLayerPlaneMode::Auto:
// Should have been resolved above.
m_active = false;
return;
}
m_active = false;
}
double FirstLayerPlane::distance_from_plane(const Vec3d &point_slicing_mm) const
{
return m_normal.dot(point_slicing_mm) - m_offset;
}
bool FirstLayerPlane::is_first_layer(const Vec3d &point_slicing_mm,
double first_layer_height_mm) const
{
if (!m_active)
return false;
return distance_from_plane(point_slicing_mm) < first_layer_height_mm;
}
int FirstLayerPlane::effective_layer_index(const Vec3d &point_slicing_mm) const
{
if (!m_active)
return INT_MAX / 2; // Effectively "way past first layer".
double d = distance_from_plane(point_slicing_mm);
if (d <= 0.0)
return 0;
return int(std::floor(d / m_thickness_mm));
}
int FirstLayerPlane::min_effective_index_for_xy_bbox(
const BoundingBoxf &xy_bbox_mm, double slicing_z_mm) const
{
if (!m_active)
return INT_MAX / 2;
// For the rectangular bbox in (x, y) at fixed z, the smallest value of
// (n.x*x + n.y*y + n.z*z - offset) is achieved at one of the four
// corners, with the smaller component picked when the corresponding
// normal coefficient is positive.
const double x_for_min = (m_normal.x() >= 0.0)
? xy_bbox_mm.min.x() : xy_bbox_mm.max.x();
const double y_for_min = (m_normal.y() >= 0.0)
? xy_bbox_mm.min.y() : xy_bbox_mm.max.y();
const double dmin = m_normal.x() * x_for_min
+ m_normal.y() * y_for_min
+ m_normal.z() * slicing_z_mm
- m_offset;
if (dmin <= 0.0)
return 0;
return int(std::floor(dmin / m_thickness_mm));
}
int FirstLayerPlane::min_effective_index_for_bbox3(
const BoundingBoxf3 &bbox_mm) const
{
if (!m_active)
return INT_MAX / 2;
const double x_for_min = (m_normal.x() >= 0.0)
? bbox_mm.min.x() : bbox_mm.max.x();
const double y_for_min = (m_normal.y() >= 0.0)
? bbox_mm.min.y() : bbox_mm.max.y();
const double z_for_min = (m_normal.z() >= 0.0)
? bbox_mm.min.z() : bbox_mm.max.z();
const double dmin = m_normal.x() * x_for_min
+ m_normal.y() * y_for_min
+ m_normal.z() * z_for_min
- m_offset;
if (dmin <= 0.0)
return 0;
return int(std::floor(dmin / m_thickness_mm));
}
} // namespace Slic3r
-76
View File
@@ -1,76 +0,0 @@
#ifndef slic3r_FirstLayerPlane_hpp_
#define slic3r_FirstLayerPlane_hpp_
#include "libslic3r.h"
#include "Point.hpp"
#include "BoundingBox.hpp"
#include "PrintConfig.hpp"
namespace Slic3r {
// Decides which extrusions get "first layer" treatment (no fan, slow speed,
// initial-layer accel/jerk, deferred temperature drop) by reference to a
// configurable plane in slicing-frame coordinates rather than the slicing
// layer index.
//
// On a normal flat-bed printer the plane is XY at slicing_Z = 0 and the
// evaluator is INACTIVE — every call site short-circuits back to the legacy
// `Layer::id() == 0` test. On a belt printer with a Z-from-Y shear the
// belt surface (machine_Z = 0) maps to a plane in slicing-frame coordinates
// derived from the gcode axis remap, so layer-index-based detection no
// longer matches the physical first printed surface.
//
// Plane representation: unit normal `n` (slicing frame) and offset along
// the normal such that the plane equation is `n · p == offset`. Signed
// perpendicular distance is `d(p) = n · p - offset`. Positive distance
// means "away from the belt surface", negative means "below the plane".
class FirstLayerPlane
{
public:
explicit FirstLayerPlane(const PrintConfig &config);
// Inactive when the legacy XY layer-index path should be used. This
// covers all non-belt printers and any belt printer where the user
// explicitly picked XY mode.
bool is_active() const { return m_active; }
FirstLayerPlaneMode effective_mode() const{ return m_mode; }
double band_thickness_mm() const { return m_thickness_mm; }
const Vec3d & normal() const { return m_normal; }
double plane_offset() const { return m_offset; }
// Signed perpendicular distance from a slicing-frame point to the plane.
double distance_from_plane(const Vec3d &point_slicing_mm) const;
// True if perpendicular distance < first_layer_height_mm. When the
// evaluator is inactive this returns false (call sites should fall back
// to the legacy per-layer path before reaching this function).
bool is_first_layer(const Vec3d &point_slicing_mm,
double first_layer_height_mm) const;
// floor((distance - 0) / band_thickness), clamped to [0, +inf). Used
// for "first N layers" thresholds (fan, slow_down_layers). Returns 0
// for points within the band. Returns INT_MAX/2 when inactive.
int effective_layer_index(const Vec3d &point_slicing_mm) const;
// Min effective index over a 2D bbox at a fixed slicing_Z. Used for
// layer-level decisions (e.g. temperature transition gate) where we
// don't want to walk every extrusion in the layer. For axis-aligned
// planes this is exact; for tilted planes it's a tight lower bound
// (the plane projection of the bbox's extreme corner).
int min_effective_index_for_xy_bbox(const BoundingBoxf &xy_bbox_mm,
double slicing_z_mm) const;
// Same as above but the bbox spans a Z range too.
int min_effective_index_for_bbox3(const BoundingBoxf3 &bbox_mm) const;
private:
bool m_active = false;
FirstLayerPlaneMode m_mode = FirstLayerPlaneMode::XY;
Vec3d m_normal = Vec3d::UnitZ(); // unit, slicing frame
double m_offset = 0.0; // n·p == m_offset
double m_thickness_mm = 0.0;
};
} // namespace Slic3r
#endif // slic3r_FirstLayerPlane_hpp_
+12 -60
View File
@@ -107,7 +107,6 @@
#include "calib.hpp"
#include "libslic3r_version.h"
#include "GCode/BeltKinematics.hpp"
#include "FirstLayerPlane.hpp"
// Intel redesigned some TBB interface considerably when merging TBB with their oneAPI set of libraries, see GH #7332.
// We are using quite an old TBB 2017 U7. Before we update our build servers, let's use the old API, which is deprecated in up to date TBB.
#if ! defined(TBB_VERSION_MAJOR)
@@ -3153,19 +3152,13 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
print.config().printable_height.value));
}
// Build the FirstLayerPlane evaluator. When inactive (non-belt printers
// and belt printers without Z shear), all per-path call sites short-
// circuit to the legacy Layer::id() == 0 path so g-code stays bit-
// identical to the pre-feature behavior.
m_first_layer_plane = std::make_unique<FirstLayerPlane>(print.config());
// Belt writers only: travel-speed selection becomes per-point (see
// GCodeWriter::uses_pointwise_travel_speed()), which must not change for
// non-belt printers. The writer gets the same test the extrusions use, so a
// travel is judged against the belt surface (belt_height_above_floor) exactly
// like the path it leads to, and not against the FirstLayerPlane, which
// misreports the height under a non-identity gcode_remap_*. Writer points
// carry the G-code origin and extruder offset that point_to_gcode() added;
// the belt surface is described in the object's own frame.
// like the path it leads to. Writer points carry the G-code origin and
// extruder offset that point_to_gcode() added; the belt surface is described
// in the object's own frame.
if (print.config().belt_printer.value) {
m_writer.set_first_layer_point_test([this](const Vec3d &point_logical) {
const Vec2d extruder_offset = m_writer.filament() != nullptr ? EXTRUDER_CONFIG(extruder_offset) : Vec2d::Zero();
@@ -5540,7 +5533,7 @@ std::string GCode::generate_object_brim(const Print &print, const PrintObject &o
// apron band has no Layer, and giving it a synthetic one would feed a fabricated
// Layer::id() into initial-layer temperature selection, the spiral vase probe,
// gradual interpolation and cooling. Correct first-layer treatment comes from
// FirstLayerPlane in BeltAffine mode, which is evaluated per point.
// the height above the belt, which is evaluated per point.
LayerResult GCode::process_belt_brim_layer(
const Print &print,
const std::vector<LayerToPrint> &layers,
@@ -6302,40 +6295,13 @@ LayerResult GCode::process_layer(
}
}
// First-layer plane: defer the temperature/PLR transition until the
// entire layer is past the first-layer band. When the evaluator is
// inactive (non-belt printers and belt printers without Z shear) we
// fall back to the legacy `!first_layer` predicate so behavior is
// bit-identical to the pre-feature path.
// Belt printers: defer the temperature/PLR transition until the entire layer
// is past the first-layer band above the belt. Elsewhere (non-belt printers,
// support-only layers) the legacy `!first_layer` predicate applies, so
// behavior is bit-identical to the pre-feature path.
bool past_first_layer_band = !first_layer;
if (int past = this->belt_layer_past_first_layer_band(object_layer); past >= 0) {
// Belt surface known for this object: measured from the belt itself, as the
// extrusions and travels are, rather than through FirstLayerPlane.
if (int past = this->belt_layer_past_first_layer_band(object_layer); past >= 0)
past_first_layer_band = past > 0;
} else if (m_first_layer_plane && m_first_layer_plane->is_active()) {
past_first_layer_band = false;
if (object_layer != nullptr) {
// Conservatively walk the layer's lslice bboxes; if every bbox's
// most-belt-side corner is outside the first-layer band, the
// layer is fully past it.
const Layer *ol = object_layer;
int min_eff = INT_MAX;
for (const BoundingBox &bb : ol->lslices_bboxes) {
BoundingBoxf bbf(
Vec2d(unscale<double>(bb.min.x()), unscale<double>(bb.min.y())),
Vec2d(unscale<double>(bb.max.x()), unscale<double>(bb.max.y())));
int eff = m_first_layer_plane->min_effective_index_for_xy_bbox(
bbf, ol->print_z);
if (eff < min_eff) min_eff = eff;
if (min_eff <= 0) break;
}
past_first_layer_band = (min_eff > 0 && min_eff != INT_MAX);
} else if (support_layer != nullptr) {
// Support-only layers: gate on the support layer's bottom_z
// proximity to the plane. Conservative.
past_first_layer_band = !first_layer;
}
}
if (past_first_layer_band && !m_second_layer_things_done) {
// Orca: set power loss recovery
@@ -8812,7 +8778,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_d
if (speed == 0)
speed = filament_max_volumetric_speed / _mm3_per_mm;
// Use the FirstLayerPlane-aware effective layer index when active so
// Use the belt-aware effective layer index when on a belt printer so
// the speed fade tracks perpendicular distance from the plane on
// belt printers; otherwise this falls back to the slicing layer id.
const int _layer = this->effective_layer_index_for_point(path_point_mm);
@@ -10637,8 +10603,8 @@ std::string GCode::set_object_info(Print *print) {
// Whether an object layer lies entirely past the first-layer band above the belt:
// 1 when its lowest point is at least one band thickness above the belt, 0 when
// any of it is inside the band, -1 when the belt surface is not known for this
// layer (not a belt print, an explicit first-layer plane, or no object layer), in
// which case the caller falls back to FirstLayerPlane / the slicing layer index.
// layer (not a belt print, or no object layer), in which case the caller falls
// back to the slicing layer index.
int GCode::belt_layer_past_first_layer_band(const Layer *object_layer) const
{
if (object_layer == nullptr)
@@ -10674,20 +10640,6 @@ bool GCode::belt_height_above_floor(const Vec3d &point_slicing_mm, double &heigh
: (m_layer != nullptr ? m_layer->object() : nullptr);
if (object == nullptr)
return false;
// Respect an explicit first-layer-plane choice: only Auto and BeltAffine mean
// "use the belt". A user who selected XY, YZ or XZ has asked for the
// FirstLayerPlane evaluator and must keep it.
const FirstLayerPlaneMode mode = m_config.first_layer_plane.value;
if (mode != FirstLayerPlaneMode::Auto && mode != FirstLayerPlaneMode::BeltAffine)
return false;
// Likewise for a dialled-in plane offset. It is expressed as a machine-Z
// shift that FirstLayerPlane converts into a perpendicular distance in the
// slicing frame; this evaluator measures along slicing Z instead, so there is
// no faithful translation of it here. Honour the user's setting by deferring
// to the evaluator that implements it rather than silently dropping it.
if (std::abs(m_config.first_layer_plane_offset.value) > EPSILON)
return false;
const SlicingParameters &sp = object->slicing_parameters();
// Deliberately NOT BeltFloorContext: its init() folds in
// belt_support_floor_offset, a support-generator diagnostic. Letting that
+13 -30
View File
@@ -8,7 +8,6 @@
#include "libslic3r.h"
#include "GCodeWriter.hpp"
#include "GCode/BeltKinematics.hpp"
#include "FirstLayerPlane.hpp"
#include "Layer.hpp"
#include "Point.hpp"
#include "PlaceholderParser.hpp"
@@ -400,7 +399,6 @@ public:
// first-layer-plane access points (on_first_layer overload, effective
// index helper) are in the protected section since they're called from
// GCode internals only.
const FirstLayerPlane *first_layer_plane() const { return m_first_layer_plane.get(); }
protected:
class GCodeOutputStream {
@@ -845,7 +843,6 @@ protected:
// PrintConfig. is_active() == false on non-belt printers and on belt
// printers without a Z-axis shear; in that case all per-path plane
// checks short-circuit to the legacy Layer::id() == 0 path.
std::unique_ptr<FirstLayerPlane> m_first_layer_plane;
// Plate origin, kept so a writer replaced during export can be given it again.
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
@@ -938,42 +935,33 @@ protected:
// On the first printing layer. This flag triggers first layer speeds.
//BBS
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0 && abs(m_layer->bottom_z()) < EPSILON; }
// Per-point first-layer test. When the FirstLayerPlane evaluator is
// active, the result depends on the supplied slicing-frame point;
// otherwise we delegate to the legacy per-layer test. This is the
// entry point used by per-path call sites in _extrude.
// Per-point first-layer test. On a belt printer the result depends on the
// supplied slicing-frame point (its height above the belt); otherwise we
// delegate to the legacy per-layer test. This is the entry point used by
// per-path call sites in _extrude.
bool on_first_layer(const Vec3d &point_slicing_mm) const {
// Belt printers: measure height above the belt surface itself, in the
// slicing frame. See belt_height_above_floor() for why this does not go
// through FirstLayerPlane.
double h;
if (this->belt_height_above_floor(point_slicing_mm, h))
return h <= m_config.initial_layer_print_height.value + EPSILON;
if (m_first_layer_plane && m_first_layer_plane->is_active())
return m_first_layer_plane->is_first_layer(
point_slicing_mm, m_config.initial_layer_print_height.value);
return on_first_layer();
}
// "Effective layer index" used to drive layer-count thresholds like
// slow_down_layers. When the evaluator is active this returns the
// perpendicular distance to the plane in band_thickness_mm units;
// otherwise it returns the legacy slicing layer index.
// slow_down_layers. On a belt printer this is the height above the belt in
// first_layer_band_mm() units; otherwise it is the legacy slicing layer index.
int effective_layer_index_for_point(const Vec3d &point_slicing_mm) const {
double h;
if (this->belt_height_above_floor(point_slicing_mm, h)) {
const double lh = this->first_layer_band_mm();
return h <= 0. ? 0 : int(std::floor(h / lh));
}
if (m_first_layer_plane && m_first_layer_plane->is_active())
return m_first_layer_plane->effective_layer_index(point_slicing_mm);
return on_first_layer() ? 0 : layer_id();
}
// Band thickness for the *effective layer index* only. FirstLayerPlane keeps
// two separate thresholds and so must this path: is_first_layer() tests
// against initial_layer_print_height, while effective_layer_index() counts
// bands of first_layer_plane_thickness. Conflating them would apply
// first-layer treatment through a whole 1mm band on a 0.2mm first layer.
// Band thickness for the *effective layer index* only. Two separate
// thresholds: on_first_layer(point) tests against initial_layer_print_height,
// while effective_layer_index_for_point() counts bands of
// first_layer_plane_thickness. Conflating them would apply first-layer
// treatment through a whole 1mm band on a 0.2mm first layer.
double first_layer_band_mm() const {
double band = m_config.first_layer_plane_thickness.value;
if (band <= 0.) band = m_config.initial_layer_print_height.value;
@@ -985,13 +973,8 @@ protected:
//
// The belt surface is known exactly in the slicing frame from the slicing
// parameters (belt_floor_shear_factor / _from_axis / _z_shift) -- the same
// description the support generator uses. FirstLayerPlane instead derives its
// plane by composing gcode_remap_* with the g-code back-transform, so its
// answer changes with the machine's *output* axis convention: on a printer
// with a non-identity remap it reported ~86mm of clearance for geometry
// sitting directly on the belt, and no extrusion was ever classified as
// first-layer. Measuring against the belt itself is independent of every
// remap and back-transform.
// description the support generator uses, independent of every remap and
// back-transform.
bool belt_height_above_floor(const Vec3d &point_slicing_mm, double &height_mm) const;
// 1 / 0 / -1: the object layer is entirely past the first-layer band above the
// belt / reaches into it / the belt surface is not known for it.
+4 -8
View File
@@ -13,16 +13,12 @@ bool BeltBackTransform::init_from_config(const PrintConfig &config)
if (!config.belt_printer.value || !config.gcode_back_transform.value)
return false;
// Require at least one active transform to proceed.
bool has_global_rotation = config.belt_slice_rotation_global.value
&& config.belt_slice_rotation.value != BeltRotationAxis::None;
bool has_preslice_global = config.belt_preslice_global.value
|| config.preslice_remap_global.value;
if (!has_global_rotation && !has_preslice_global
&& !BeltTransformPipeline::has_preslice_remap(config))
// The back-transform undoes the global pre-slice rotation; without global
// mode the slicing frame is not a common frame to undo.
if (!config.belt_preslice_global.value)
return false;
// Build the forward pipeline (rotation * pre_remap) and store its inverse.
// Build the forward pipeline (the rotation) and store its inverse.
Transform3d forward = BeltTransformPipeline::build_forward_transform(config);
if (forward.isApprox(Transform3d::Identity()))
return false;
+6 -10
View File
@@ -7,25 +7,21 @@
namespace Slic3r {
// Reverses the pre-slice remap + shear + scale transforms that
// PrintObjectSlice.cpp applies to belt printer geometry, converting G-code
// coordinates from the sliced (remapped/sheared/scaled) frame back to the
// machine's real coordinate space.
// Reverses the pre-slice rotation that PrintObjectSlice.cpp applies to belt
// printer geometry, converting G-code coordinates from the sliced (rotated)
// frame back to the machine's real coordinate space.
//
// Initialized once from PrintConfig, then applied per-point in
// BeltKinematics::to_machine() before axis remapping.
//
// Active when gcode_back_transform is true AND at least one of:
// - a shear axis has global mode enabled, or
// - a pre-slice axis remap is non-identity.
// Active when gcode_back_transform and belt_preslice_global are both set.
class BeltBackTransform
{
public:
BeltBackTransform() = default;
// Initialize from belt printer config. Rebuilds the same pre-slice remap,
// shear, and scale matrices as PrintObjectSlice.cpp and precomputes the
// affine inverse. Returns true if a non-identity back-transform was computed.
// Initialize from belt printer config. Rebuilds the same pre-slice rotation
// as PrintObjectSlice.cpp and precomputes the affine inverse. Returns true if a non-identity back-transform was computed.
bool init_from_config(const PrintConfig &config);
// Apply the inverse transform to a point. Returns pos unchanged if
+1 -35
View File
@@ -2641,9 +2641,6 @@ void GCodeProcessorResult::reset() {
machine_frame_transform_active = false;
belt_tilt_angle = 0.f;
belt_z_origin = 0.f;
preslice_remap_x = RemapAxis::PosX;
preslice_remap_y = RemapAxis::PosY;
preslice_remap_z = RemapAxis::PosZ;
settings_ids.reset();
filaments_count = 0;
backtrace_enabled = false;
@@ -3223,8 +3220,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
const auto *belt = config.option<ConfigOptionBool>("belt_printer");
if (belt != nullptr) {
static const char *belt_keys[] = {
"belt_printer", "belt_slice_rotation", "belt_slice_rotation_angle", "belt_slice_rotation_global",
"belt_preslice_global", "preslice_remap_x", "preslice_remap_y", "preslice_remap_z", "preslice_remap_global",
"belt_printer", "belt_slice_rotation", "belt_slice_rotation_angle", "belt_preslice_global",
"gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "gcode_back_transform",
"belt_frame_tilt_decouple", "belt_frame_tilt_angle",
};
@@ -4350,36 +4346,6 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
} catch (...) {}
return;
}
// Belt printer: parse pre-slice axis remap from header comments.
{
auto trim = [](const std::string &s) -> std::string {
size_t start = s.find_first_not_of(" \t\r\n");
size_t end = s.find_last_not_of(" \t\r\n");
return (start == std::string::npos) ? "" : s.substr(start, end - start + 1);
};
// Pre-slice axis remap
auto parse_remap_axis = [](const std::string &s) -> RemapAxis {
if (s == "pos_x") return RemapAxis::PosX;
if (s == "pos_y") return RemapAxis::PosY;
if (s == "pos_z") return RemapAxis::PosZ;
if (s == "neg_x") return RemapAxis::NegX;
if (s == "neg_y") return RemapAxis::NegY;
if (s == "neg_z") return RemapAxis::NegZ;
if (s == "rev_x") return RemapAxis::RevX;
if (s == "rev_y") return RemapAxis::RevY;
if (s == "rev_z") return RemapAxis::RevZ;
return RemapAxis::PosX;
};
if (boost::starts_with(comment, " preslice_remap_x = ")) {
m_result.preslice_remap_x = parse_remap_axis(trim(std::string(comment.substr(20)))); return;
}
if (boost::starts_with(comment, " preslice_remap_y = ")) {
m_result.preslice_remap_y = parse_remap_axis(trim(std::string(comment.substr(20)))); return;
}
if (boost::starts_with(comment, " preslice_remap_z = ")) {
m_result.preslice_remap_z = parse_remap_axis(trim(std::string(comment.substr(20)))); return;
}
}
// wipe start tag
if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) {
m_wiping = true;
-6
View File
@@ -304,9 +304,6 @@ class Print;
// machine frame and should not be compared against `printable_height`
// (which lives in the build-volume frame).
bool machine_frame_transform_active{ false };
RemapAxis preslice_remap_x{ RemapAxis::PosX };
RemapAxis preslice_remap_y{ RemapAxis::PosY };
RemapAxis preslice_remap_z{ RemapAxis::PosZ };
SettingsIds settings_ids;
size_t filaments_count;
bool backtrace_enabled;
@@ -405,9 +402,6 @@ class Print;
belt_tilt_angle = std::forward<Other>(other).belt_tilt_angle;
belt_z_origin = std::forward<Other>(other).belt_z_origin;
machine_frame_transform_active = std::forward<Other>(other).machine_frame_transform_active;
preslice_remap_x = std::forward<Other>(other).preslice_remap_x;
preslice_remap_y = std::forward<Other>(other).preslice_remap_y;
preslice_remap_z = std::forward<Other>(other).preslice_remap_z;
#if ENABLE_GCODE_VIEWER_STATISTICS
time = std::forward<Other>(other).time;
#endif
+3 -4
View File
@@ -1560,13 +1560,12 @@ static std::vector<std::string> s_Preset_machine_limits_options {
static std::vector<std::string> s_Preset_printer_options {
"printer_technology",
"printable_area", "extruder_printable_area", "support_parallel_printheads", "parallel_printheads_count", "parallel_printheads_bed_exclude_areas", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "build_plate_tilt_x", "build_plate_tilt_y", "belt_printer", "belt_printer_infinite_y",
"belt_slice_rotation", "belt_slice_rotation_angle", "belt_slice_rotation_global",
"preslice_remap_x", "preslice_remap_y", "preslice_remap_z", "preslice_remap_global",
"belt_slice_rotation", "belt_slice_rotation_angle",
"gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "gcode_back_transform",
"belt_frame_tilt_decouple", "belt_frame_tilt_angle",
"belt_preslice_global",
"first_layer_plane", "first_layer_plane_offset", "first_layer_plane_thickness",
"belt_support_floor_offset", "belt_support_floor_mode", "belt_support_z_offset_mode",
"first_layer_plane_thickness",
"belt_support_floor_offset", "belt_support_floor_mode",
"enable_belt_purge_tower",
"gcode_flavor", "gcode_skip_config_block",
"fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
+5 -17
View File
@@ -177,7 +177,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
// Machine-frame transform (derived from belt tilt; only affects G-code output).
"belt_frame_tilt_decouple", "belt_frame_tilt_angle",
"gcode_back_transform",
"first_layer_plane", "first_layer_plane_offset", "first_layer_plane_thickness",
"first_layer_plane_thickness",
// Only inflates the GUI bed volume, like printable_area.
"belt_printer_infinite_y",
//BBS
@@ -389,17 +389,11 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "belt_printer"
|| opt_key == "belt_slice_rotation"
|| opt_key == "belt_slice_rotation_angle"
|| opt_key == "belt_slice_rotation_global"
|| opt_key == "belt_preslice_global"
|| opt_key == "preslice_remap_global"
|| opt_key == "preslice_remap_x"
|| opt_key == "preslice_remap_y"
|| opt_key == "preslice_remap_z") {
|| opt_key == "belt_preslice_global") {
osteps.emplace_back(posSlice);
} else if (
opt_key == "belt_support_floor_offset"
|| opt_key == "belt_support_floor_mode"
|| opt_key == "belt_support_z_offset_mode") {
|| opt_key == "belt_support_floor_mode") {
osteps.emplace_back(posSupportMaterial);
} else if (
opt_key == "print_sequence"
@@ -2107,9 +2101,7 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
bool have_height = false;
if (belt_printer) {
double raw_z = print_object.model_object()->max_z();
if (BeltTransformPipeline::has_preslice_remap(this->config()))
raw_z = BeltTransformPipeline::remap_bbox(*print_object.model_object(), this->config()).size().z();
const double raw_z = print_object.model_object()->max_z();
effective_max_z = raw_z;
have_height = raw_z > 0;
} else {
@@ -3069,11 +3061,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
std::set<PrintObject*> re_slicing_objects;
// Belt global modes couple each object's bed position into its layer Z values,
// so sharing layers between "identical" objects is wrong.
bool belt_no_share = m_config.belt_printer.value &&
((m_config.belt_slice_rotation_global.value
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None)
|| m_config.preslice_remap_global.value
|| m_config.belt_preslice_global.value);
bool belt_no_share = m_config.belt_printer.value && m_config.belt_preslice_global.value;
if (!use_cache) {
for (int index = 0; index < object_count; index++)
{
+3 -4
View File
@@ -231,11 +231,10 @@ class ConstSupportLayerPtrsAdaptor : public ConstVectorOfPtrsAdaptor<SupportLaye
ConstSupportLayerPtrsAdaptor(const SupportLayerPtrs *data) : ConstVectorOfPtrsAdaptor<SupportLayer>(data) {}
};
// Returns the model's raw bounding box with pre-slice axis remap applied.
// When no remap is active, returns the unmodified raw_bounding_box().
inline BoundingBoxf3 belt_remapped_bbox(const ModelObject &model_object, const PrintConfig &config)
// The model's raw bounding box, in the frame the belt floor parameters refer to.
inline BoundingBoxf3 belt_remapped_bbox(const ModelObject &model_object, const PrintConfig & /*config*/)
{
return BeltTransformPipeline::remap_bbox(model_object, config);
return model_object.raw_bounding_box();
}
// Single instance of a PrintObject.
+2 -11
View File
@@ -1844,12 +1844,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Orca: Updated for XYZ filament shrink compensation
// Belt global mode: force each instance into its own PrintObject
// so each gets independent layer Z values.
bool belt_force_separate = m_config.belt_printer.value && (
(m_config.belt_slice_rotation_global.value
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None
&& std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON)
|| m_config.belt_preslice_global.value
|| (m_config.preslice_remap_global.value && BeltTransformPipeline::has_preslice_remap(m_config)));
bool belt_force_separate = m_config.belt_printer.value && m_config.belt_preslice_global.value;
model_object_status.print_instances = print_objects_from_model_object(*model_object, this->shrinkage_compensation(), belt_force_separate);
std::vector<const PrintObjectStatus*> old;
old.reserve(print_object_status_db.count(*model_object));
@@ -1940,11 +1935,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// min_shift across all objects, so one move affects everyone).
if (belt_instances_shifted
&& m_config.belt_printer.value
&& ((m_config.belt_slice_rotation_global.value
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None
&& std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON)
|| m_config.belt_preslice_global.value
|| (m_config.preslice_remap_global.value && BeltTransformPipeline::has_preslice_remap(m_config)))) {
&& m_config.belt_preslice_global.value) {
for (PrintObject *object : m_objects)
update_apply_status(object->invalidate_step(posSlice));
}
+15 -121
View File
@@ -404,25 +404,6 @@ static t_config_enum_values s_keys_map_BeltSupportFloorMode {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltSupportFloorMode)
static t_config_enum_values s_keys_map_BeltSupportZOffsetMode {
{ "none", int(BeltSupportZOffsetMode::None) },
{ "unconditional", int(BeltSupportZOffsetMode::Unconditional) },
{ "raft_only", int(BeltSupportZOffsetMode::RaftOnly) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltSupportZOffsetMode)
static t_config_enum_values s_keys_map_FirstLayerPlaneMode {
{ "auto", int(FirstLayerPlaneMode::Auto) },
{ "xy", int(FirstLayerPlaneMode::XY) },
{ "yz", int(FirstLayerPlaneMode::YZ) },
{ "xz", int(FirstLayerPlaneMode::XZ) },
{ "belt_affine", int(FirstLayerPlaneMode::BeltAffine) },
// Back-compat alias: pre-rotation builds serialised this mode as
// "belt_shear". Accept it on parse so old 3MFs / presets keep loading.
{ "belt_shear", int(FirstLayerPlaneMode::BeltAffine) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FirstLayerPlaneMode)
static t_config_enum_values s_keys_map_SupportMaterialPattern {
{ "rectilinear", smpRectilinear },
{ "rectilinear-grid", smpRectilinearGrid },
@@ -7425,16 +7406,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(45.));
def = this->add("belt_slice_rotation_global", coBool);
def->label = L("Global");
def->category = L("Printable space");
def->tooltip = L("Treat the slicing rotation as part of the global forward transform "
"that BeltBackTransform inverts before the machine-frame remap. "
"Required for rotation-mode belt printers. "
"Defaults to on because virtually all rotation-mode printers need it.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("belt_frame_tilt_decouple", coBool);
def->label = L("Decouple machine-frame tilt");
def->category = L("Printable space");
@@ -7458,7 +7429,7 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(45.));
// G-code axis remap with sign
// G-code axis remap with sign. Each field is its own row in the settings tab.
auto add_belt_remap = [this](const char *key, const char *label, const char *tooltip,
RemapAxis default_axis, ConfigOptionMode mode = comSimple) {
auto def = this->add(key, coEnum);
@@ -7472,39 +7443,9 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionEnum<RemapAxis>(default_axis));
};
add_belt_remap("preslice_remap_x", "X",
"Before slicing, which model-space axis becomes the slicer's X axis. "
"Use this to re-orient the coordinate system so the slicer's XY plane matches "
"your belt printer's physical bed plane. For a printer whose bed is in the XZ plane, "
"set Y to +Z and Z to +Y (or -Y) to swap the vertical and belt-travel axes. "
"Default +X: no change.",
RemapAxis::PosX, comDevelop);
add_belt_remap("preslice_remap_y", "Y",
"Before slicing, which model-space axis becomes the slicer's Y axis. "
"The slicer treats Y as one of the two horizontal bed axes. If your physical "
"belt surface runs along the Z axis, map Y to +Z here so the slicer slices "
"along the correct plane. Default +Y: no change.",
RemapAxis::PosY, comDevelop);
add_belt_remap("preslice_remap_z", "Z",
"Before slicing, which model-space axis becomes the slicer's Z axis (layer stacking direction). "
"The slicer builds layers upward along this axis. If your printer's layer-stacking "
"direction is the physical Y axis, map Z to +Y (or -Y for inverted direction). "
"Rev mode mirrors relative to the build volume maximum. Default +Z: no change.",
RemapAxis::PosZ, comDevelop);
def = this->add("preslice_remap_global", coBool);
def->label = L("Global");
def->category = L("Printable space");
def->tooltip = L("When enabled, the pre-slice axis remap accounts for each object's bed position. "
"Without this, the remap is applied locally around each object's center, so "
"objects at different positions don't get a position-dependent contribution. "
"Mirrors the 'Global' option on the belt slicing rotation, but for the remap.");
def->mode = comDevelop;
def->set_default_value(new ConfigOptionBool(false));
add_belt_remap("gcode_remap_x", "X", "Which slicing axis maps to machine X in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosX, comDevelop);
add_belt_remap("gcode_remap_y", "Y", "Which slicing axis maps to machine Y in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosY, comDevelop);
add_belt_remap("gcode_remap_z", "Z", "Which slicing axis maps to machine Z in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosZ, comDevelop);
add_belt_remap("gcode_remap_x", "G-code remap X", "Which slicing axis maps to machine X in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosX, comDevelop);
add_belt_remap("gcode_remap_y", "G-code remap Y", "Which slicing axis maps to machine Y in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosY, comDevelop);
add_belt_remap("gcode_remap_z", "G-code remap Z", "Which slicing axis maps to machine Z in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosZ, comDevelop);
// The machine-frame G-code transform (shear + scale) is no longer configured
// by per-axis keys: it is derived from the belt tilt (belt_slice_rotation axis
@@ -7528,53 +7469,14 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(true));
// First-layer plane: which surface defines "first layer" for fan / speed /
// accel decisions. On belt printers the slicing-frame layer 0 is a tilted
// slab that no longer corresponds to the physical first printed layer.
// Auto picks BeltAffine when any belt-side affine transform is active
// (Z shear or slicing rotation), otherwise XY (legacy).
def = this->add("first_layer_plane", coEnum);
def->label = L("First layer plane");
def->category = L("Printable space");
def->tooltip = L("Selects the reference plane used to decide which extrusions get "
"first-layer settings (no fan, slow speed, initial-layer accel/jerk, "
"deferred temperature drop). On belt printers a single slicing layer "
"contains paths at many machine-Z values, so layer-index based detection "
"fails. Auto resolves to Belt affine plane when any belt-side affine "
"transform (Z shear or slicing rotation) is active, otherwise XY (legacy). "
"Pick XY explicitly to opt out and force the legacy slicing-layer-0 "
"detection.");
def->enum_keys_map = &ConfigOptionEnum<FirstLayerPlaneMode>::get_enum_values();
def->enum_values = {"auto", "xy", "yz", "xz", "belt_affine"};
def->enum_labels = {L("Auto"), L("XY (machine bed)"), L("YZ"), L("XZ"), L("Belt affine plane")};
def->mode = comExpert;
// Auto, not BeltAffine: BeltAffine activates the plane evaluator unconditionally, so on a
// non-belt printer on_first_layer(point) stopped agreeing with the legacy slicing-layer-0
// test and first-layer speeds were skipped (brim printed at the volumetric fallback rather
// than initial_layer_speed). Auto resolves to BeltAffine only when belt_printer is set with
// a non-zero slicing rotation, and to XY (evaluator inactive, legacy behaviour) otherwise --
// which is what this option's own description promises.
def->set_default_value(new ConfigOptionEnum<FirstLayerPlaneMode>(FirstLayerPlaneMode::Auto));
def = this->add("first_layer_plane_offset", coFloat);
def->label = L("Belt plane offset");
def->category = L("Printable space");
def->tooltip = L("Shifts the first-layer plane along its normal (mm). For axis-aligned "
"planes this is just a coordinate shift. Positive values move the plane "
"away from the belt surface (deeper into the model).");
def->sidetext = L("mm");
def->min = -1000;
def->max = 1000;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.0));
def = this->add("first_layer_plane_thickness", coFloat);
def->label = L("Plane band thickness");
def->label = L("First layer band thickness");
def->category = L("Printable space");
def->tooltip = L("Thickness of one 'band' relative to the first-layer plane, in mm. "
"Used as the unit by which 'No cooling for the first N layers' (and "
"similar layer-count thresholds) is multiplied when the first-layer "
"plane is active. -1 means use initial_layer_print_height.");
def->tooltip = L("Belt printers only. Every tilted layer touches the belt, so the first-layer "
"settings apply to a band above the belt surface rather than to the first "
"slicing layer. This is the thickness of one band, in mm: the unit by which "
"'No cooling for the first N layers' and similar layer-count thresholds are "
"multiplied. -1 means use the first layer height.");
def->sidetext = L("mm");
def->min = -1;
def->max = 100;
@@ -7605,19 +7507,6 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionEnum<BeltSupportFloorMode>(BeltSupportFloorMode::GeneratorOnly));
}
{
auto def = this->add("belt_support_z_offset_mode", coEnum);
def->label = L("Z offset mode");
def->category = L("Printable space");
def->tooltip = L("How global Z offset is applied to support layers for belt printers with global shear. "
"'None' = don't offset. 'Unconditional' = offset all layers. 'Raft only' = only offset raft layers.");
def->enum_keys_map = &ConfigOptionEnum<BeltSupportZOffsetMode>::get_enum_values();
def->enum_values = {"none", "unconditional", "raft_only"};
def->enum_labels = {L("None"), L("Unconditional"), L("Raft only")};
def->mode = comExpert;
def->set_default_value(new ConfigOptionEnum<BeltSupportZOffsetMode>(BeltSupportZOffsetMode::Unconditional));
}
def = this->add("enable_belt_purge_tower", coBool);
def->label = L("Enable belt purge tower");
def->category = L("Multimaterial");
@@ -9793,6 +9682,11 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
"smooth_coefficient", "overhang_totally_speed", "silent_mode",
"overhang_speed_classic",
"anisotropic_surfaces", // superseded by top_surface_fill_order / bottom_surface_fill_order
// Belt printer keys retired before the first release: the global switches collapsed
// into belt_preslice_global, the pre-slice axis remap and the support Z offset mode
// were removed.
"belt_slice_rotation_global", "preslice_remap_x", "preslice_remap_y", "preslice_remap_z", "preslice_remap_global",
"belt_support_z_offset_mode", "first_layer_plane", "first_layer_plane_offset",
};
if (ignore.find(opt_key) != ignore.end()) {
-38
View File
@@ -300,34 +300,6 @@ enum class BeltSupportFloorMode
GeneratorOnly, // Only in tree support drop_nodes/contact_points
};
enum class BeltSupportZOffsetMode
{
None, // Don't apply global_z_offset to support layers
Unconditional, // Apply to all support layers
RaftOnly, // Only apply to raft layers
};
// Selects which plane the slicer treats as the "first layer plane" — the
// reference surface used to decide which extrusions get first-layer settings
// (no fan, slow speed, initial-layer accel/jerk, deferred temperature drop).
//
// Auto resolves to:
// - XY (inactive, legacy behavior) for non-belt printers and for belt
// printers with no active belt-side transform.
// - BeltAffine for belt printers with any active belt-side affine
// transform (Z shear, slicing rotation, or both).
//
// XY is also used as an explicit "opt out" mode that forces legacy
// per-layer first-layer detection even on belt printers.
enum class FirstLayerPlaneMode
{
Auto = 0,
XY,
YZ,
XZ,
BeltAffine, // formerly BeltShear; renamed to reflect rotation support
};
enum SupportMaterialPattern {
smpDefault,
smpRectilinear, smpRectilinearGrid, smpHoneycomb,
@@ -775,8 +747,6 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltRotationAxis)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(RemapAxis)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportFloorMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportZOffsetMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FirstLayerPlaneMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialPattern)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern)
@@ -1898,26 +1868,18 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
// g-code back-transform inverts the rotation before the machine-frame stage.
((ConfigOptionEnum<BeltRotationAxis>, belt_slice_rotation))
((ConfigOptionFloat, belt_slice_rotation_angle))
((ConfigOptionBool, belt_slice_rotation_global))
// Expert override: decouple the machine-frame tilt angle from the pre-slice
// rotation angle. When disabled, the machine frame uses belt_slice_rotation_angle.
((ConfigOptionBool, belt_frame_tilt_decouple))
((ConfigOptionFloat, belt_frame_tilt_angle))
((ConfigOptionEnum<RemapAxis>, preslice_remap_x))
((ConfigOptionEnum<RemapAxis>, preslice_remap_y))
((ConfigOptionEnum<RemapAxis>, preslice_remap_z))
((ConfigOptionBool, preslice_remap_global))
((ConfigOptionEnum<RemapAxis>, gcode_remap_x))
((ConfigOptionEnum<RemapAxis>, gcode_remap_y))
((ConfigOptionEnum<RemapAxis>, gcode_remap_z))
((ConfigOptionBool, gcode_back_transform))
((ConfigOptionBool, belt_preslice_global))
((ConfigOptionEnum<FirstLayerPlaneMode>, first_layer_plane))
((ConfigOptionFloat, first_layer_plane_offset))
((ConfigOptionFloat, first_layer_plane_thickness))
((ConfigOptionFloat, belt_support_floor_offset))
((ConfigOptionEnum<BeltSupportFloorMode>, belt_support_floor_mode))
((ConfigOptionEnum<BeltSupportZOffsetMode>, belt_support_z_offset_mode))
// Width (machine X, across the belt) of the auto-generated belt purge prism.
((ConfigOptionFloat, belt_purge_tower_width))
// Belt-printer-only "type" of purge tower: enables the auto-generated belt
+1 -6
View File
@@ -4154,9 +4154,7 @@ void PrintObject::update_slicing_parameters()
BeltTransformPipeline::BeltFloorParams belt_floor;
const auto &pcfg = this->print()->config();
if (pcfg.belt_printer.value) {
BoundingBoxf3 bb = BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg);
if (BeltTransformPipeline::has_preslice_remap(pcfg))
object_height = bb.size().z();
const BoundingBoxf3 bb = this->model_object()->raw_bounding_box();
auto hr = BeltTransformPipeline::compute_belt_height_and_floor(pcfg, bb, object_height);
object_height = hr.object_height;
belt_floor = hr.floor_params;
@@ -4217,9 +4215,6 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
BoundingBoxf3 bb = model_object.raw_bounding_box();
object_max_z = (float)bb.size().z();
if (print_config.belt_printer.value) {
bb = BeltTransformPipeline::remap_bbox(model_object, print_config);
if (BeltTransformPipeline::has_preslice_remap(print_config))
object_max_z = (float)bb.size().z();
auto hr = BeltTransformPipeline::compute_belt_height_and_floor(print_config, bb, object_max_z);
object_max_z = (float)hr.object_height;
belt_floor = hr.floor_params;
+8 -39
View File
@@ -343,8 +343,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
// pushes those layers into the parallel_for path below, which handles multi-volume
// clipping per layer without relying on the bbox Z range.
const bool bbox_z_in_layer_frame = !(print_config.belt_printer.value &&
(BeltTransformPipeline::has_rotation(print_config)
|| BeltTransformPipeline::has_preslice_remap(print_config)));
BeltTransformPipeline::has_rotation(print_config));
// Belt-transform addendum: with bbox-Z untrusted, the simple path's
// "first model_part wins" logic drops subsequent volumes' slices unless
// they XY-overlap with the first. Assemblies whose volumes are stacked
@@ -926,14 +925,9 @@ void PrintObject::slice()
// So: belt_floor_z_shift = remapped_bb.min.z() + z_shift_val
if (std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON) {
double z_shift_val = (m_belt_min_z < 0.) ? -m_belt_min_z : 0.;
// With pre-remap, the belt surface (model_Y=0) may not be at Z=0 in
// centered slicer space — add the remapped bbox min Z to compensate.
// Without pre-remap, the belt surface IS at Z=0 and bb.min.z() is
// already folded into m_belt_min_z, so use 0.
const auto &pcfg = this->print()->config();
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
m_slicing_params.belt_floor_z_shift = belt_surface_z + z_shift_val;
// The belt surface is at Z=0 in centered slicer space and bb.min.z() is
// already folded into m_belt_min_z.
m_slicing_params.belt_floor_z_shift = z_shift_val;
}
int firstLayerReplacedBy = 0;
@@ -982,7 +976,6 @@ void PrintObject::slice()
const auto &pcfg = this->print()->config();
BOOST_LOG_TRIVIAL(trace) << "Belt global check: belt_printer=" << pcfg.belt_printer.value
<< " belt_slice_rotation=" << int(pcfg.belt_slice_rotation.value)
<< " belt_slice_rotation_global=" << pcfg.belt_slice_rotation_global.value
<< " belt_preslice_global=" << pcfg.belt_preslice_global.value
<< " object=" << this->model_object()->name;
if (pcfg.belt_printer.value) {
@@ -1003,8 +996,7 @@ void PrintObject::slice()
// couples slicer_z back into both machine_y and machine_z. Compensating
// layer.print_z by belt_z_shift here makes the back-transform produce
// correct machine-frame coordinates whether or not a global mode is active.
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
const double belt_surface_z = 0.; // the belt surface is Z=0 in centered slicer space
// The compensation must mirror the Z-shift actually applied, which
// is max(0, -m_belt_min_z): when the transformed mesh starts ABOVE
// slicer Z=0 (m_belt_min_z > 0 — possible for counter-rotated or
@@ -1036,7 +1028,9 @@ void PrintObject::slice()
if (pcfg.belt_preslice_global.value) {
// Global pre-slice mode: compute full correction c = (T.linear() - I) * d
// where T is the belt forward transform and d is the bed position.
// where T is the belt forward transform and d is the bed position, so
// objects at different bed positions print at different machine Z values
// along the inclined belt.
Transform3d T = BeltTransformPipeline::build_forward_transform(pcfg);
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
Vec3d c = T.linear() * d - d;
@@ -1046,31 +1040,6 @@ void PrintObject::slice()
BOOST_LOG_TRIVIAL(trace) << "Belt preslice_global: correction=("
<< c.x() << ", " << c.y() << ", " << c.z() << ")"
<< " belt_z_shift=" << belt_z_shift << " (m_belt_min_z=" << m_belt_min_z << ")";
} else {
// Slicing rotation in global mode: bed-position-dependent Z offset.
// For R(α, X): c.z = sin(α)*d.y so objects at different bed-Y
// values print at different machine Z values along the inclined belt.
if (pcfg.belt_slice_rotation_global.value
&& pcfg.belt_slice_rotation.value != BeltRotationAxis::None
&& std::abs(pcfg.belt_slice_rotation_angle.value) > EPSILON) {
Transform3d T = BeltTransformPipeline::build_forward_transform(pcfg);
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
Vec3d c = T.linear() * d - d;
global_z_offset += c.z();
m_belt_global_xy_correction = Vec2d(c.x(), c.y());
}
// Pre-slice remap global mode: when on, the remap accounts for the
// instance bed position. The Z component of the correction
// (R - I) * d shifts layer print_z so e.g. a Y↔Z swap with an
// object at Y=50 prints at Z=50.
if (pcfg.preslice_remap_global.value
&& BeltTransformPipeline::has_preslice_remap(pcfg)) {
Transform3d R = BeltTransformPipeline::build_preslice_remap(pcfg);
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
Vec3d remap_correction = R.linear() * d - d;
global_z_offset += remap_correction.z();
}
}
BOOST_LOG_TRIVIAL(trace) << "Belt global: z_offset=" << global_z_offset
+3 -4
View File
@@ -1339,10 +1339,9 @@ static Transform3d compute_belt_back_transform(const PrintConfig& cfg)
// build-volume offset for Rev axes). This is the matrix form of the per-point
// GCodeWriter::apply_axis_remap (row convention: each OUTPUT axis selects an input
// axis + sign) and MUST stay in sync with it. The build-volume max matches what the
// writer is fed in GCode.cpp (printable_area max + printable_height). NB: this is the
// transpose of the column convention used by BeltTransformPipeline::build_preslice_remap
// — the two remaps are not interchangeable. (Follow-up: precompute this matrix once in
// GCodeWriter and share it with apply_axis_remap to remove the parallel encoding.)
// writer is fed in GCode.cpp (printable_area max + printable_height). (Follow-up:
// precompute this matrix once in GCodeWriter and share it with apply_axis_remap to
// remove the parallel encoding.)
Transform3d ar = Transform3d::Identity();
const int rr[3] = { int(cfg.gcode_remap_x.value), int(cfg.gcode_remap_y.value), int(cfg.gcode_remap_z.value) };
if (rr[0] != 0 || rr[1] != 1 || rr[2] != 2) {
+19 -68
View File
@@ -5260,67 +5260,27 @@ void TabPrinter::build_fff()
line.label_path = "printer_basic_information_belt_printer#belt-tilt";
line.append_option(belt_og->get_option("belt_slice_rotation"));
line.append_option(belt_og->get_option("belt_slice_rotation_angle"));
line.append_option(belt_og->get_option("belt_slice_rotation_global"));
belt_og->append_line(line);
}
{
Line line = { L("Pre-slice axis remap"),
L("Remap model axes before slicing so the slicer's coordinate system matches "
"the physical bed orientation. For belt printers whose bed is NOT in the XY plane, "
"use this to swap axes so layers are stacked in the correct physical direction.") };
line.label_path = "printer_basic_information_belt_printer#pre-slice-axis-remap";
line.append_option(belt_og->get_option("preslice_remap_x"));
line.append_option(belt_og->get_option("preslice_remap_y"));
line.append_option(belt_og->get_option("preslice_remap_z"));
line.append_option(belt_og->get_option("preslice_remap_global"));
belt_og->append_line(line);
}
belt_og->append_single_option_line("belt_preslice_global", "printer_basic_information_belt_printer#global-mesh-transforms");
belt_og->append_single_option_line("gcode_back_transform", "printer_basic_information_belt_printer#g-code-back-transform");
{
Line line = { L("First layer plane"),
L("Reference plane used to decide which extrusions get first-layer "
"settings (no fan, slow speed, deferred temperature drop). On belt "
"printers, Auto resolves to the tilted belt-shear plane so that "
"first-layer treatment follows perpendicular distance from the belt "
"surface, not slicing layer index.") };
line.label_path = "printer_basic_information_belt_printer#first-layer-plane";
line.append_option(belt_og->get_option("first_layer_plane"));
line.append_option(belt_og->get_option("first_layer_plane_offset"));
line.append_option(belt_og->get_option("first_layer_plane_thickness"));
belt_og->append_line(line);
}
belt_og->append_single_option_line("first_layer_plane_thickness", "printer_basic_information_belt_printer#first-layer-band-thickness");
// Support floor: split across lines so each setting's own mode controls
// its visibility (floor_mode = Develop, floor_offset = Advanced, z_offset_mode = Expert).
// its visibility (floor_mode = Develop, floor_offset = Advanced).
belt_og->append_single_option_line("belt_support_floor_offset", "printer_basic_information_belt_printer#support-floor-z-offset");
belt_og->append_single_option_line("belt_support_z_offset_mode", "printer_basic_information_belt_printer#z-offset-mode");
belt_og->append_single_option_line("belt_support_floor_mode", "printer_basic_information_belt_printer#floor-mode");
// Machine-frame transform: the shear (tan) + scale (1/cos) that map
// Machine-frame transform: the shear (cot) + scale (1/sin) that map
// Cartesian G-code into the printer's physical machine frame are derived
// from the belt tilt angle. Only the post-slice axis remap and the expert
// decouple override are exposed here.
// decouple override are exposed here, one option per row.
{
auto mf = page->new_optgroup(L("Machine frame transforms"), L"param_advanced");
{
Line line = { L("G-code axis remap (post-slice)"), L("Remap slicing-frame axes to machine axes in G-code output. Applied AFTER slicing, during G-code generation.") };
line.label_path = "printer_basic_information_machine_frame_transforms#g-code-axis-remap";
line.append_option(mf->get_option("gcode_remap_x"));
line.append_option(mf->get_option("gcode_remap_y"));
line.append_option(mf->get_option("gcode_remap_z"));
mf->append_line(line);
}
{
Line line = { L("Machine-frame tilt"),
L("The machine-frame shear (tan) and scale (1/cos) are derived from "
"the belt tilt angle. Enable 'Decouple' to set an independent "
"machine-frame angle when the physical gantry tilt differs from "
"the slicing rotation.") };
line.label_path = "printer_basic_information_machine_frame_transforms#machine-frame-tilt";
line.append_option(mf->get_option("belt_frame_tilt_decouple"));
line.append_option(mf->get_option("belt_frame_tilt_angle"));
mf->append_line(line);
}
mf->append_single_option_line("gcode_remap_x", "printer_basic_information_machine_frame_transforms#g-code-axis-remap");
mf->append_single_option_line("gcode_remap_y", "printer_basic_information_machine_frame_transforms#g-code-axis-remap");
mf->append_single_option_line("gcode_remap_z", "printer_basic_information_machine_frame_transforms#g-code-axis-remap");
mf->append_single_option_line("belt_frame_tilt_decouple", "printer_basic_information_machine_frame_transforms#machine-frame-tilt");
mf->append_single_option_line("belt_frame_tilt_angle", "printer_basic_information_machine_frame_transforms#machine-frame-tilt");
}
option = optgroup->get_option("thumbnails");
@@ -6370,36 +6330,27 @@ void TabPrinter::toggle_options()
// Remap, back-transform, and global mesh-transforms toggles are gated by belt
// mode here; finer mode-based visibility is handled by each option's
// ConfigOptionMode in PrintConfig.cpp. Both axis remaps are Develop-only: a
// printer profile sets them once for its kinematics, and a wrong value sends
// ConfigOptionMode in PrintConfig.cpp. The axis remap is Develop-only: a
// printer profile sets it once for its kinematics, and a wrong value sends
// the gantry outside the machine.
for (auto el : {"preslice_remap_x", "gcode_remap_x", "gcode_back_transform"})
for (auto el : {"gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "gcode_back_transform"})
toggle_line(el, is_belt);
toggle_line("belt_preslice_global", is_belt);
bool belt_global = is_belt && m_config->opt_bool("belt_preslice_global");
// preslice_remap_global: superseded by belt_preslice_global
toggle_option("preslice_remap_global", is_belt && !belt_global);
// Rotation is the only mesh-side belt transform. Gray out its angle/global
// sub-options when no rotation axis is selected.
// Rotation is the only mesh-side belt transform. Gray out its angle when no
// rotation axis is selected.
auto rot_axis = m_config->option<ConfigOptionEnum<BeltRotationAxis>>("belt_slice_rotation")->value;
toggle_option("belt_slice_rotation_angle", is_belt && rot_axis != BeltRotationAxis::None);
toggle_option("belt_slice_rotation_global", is_belt && rot_axis != BeltRotationAxis::None);
// Machine-frame transform: derived from the belt tilt. Only the expert
// decouple override is exposed; its angle is enabled only when decoupled.
// decouple override is exposed; its angle is shown only when decoupled.
toggle_line("belt_frame_tilt_decouple", is_belt && expert_or_above);
toggle_option("belt_frame_tilt_angle",
is_belt && expert_or_above && m_config->opt_bool("belt_frame_tilt_decouple"));
toggle_line("belt_frame_tilt_angle",
is_belt && expert_or_above && m_config->opt_bool("belt_frame_tilt_decouple"));
// First-layer plane: visible alongside the rest of belt-printer settings.
toggle_line("first_layer_plane", is_belt);
toggle_option("first_layer_plane_offset", is_belt);
toggle_option("first_layer_plane_thickness", is_belt);
toggle_line("first_layer_plane_thickness", is_belt);
for (auto el : {"belt_support_floor_mode", "belt_support_floor_offset", "belt_support_z_offset_mode"})
for (auto el : {"belt_support_floor_mode", "belt_support_floor_offset"})
toggle_line(el, is_belt);
const bool support_parallel_printheads = printer_cfg.opt_bool("support_parallel_printheads");
toggle_line("parallel_printheads_count", support_parallel_printheads);
-2
View File
@@ -56,7 +56,6 @@ TEST_CASE("Belt machine coordinates retain a non-45-degree slicing angle", "[GCo
config.belt_printer.value = true;
config.belt_slice_rotation.value = BeltRotationAxis::X;
config.belt_slice_rotation_angle.value = 30.;
config.belt_slice_rotation_global.value = true;
config.gcode_back_transform.value = true;
config.gcode_remap_x.value = RemapAxis::PosX;
config.gcode_remap_y.value = RemapAxis::PosZ;
@@ -1030,7 +1029,6 @@ SCENARIO("Belt: the first travel does not lift through the uninitialised origin"
belt_config.gcode_back_transform.value = true;
belt_config.belt_slice_rotation.value = BeltRotationAxis::X;
belt_config.belt_slice_rotation_angle.value = 45.0;
belt_config.belt_slice_rotation_global.value = true;
belt_config.belt_preslice_global.value = true;
belt_config.belt_frame_tilt_decouple.value = false;
belt_config.belt_frame_tilt_angle.value = 45.0;
-1
View File
@@ -1438,7 +1438,6 @@ TEST_CASE("Belt printers slice Precise Seam modifiers in the frame the object wa
{ "belt_printer", 1 },
{ "belt_slice_rotation", "x" },
{ "belt_slice_rotation_angle", 45 },
{ "belt_slice_rotation_global", 1 },
{ "layer_height", 0.2 },
{ "initial_layer_print_height", 0.2 },
{ "skirt_loops", 0 },
+2 -15
View File
@@ -765,7 +765,6 @@ TEST_CASE("Belt printers never start a scarf seam below the layer", "[Print][bel
{ "belt_printer", 1 },
{ "belt_slice_rotation", "x" },
{ "belt_slice_rotation_angle", 45 },
{ "belt_slice_rotation_global", 1 },
{ "gcode_remap_x", "rev_x" },
{ "gcode_remap_y", "pos_z" },
{ "gcode_remap_z", "pos_y" },
@@ -815,7 +814,6 @@ TEST_CASE("Belt printers refuse an object taller than the gantry clearance", "[P
{ "belt_printer", 1 },
{ "belt_slice_rotation", "x" },
{ "belt_slice_rotation_angle", 45 },
{ "belt_slice_rotation_global", 1 },
{ "gcode_remap_x", "rev_x" },
{ "gcode_remap_y", "pos_z" },
{ "gcode_remap_z", "pos_y" },
@@ -851,7 +849,6 @@ TEST_CASE("Belt printers keep the part fan off within the band above the belt",
{ "belt_printer", 1 },
{ "belt_slice_rotation", "x" },
{ "belt_slice_rotation_angle", 45 },
{ "belt_slice_rotation_global", 1 },
{ "gcode_remap_x", "rev_x" },
{ "gcode_remap_y", "pos_z" },
{ "gcode_remap_z", "pos_y" },
@@ -930,7 +927,6 @@ TEST_CASE("Belt printers slice organic tree supports that reach the belt", "[Pri
{ "belt_printer", 1 },
{ "belt_slice_rotation", "x" },
{ "belt_slice_rotation_angle", 45 },
{ "belt_slice_rotation_global", 1 },
{ "gcode_remap_x", "rev_x" },
{ "gcode_remap_y", "pos_z" },
{ "gcode_remap_z", "pos_y" },
@@ -1003,7 +999,6 @@ static DynamicPrintConfig belt_test_config()
{ "belt_printer", 1 },
{ "belt_slice_rotation", "x" },
{ "belt_slice_rotation_angle", 45 },
{ "belt_slice_rotation_global", 1 },
{ "gcode_remap_x", "rev_x" },
{ "gcode_remap_y", "pos_z" },
{ "gcode_remap_z", "pos_y" },
@@ -1039,30 +1034,23 @@ TEST_CASE("Belt-only keys at non-default values leave non-belt G-code unchanged"
// Every belt key a profile can carry, at a value that would change a belt print.
// belt_printer stays off, so none of them may reach the G-code: the axis remaps are
// gated on belt mode, the rest is only read on belt printers. build_plate_tilt_x/y
// and an explicit first_layer_plane are features of their own on a flat bed and are
// left alone here; "leading_edge_only" prints as an outer brim by design.
// is a feature of its own on a flat bed and is left alone here; "leading_edge_only"
// prints as an outer brim by design.
config.set_deserialize_strict({
{ "belt_printer", 0 },
{ "belt_printer_infinite_y", 0 },
{ "belt_slice_rotation", "y" },
{ "belt_slice_rotation_angle", 30 },
{ "belt_slice_rotation_global", 0 },
{ "belt_preslice_global", 0 },
{ "preslice_remap_x", "pos_x" },
{ "preslice_remap_y", "pos_z" },
{ "preslice_remap_z", "neg_y" },
{ "preslice_remap_global", 1 },
{ "gcode_remap_x", "rev_x" },
{ "gcode_remap_y", "pos_z" },
{ "gcode_remap_z", "pos_y" },
{ "gcode_back_transform", 0 },
{ "belt_frame_tilt_decouple", 1 },
{ "belt_frame_tilt_angle", 30 },
{ "first_layer_plane_offset", 1 },
{ "first_layer_plane_thickness", 1 },
{ "belt_support_floor_offset", -5 },
{ "belt_support_floor_mode", "none" },
{ "belt_support_z_offset_mode", "raft_only" },
{ "enable_belt_purge_tower", 1 },
{ "belt_purge_tower_width", 10 },
{ "leading_brim_length", 10 },
@@ -1136,7 +1124,6 @@ TEST_CASE("A support-only change on a belt purge print matches a fresh slice", "
{ "belt_printer", 1 },
{ "belt_slice_rotation", "x" },
{ "belt_slice_rotation_angle", 45 },
{ "belt_slice_rotation_global", 1 },
{ "gcode_remap_x", "rev_x" },
{ "gcode_remap_y", "pos_z" },
{ "gcode_remap_z", "pos_y" },
-4
View File
@@ -641,7 +641,6 @@ static DynamicPrintConfig belt_brim_config()
{ "belt_printer", 1 },
{ "belt_slice_rotation", "x" },
{ "belt_slice_rotation_angle", 45 },
{ "belt_slice_rotation_global", 1 },
{ "gcode_remap_x", "rev_x" },
{ "gcode_remap_y", "pos_z" },
{ "gcode_remap_z", "pos_y" },
@@ -667,7 +666,6 @@ static DynamicPrintConfig belt_brim_multifilament_config(unsigned int filaments,
{ "belt_printer", 1 },
{ "belt_slice_rotation", "x" },
{ "belt_slice_rotation_angle", 45 },
{ "belt_slice_rotation_global", 1 },
{ "gcode_remap_x", "rev_x" },
{ "gcode_remap_y", "pos_z" },
{ "gcode_remap_z", "pos_y" },
@@ -1246,9 +1244,7 @@ TEST_CASE("Belt brim allows instances placed across the belt", "[SkirtBrim][belt
auto multi_instance_has_brim = [](double dx, double dy) {
DynamicPrintConfig config = belt_brim_config();
config.set_deserialize_strict({
{ "belt_slice_rotation_global", 0 },
{ "belt_preslice_global", 0 },
{ "preslice_remap_global", 0 },
{ "brim_type", "outer_only" },
{ "brim_width", 4 },
{ "brim_object_gap", 0 },