From 3fc3b8a8aedae1042cfe8f8166c51f3c0b25351f Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Sat, 6 Jun 2026 19:39:57 +0200 Subject: [PATCH] belt: anchor the designed-view G-code preview onto the model bounding box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The belt designed (upright) preview back-transforms the machine-frame G-code into model space with the linear belt inverse. That inverse recovers the print's shape and orientation, but not the per-object placement/lift translation: the object's position on the belt, the BeltSliceStrategy min-Z lift, and the centering pre-translate are applied OUTSIDE build_forward_transform() (see PrintObjectSlice.cpp), so its linear inverse cannot undo them. The result was a constant offset (~20 mm on the belt-advance axis) of the toolpaths from the model shell, on every model. Recover the missing translation generally — independent of the offset's exact source or the axis remap — by anchoring the back-transformed object body (extrusions on layer_id >= 1, i.e. excluding the layer-0 prime/skirt) onto the upright model bounding box, the same space the shells render in, and folding that translation into the belt inverse before converting to libvgcode. Replaces the previous Y=0 anchoring in LibVGCodeWrapper, which pinned the toolpaths to the belt entry rather than to the model and so left the offset in place for any object not sitting at the origin. --- src/slic3r/GUI/GCodeViewer.cpp | 28 ++++++++++++++++++- src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp | 23 +++------------ 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 963730e093..9baced5d78 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1229,10 +1229,36 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const // inverse (handles any mesh rotation + shear + axis remap). When off, the raw // machine-frame G-code is shown (useful for debugging the transform itself). const bool is_belt = print.config().belt_printer.value; - const Transform3d belt_inv = (is_belt && m_belt_show_designed) + Transform3d belt_inv = (is_belt && m_belt_show_designed) ? compute_belt_back_transform(print.config()) : Transform3d::Identity(); const bool apply_belt = is_belt && m_belt_show_designed && !belt_inv.matrix().isApprox(Transform3d::Identity().matrix()); + if (apply_belt) { + // The linear belt back-transform recovers the print's shape and orientation but not + // the per-object placement/lift translation: the object's position on the belt, the + // BeltSliceStrategy min-Z lift, and the centering pre-translate are applied OUTSIDE + // build_forward_transform() (see PrintObjectSlice.cpp), so its linear inverse leaves + // them un-undone. Uncorrected, the upright toolpaths float a fixed offset from the + // model shell. Recover the translation generally — independent of the offset's exact + // source or the axis remap — by anchoring the back-transformed object body onto the + // upright model bounding box (the same space the shells render in). + BoundingBoxf3 model_bb; + 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; + 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 (model_bb.defined && tp_bb.defined) { + 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() << ")"; + } + } libvgcode::GCodeInputData data = libvgcode::convert(gcode_result, str_tool_colors, str_color_print_colors, m_viewer, apply_belt ? &belt_inv : nullptr); diff --git a/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp index ffc549755b..015947e9bb 100644 --- a/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp +++ b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp @@ -275,25 +275,10 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve } ret.vertices.shrink_to_fit(); - // Belt designed view: the linear back-transform recovers the correct shape - // and orientation, but not the constant machine-frame origin offset baked - // into the G-code (e.g. the start-G-code belt advance + a G92 reset leaves a - // ~20 mm Z residual). Anchor the lowest extrusion to the belt entry (Y=0) so - // the toolpaths sit on the bed under the model shell. Independent of the - // offset's source, so it stays general across machines. - if (belt_xform != nullptr && !ret.vertices.empty()) { - // Anchor on the OBJECT extrusions only (layer_id >= 1): the start-G-code - // prime lines (layer 0) print at the belt origin, while the object prints - // after the start-G-code belt advance, so anchoring on the global min - // would lock onto the prime and leave the object offset. - float min_y = std::numeric_limits::max(); - for (const PathVertex& v : ret.vertices) - if (v.type == EMoveType::Extrude && v.layer_id >= 1 && v.position[1] < min_y) - min_y = v.position[1]; - if (min_y != std::numeric_limits::max() && std::abs(min_y) > 1e-3f) - for (PathVertex& v : ret.vertices) - v.position[1] -= min_y; - } + // Note: the belt designed-view anchoring (recovering the per-object placement/ + // lift translation the linear back-transform cannot) is folded into belt_xform + // by the caller (GCodeViewer::load_as_gcode), which anchors onto the upright + // model bounding box. Nothing extra to do here. ret.spiral_vase_mode = result.spiral_vase_mode;