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
+142
View File
@@ -2432,6 +2432,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// Build volume extents for Rev remap mode.
BoundingBoxf bbox_bed(print.config().printable_area.values);
m_writer.set_build_volume_max(Vec3d(bbox_bed.max.x(), bbox_bed.max.y(), print.config().printable_height.value));
// Initialize the back-transform that undoes slicing shear/scale.
m_writer.set_belt_back_transform(print.config());
// Per-axis origin snap: store config; actual snap is computed
// per-instance in update_origin_snap() called from set_origin().
m_origin_snap[0] = print.config().belt_origin_snap_x.value;
m_origin_snap[1] = print.config().belt_origin_snap_y.value;
m_origin_snap[2] = print.config().belt_origin_snap_z.value;
m_origin_snap_offset[0] = print.config().belt_origin_offset_x.value;
m_origin_snap_offset[1] = print.config().belt_origin_offset_y.value;
m_origin_snap_offset[2] = print.config().belt_origin_offset_z.value;
}
// How many times will be change_layer() called?
@@ -2544,6 +2554,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
file.write_format("; belt_scale_y_angle = %.1f\n", print.config().belt_scale_y_angle.value);
file.write_format("; belt_scale_z = %s\n", full_cfg.opt_serialize("belt_scale_z").c_str());
file.write_format("; belt_scale_z_angle = %.1f\n", print.config().belt_scale_z_angle.value);
file.write_format("; belt_preslice_remap_x = %s\n", full_cfg.opt_serialize("belt_preslice_remap_x").c_str());
file.write_format("; belt_preslice_remap_y = %s\n", full_cfg.opt_serialize("belt_preslice_remap_y").c_str());
file.write_format("; belt_preslice_remap_z = %s\n", full_cfg.opt_serialize("belt_preslice_remap_z").c_str());
}
if (is_bbl_printers)
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
@@ -3248,6 +3261,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
}
print.throw_if_canceled();
this->set_origin(unscale((*print_object_instance_sequential_active)->shift));
this->update_origin_snap((*print_object_instance_sequential_active)->print_object,
(*print_object_instance_sequential_active)->shift);
// BBS: prime extruder if extruder change happens before this object instance
bool prime_extruder = false;
@@ -5263,6 +5278,7 @@ LayerResult GCode::process_layer(
m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset));
this->update_origin_snap(&instance_to_print.print_object, offset);
if (instance_to_print.object_by_extruder.support != nullptr) {
m_layer = layers[instance_to_print.layer_id].support_layer;
m_object_layer_over_raft = false;
@@ -5286,6 +5302,7 @@ LayerResult GCode::process_layer(
m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset));
this->update_origin_snap(&instance_to_print.print_object, offset);
ExtrusionEntityCollection support_eec;
// BBS
@@ -5334,6 +5351,7 @@ LayerResult GCode::process_layer(
m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset));
this->update_origin_snap(&instance_to_print.print_object, offset);
//FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
auto has_infill = [](const std::vector<ObjectByExtruder::Island::Region> &by_region) {
@@ -5560,6 +5578,130 @@ void GCode::set_origin(const Vec2d &pointf)
m_origin = pointf;
}
void GCode::update_origin_snap(const PrintObject *obj, const Point &inst_shift)
{
if (!m_origin_snap[0] && !m_origin_snap[1] && !m_origin_snap[2])
return;
// Clear existing snap so to_machine_coords gives raw machine coords for bbox computation.
for (int a = 0; a < 3; ++a)
m_writer.set_origin_snap(a, false, 0., 0.);
// Reconstruct the belt pipeline transform for this object (same as
// PrintObjectSlice.cpp: z_shift * scale * shear * pre_remap).
const auto &cfg = m_config;
Transform3d belt = Transform3d::Identity();
// Pre-slice remap
int pre_rx = int(cfg.belt_preslice_remap_x.value);
int pre_ry = int(cfg.belt_preslice_remap_y.value);
int pre_rz = int(cfg.belt_preslice_remap_z.value);
if (pre_rx != int(BeltRemapAxis::PosX) ||
pre_ry != int(BeltRemapAxis::PosY) ||
pre_rz != int(BeltRemapAxis::PosZ)) {
auto remap_col = [](int r) -> Vec3d {
int a = r % 3; Vec3d c = Vec3d::Zero();
c[a] = (r < 3) ? 1.0 : -1.0;
return c;
};
Matrix3d lin;
lin.col(0) = remap_col(pre_rx);
lin.col(1) = remap_col(pre_ry);
lin.col(2) = remap_col(pre_rz);
Transform3d pre = Transform3d::Identity();
pre.linear() = lin;
if (pre_rx >= 6 || pre_ry >= 6 || pre_rz >= 6) {
BoundingBoxf bb(cfg.printable_area.values);
Vec3d vm(bb.max.x(), bb.max.y(), cfg.printable_height.value);
Vec3d tr = Vec3d::Zero();
if (pre_rx >= 6) tr[0] = vm[pre_rx % 3];
if (pre_ry >= 6) tr[1] = vm[pre_ry % 3];
if (pre_rz >= 6) tr[2] = vm[pre_rz % 3];
pre.translation() = tr;
}
belt = pre * belt;
}
// Shear
auto shear_f = [](BeltShearMode m, double a) -> double {
double r = Geometry::deg2rad(a), s = std::sin(r), c = std::cos(r);
switch (m) {
case BeltShearMode::PosCot: return (s > EPSILON) ? c/s : 0.;
case BeltShearMode::NegCot: return (s > EPSILON) ? -c/s : 0.;
case BeltShearMode::PosTan: return (c > EPSILON) ? s/c : 0.;
case BeltShearMode::NegTan: return (c > EPSILON) ? -s/c : 0.;
default: return 0.;
}
};
struct AS { BeltShearMode m; double a; int f; };
AS axes[3] = {
{cfg.belt_shear_x.value, cfg.belt_shear_x_angle.value, int(cfg.belt_shear_x_from.value)},
{cfg.belt_shear_y.value, cfg.belt_shear_y_angle.value, int(cfg.belt_shear_y_from.value)},
{cfg.belt_shear_z.value, cfg.belt_shear_z_angle.value, int(cfg.belt_shear_z_from.value)},
};
Transform3d shear = Transform3d::Identity();
for (int i = 0; i < 3; ++i)
if (axes[i].m != BeltShearMode::None) {
double f = shear_f(axes[i].m, axes[i].a);
if (std::abs(f) > EPSILON)
shear.matrix()(i, axes[i].f) += f;
}
// Scale
auto scale_f = [](BeltScaleMode m, double a) -> double {
if (m == BeltScaleMode::None) return 1.;
double r = Geometry::deg2rad(a), s = std::sin(r), c = std::cos(r);
switch (m) {
case BeltScaleMode::InvSin: return (s > EPSILON) ? 1./s : 1.;
case BeltScaleMode::InvCos: return (c > EPSILON) ? 1./c : 1.;
case BeltScaleMode::Sin: return s;
case BeltScaleMode::Cos: return c;
default: return 1.;
}
};
Transform3d sc = Transform3d::Identity();
sc.matrix()(0,0) = scale_f(cfg.belt_scale_x.value, cfg.belt_scale_x_angle.value);
sc.matrix()(1,1) = scale_f(cfg.belt_scale_y.value, cfg.belt_scale_y_angle.value);
sc.matrix()(2,2) = scale_f(cfg.belt_scale_z.value, cfg.belt_scale_z_angle.value);
belt = sc * shear * belt;
// Z-shift
double zs = (obj->belt_min_z() < 0.) ? -obj->belt_min_z() : 0.;
if (zs > 0.) {
Transform3d zsh = Transform3d::Identity();
zsh.matrix()(2, 3) = zs;
belt = zsh * belt;
}
// Full transform: belt * trafo_centered
Transform3d full = belt * obj->trafo_centered();
// Instance shift in slicer space + global Z offset
Vec3d shift(unscale<double>(inst_shift.x()),
unscale<double>(inst_shift.y()),
obj->belt_global_z_offset());
// Compute this instance's machine-space bbox min
BoundingBoxf3 bb = obj->model_object()->raw_bounding_box();
Vec3d mn = bb.min.cast<double>(), mx = bb.max.cast<double>();
Vec3d inst_min(std::numeric_limits<double>::max(),
std::numeric_limits<double>::max(),
std::numeric_limits<double>::max());
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 mc = m_writer.to_machine_coords(full * c + shift);
for (int a = 0; a < 3; ++a)
inst_min[a] = std::min(inst_min[a], mc[a]);
}
// Update writer snap for each enabled axis
for (int a = 0; a < 3; ++a)
m_writer.set_origin_snap(a, m_origin_snap[a], m_origin_snap_offset[a], inst_min[a]);
}
std::string GCode::preamble()
{
std::string gcode = m_writer.preamble();