From 8593d66a3998de8e87886251493f6d10bbc26eef Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Sun, 7 Jun 2026 09:57:12 +0200 Subject: [PATCH] belt: correct the designed-view preview's belt-Z origin and reject mis-mapped outliers The Cartesian designed-view preview over-extended the toolpaths past the model shell by a height-proportional amount (up to ~20mm tall parts), most visibly on long multi-part prints; compact parts like a calibration cube looked fine. Two coupled causes: - Belt start G-code that primes with a Z advance and a 'G92 Z0' reset leaves a constant machine-Z origin in the GCodeProcessor, so move positions are stored as gcode_Z + origin. The linear back-transform mixes that constant with the gantry-Y term, leaving a per-move designed-Y error that min-corner anchoring cannot cancel when an elevated move (e.g. a bridge) happens to cancel it at the bbox minimum. Expose GCodeProcessorResult::belt_z_origin (the m_origin[Z] left by the start G-code) and subtract it before the back-transform. - Elevated features (bridges/overhangs) are mis-mapped by the linear inverse to outside the model body; build the anchor bbox only from moves within model_bb +/- 10mm, with a fallback to the full bbox when the clip would drop the bulk (object placed away from the belt entry) so the gross-offset case still anchors. Preview-only; G-code output is unchanged. --- src/libslic3r/GCode/GCodeProcessor.cpp | 7 ++++ src/libslic3r/GCode/GCodeProcessor.hpp | 6 ++++ src/slic3r/GUI/GCodeViewer.cpp | 47 ++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 003c0af05a..b15736ef57 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -4933,6 +4933,13 @@ void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line) if (line.has_z()) { m_origin[Z] = m_end_position[Z] - line.z() * lengths_scale_factor; any_found = true; + // Belt only: the start G-code's purge-blob advance + G92 Z0 resets leave a constant + // machine-Z origin offset here; the designed-view back-transform subtracts it so + // toolpaths map to the model's belt coordinate (gcode Z). Gated on belt_tilt_angle + // (set from the belt header, parsed before the body) so non-belt G-code processing + // is byte-identical — no unconditional work on the shared path. + if (m_result.belt_tilt_angle != 0.f) + m_result.belt_z_origin = m_origin[Z]; } if (line.has_e()) { diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 3047c005ed..8fcf2e0c43 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -248,6 +248,11 @@ class Print; // Belt printer: physical tilt magnitude (deg) parsed from the slicing-rotation // header comment; used to enable the preview's belt view. float belt_tilt_angle{ 0.f }; + // Belt printer: machine-Z origin offset (mm) left in m_origin[Z] by the start + // G-code (purge-blob belt advance + G92 Z0 resets). Move positions are stored + // as gcode_Z + this offset, so the designed-view back-transform must subtract it + // to recover the model's belt coordinate. + float belt_z_origin{ 0.f }; RemapAxis preslice_remap_x{ RemapAxis::PosX }; RemapAxis preslice_remap_y{ RemapAxis::PosY }; RemapAxis preslice_remap_z{ RemapAxis::PosZ }; @@ -317,6 +322,7 @@ class Print; filament_change_count_map = other.filament_change_count_map; initial_layer_time = other.initial_layer_time; belt_tilt_angle = other.belt_tilt_angle; + belt_z_origin = other.belt_z_origin; preslice_remap_x = other.preslice_remap_x; preslice_remap_y = other.preslice_remap_y; preslice_remap_z = other.preslice_remap_z; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 9baced5d78..1263e3fed6 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1231,6 +1231,14 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const const bool is_belt = print.config().belt_printer.value; Transform3d belt_inv = (is_belt && m_belt_show_designed) ? compute_belt_back_transform(print.config()) : Transform3d::Identity(); + // Belt: move positions are stored as gcode_Z + belt_z_origin (the start G-code's + // purge-blob advance baked into the machine-Z origin by its G92 Z0 resets). Subtract + // that constant before the linear back-transform so every toolpath maps to the model's + // belt coordinate. Without it the back-transform mixes the offset with the gantry-Y + // term, leaving a per-move designed-Y error that min-corner anchoring cannot remove + // when a bridge/keel move happens to cancel it at the bbox minimum. + if (is_belt && m_belt_show_designed && gcode_result.belt_z_origin != 0.0f) + belt_inv = belt_inv * Transform3d(Eigen::Translation3d(Vec3d(0.0, 0.0, -double(gcode_result.belt_z_origin)))); const bool apply_belt = is_belt && m_belt_show_designed && !belt_inv.matrix().isApprox(Transform3d::Identity().matrix()); if (apply_belt) { @@ -1246,17 +1254,42 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const for (const PrintObject* po : print.objects()) for (const ModelInstance* mi : po->model_object()->instances) model_bb.merge(po->model_object()->instance_bounding_box(*mi)); - BoundingBoxf3 tp_bb; + // Build the anchor bbox from surface toolpaths only. After the belt_z_origin + // correction the surface back-transforms onto the model, but a few elevated + // features (bridges/overhangs over the chevron gap) are mis-mapped by the linear + // inverse to well outside the model body; if one becomes the bbox minimum it + // drags the min-corner anchor by ~20mm. Drop moves that land clearly outside the + // (correct) model bbox — a geometric filter, not a role guess. + BoundingBoxf3 tp_bb_clip, tp_bb_full; + const double y_lo = model_bb.defined ? model_bb.min.y() - 10.0 : -1e30; + const double y_hi = model_bb.defined ? model_bb.max.y() + 10.0 : 1e30; for (const GCodeProcessorResult::MoveVertex& mv : gcode_result.moves) - if (mv.type == EMoveType::Extrude && mv.layer_id >= 1) // skip layer-0 prime/skirt - tp_bb.merge(Vec3d(belt_inv * mv.position.cast())); + if (mv.type == EMoveType::Extrude && mv.layer_id >= 1) { // skip layer-0 prime/skirt + const Vec3d p = belt_inv * mv.position.cast(); + tp_bb_full.merge(p); + if (p.y() >= y_lo && p.y() <= y_hi) + tp_bb_clip.merge(p); + } + // Use the clipped bbox only when it still holds the bulk of the body (outliers + // removed). If the object sits far from the belt entry the toolpaths are grossly + // offset and the clip would drop most of them — fall back to the full bbox so the + // min-corner anchor still recovers that gross translation rather than breaking. + const BoundingBoxf3& tp_bb = + (tp_bb_clip.defined && tp_bb_full.defined && + tp_bb_clip.size().y() >= 0.5 * tp_bb_full.size().y()) ? tp_bb_clip : tp_bb_full; if (model_bb.defined && tp_bb.defined) { + // Anchor the back-transformed toolpath body onto the upright model bbox by + // its MIN corner. (Center anchoring was tried and regressed when the toolpath + // and model bounding boxes differ in extent.) With the belt_z_origin + // correction above and the outlier-robust bbox below, the surface overlaps the + // shell to well under a millimetre when the object is at the belt entry; an + // object placed elsewhere in global-rotation mode still carries the placement + // translation, which this min-corner step recovers. const Vec3d d = model_bb.min - tp_bb.min; belt_inv = Transform3d(Eigen::Translation3d(d)) * belt_inv; - BOOST_LOG_TRIVIAL(debug) << "[BELT-PREVIEW] anchor: model_bb.min=(" - << model_bb.min.x() << "," << model_bb.min.y() << "," << model_bb.min.z() - << ") tp_bb.min=(" << tp_bb.min.x() << "," << tp_bb.min.y() << "," << tp_bb.min.z() - << ") d=(" << d.x() << "," << d.y() << "," << d.z() << ")"; + BOOST_LOG_TRIVIAL(debug) << "[BELT-PREVIEW] z_origin=" << gcode_result.belt_z_origin + << " model_bb.y=[" << model_bb.min.y() << "," << model_bb.max.y() + << "] tp_bb.y=[" << tp_bb.min.y() << "," << tp_bb.max.y() << "] d.y=" << d.y(); } } libvgcode::GCodeInputData data = libvgcode::convert(gcode_result, str_tool_colors, str_color_print_colors, m_viewer,