mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-08 16:21:14 +00:00
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:
co-authored by
Claude Fable 5.1
parent
199758f67f
commit
8039d4d2ac
@@ -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",
|
||||
|
||||
@@ -41,6 +41,7 @@ struct SlopeDetection
|
||||
bool actived;
|
||||
float normal_z;
|
||||
mat3 volume_world_normal_matrix;
|
||||
vec3 up_direction;
|
||||
};
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
|
||||
@@ -44,6 +44,7 @@ struct SlopeDetection
|
||||
bool actived;
|
||||
float normal_z;
|
||||
mat3 volume_world_normal_matrix;
|
||||
vec3 up_direction;
|
||||
};
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -94,8 +94,6 @@ set(lisbslic3r_sources
|
||||
BeltSliceStrategy.hpp
|
||||
BeltTransform.cpp
|
||||
BeltTransform.hpp
|
||||
FirstLayerPlane.cpp
|
||||
FirstLayerPlane.hpp
|
||||
Brim.cpp
|
||||
BrimEarsPoint.hpp
|
||||
Brim.hpp
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user