Part 2.7: Add G-code back-transform and tree support belt floor clipping

- Add BeltBackTransform class that inverts the shear/scale matrix and
  applies it in GCodeWriter::to_machine_coords() so G-code outputs in
  the machine's physical coordinate space, gated by new
  belt_gcode_back_transform config option
- Extend belt floor clipping to all three tree support pipelines
  (Prusa-style, Orca organic, TreeModelVolumes) with per-layer polygon
  clipping, anti-overhang integration, and belt raft extension layers
- Fix tree drop_nodes() belt termination, organic support global Z
  offset, collision calculation index bug, and first-layer brim/empty
  layer checks for belt printers

two-shot - first build built but didn't plumb to UI.  Woah.

add pre-slice axis remap, because Y needs to be Z

going to change tactic and move based on bbox min

switch to per axis snapping

per axis swap snap now per object

build plate tilt wasn't invalidating slicer settings

support upper bound now correct, need to get lower bound corrected

axis swapped support termination corrected

Z Shear works with and without pre-slice remap now
This commit is contained in:
harrierpigeon
2026-04-09 23:07:07 -05:00
parent ea5c6776b3
commit 9bbac19de4
20 changed files with 767 additions and 36 deletions
+98 -19
View File
@@ -143,6 +143,58 @@ static std::vector<VolumeSlices> slice_volumes_inner(
params_base.extra_offset = 0;
params_base.trafo = object_trafo;
if (print_config.belt_printer.value) {
// --- Pre-slice axis remap ---
// Permutes/negates model axes before slicing so the slicer's coordinate
// system matches the physical bed orientation (e.g. XZ bed instead of XY).
int pre_rx = int(print_config.belt_preslice_remap_x.value);
int pre_ry = int(print_config.belt_preslice_remap_y.value);
int pre_rz = int(print_config.belt_preslice_remap_z.value);
bool has_preslice_remap = (pre_rx != int(BeltRemapAxis::PosX) ||
pre_ry != int(BeltRemapAxis::PosY) ||
pre_rz != int(BeltRemapAxis::PosZ));
if (has_preslice_remap) {
// Build volume extents for Rev mode.
BoundingBoxf bbox_bed(print_config.printable_area.values);
Vec3d vol_max(bbox_bed.max.x(), bbox_bed.max.y(),
print_config.printable_height.value);
// Each remap value selects a source axis and sign.
// The column vector tells the matrix which input axis feeds this output.
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);
// Translation for Rev modes: output = max[src] - input[src].
Vec3d remap_trans = Vec3d::Zero();
auto add_rev_offset = [&](int r, int out_axis) {
if (r >= 6) {
int src_axis = r % 3;
remap_trans[out_axis] = vol_max[src_axis];
}
};
add_rev_offset(pre_rx, 0);
add_rev_offset(pre_ry, 1);
add_rev_offset(pre_rz, 2);
Transform3d pre_remap = Transform3d::Identity();
pre_remap.linear() = remap_lin;
pre_remap.translation() = remap_trans;
params_base.trafo = pre_remap * params_base.trafo;
}
// Build per-axis shear matrix from 3 independent axis configs.
auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double {
double angle_rad = Geometry::deg2rad(angle_deg);
@@ -204,11 +256,12 @@ static std::vector<VolumeSlices> slice_volumes_inner(
}
// Apply: scale * shear * trafo (shear first, then scale).
if (has_shear || has_scale) {
if (has_shear || has_scale)
params_base.trafo = belt_scale * belt_shear * params_base.trafo;
// After the shear/scale transform, the mesh may clip through the
// build plate (Z < 0). Detect this and shift the mesh up.
// After pre-remap/shear/scale, the mesh may clip through the build
// plate (Z < 0). Detect this and shift the mesh up along slicer Z.
if (has_preslice_remap || has_shear || has_scale) {
Transform3d combined = params_base.trafo;
double min_z = std::numeric_limits<double>::max();
for (const ModelVolume *mv : model_volumes) {
@@ -893,17 +946,34 @@ void PrintObject::slice()
this->slice_volumes();
m_print->throw_if_canceled();
// After slicing, m_belt_min_z holds the exact post-shear minimum Z
// in trafo_centered space (which includes the ensure_on_bed Z offset).
// The belt surface is at Z=0 in trafo_centered space; after shear it
// becomes Z = sf*Y, and after the z-shift that keeps the mesh above
// Z=0 it becomes Z = sf*Y + z_shift_val. So belt_floor_z_shift is
// simply the z-shift applied, i.e. max(0, -m_belt_min_z).
// NOTE: do NOT add raw_bounding_box().min.z() here — m_belt_min_z
// already includes the ensure_on_bed offset, unlike the min_rz used
// in update_slicing_parameters() which needs that compensation.
// Belt floor Z-shift: where is the belt surface in final slicer space?
//
// The belt surface is at model_Y=0 (XZ belt plane). After the full
// pipeline (trafo_centered → pre_remap → shear → z_shift), the belt
// surface equation in slicer space is:
// Z_belt = sf * from_axis + belt_surface_z_centered + z_shift_val
//
// belt_surface_z_centered = remapped_bbox.min.z() (the Z position of
// the belt surface in centered-pre-shear slicer space, which is 0
// without pre-remap but nonzero when e.g. Y↔Z swap shifts the belt
// surface away from Z=0 by the centering offset).
//
// z_shift_val = max(0, -m_belt_min_z) (lifts mesh above Z=0).
//
// So: belt_floor_z_shift = remapped_bb.min.z() + z_shift_val
if (std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON) {
m_slicing_params.belt_floor_z_shift = (m_belt_min_z < 0.) ? -m_belt_min_z : 0.;
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();
bool has_preslice_remap = (int(pcfg.belt_preslice_remap_x.value) != int(BeltRemapAxis::PosX) ||
int(pcfg.belt_preslice_remap_y.value) != int(BeltRemapAxis::PosY) ||
int(pcfg.belt_preslice_remap_z.value) != int(BeltRemapAxis::PosZ));
double belt_surface_z = has_preslice_remap
? belt_remapped_bbox(*this->model_object(), pcfg).min.z() : 0.;
m_slicing_params.belt_floor_z_shift = belt_surface_z + z_shift_val;
}
int firstLayerReplacedBy = 0;
@@ -989,14 +1059,23 @@ void PrintObject::slice()
const auto &za = gaxes[2]; // Z row
if (za.global && za.mode != BeltShearMode::None && za.from < 2) {
double factor = compute_shear_factor(za.mode, za.angle);
// The Z-shift brought the mesh's lowest sheared vertex to
// Z=0. That vertex's physical Y determines the belt contact
// point. With trafo_z preserved (ensure_on_bed offset),
// min_z = Y_at_contact * factor for bottom-face vertices,
// so: z_offset = center_Y * factor + min_z.
// The global Z offset accounts for the instance's position-
// dependent shear contribution. m_belt_min_z is the minimum Z
// of the mesh after pre_remap + shear + trafo_centered, which
// includes the centering offset on the remapped Z axis.
// Subtract the belt surface's centered Z position so we get
// only the shear-induced contribution (same correction as the
// belt_floor_z_shift fix).
// Same pre-remap guard as belt_floor_z_shift above.
bool has_preslice_remap2 = (int(pcfg.belt_preslice_remap_x.value) != int(BeltRemapAxis::PosX) ||
int(pcfg.belt_preslice_remap_y.value) != int(BeltRemapAxis::PosY) ||
int(pcfg.belt_preslice_remap_z.value) != int(BeltRemapAxis::PosZ));
double belt_surface_z = has_preslice_remap2
? belt_remapped_bbox(*this->model_object(), this->print()->config()).min.z() : 0.;
double shear_min_z = m_belt_min_z - belt_surface_z;
Point phys = inst_shift; // already has center_offset subtracted
double center_on_axis = (za.from == 0) ? unscale<double>(phys.x()) : unscale<double>(phys.y());
global_z_offset += center_on_axis * factor + m_belt_min_z;
global_z_offset += center_on_axis * factor + shear_min_z;
}
BOOST_LOG_TRIVIAL(warning) << "Belt global: z_offset=" << global_z_offset