From 8039d4d2acae4ca22894db268da066a8d7548657 Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Tue, 6 Oct 2026 18:14:34 -0500 Subject: [PATCH 1/3] 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 --- .../Custom/machine/fdm_belt_common.json | 1 - .../IdeaFormer/machine/fdm_belt_common.json | 1 - .../Printcepts/machine/fdm_belt_common.json | 1 - resources/shaders/110/phong.fs | 1 + resources/shaders/140/phong.fs | 1 + src/libslic3r/BeltGCode.cpp | 31 +-- src/libslic3r/BeltSliceStrategy.cpp | 11 +- src/libslic3r/BeltTransform.cpp | 97 +------- src/libslic3r/BeltTransform.hpp | 58 +---- src/libslic3r/CMakeLists.txt | 2 - src/libslic3r/FirstLayerPlane.cpp | 229 ------------------ src/libslic3r/FirstLayerPlane.hpp | 76 ------ src/libslic3r/GCode.cpp | 72 +----- src/libslic3r/GCode.hpp | 43 +--- src/libslic3r/GCode/BeltBackTransform.cpp | 12 +- src/libslic3r/GCode/BeltBackTransform.hpp | 16 +- src/libslic3r/GCode/GCodeProcessor.cpp | 36 +-- src/libslic3r/GCode/GCodeProcessor.hpp | 6 - src/libslic3r/Preset.cpp | 7 +- src/libslic3r/Print.cpp | 22 +- src/libslic3r/Print.hpp | 7 +- src/libslic3r/PrintApply.cpp | 13 +- src/libslic3r/PrintConfig.cpp | 136 ++--------- src/libslic3r/PrintConfig.hpp | 38 --- src/libslic3r/PrintObject.cpp | 7 +- src/libslic3r/PrintObjectSlice.cpp | 47 +--- src/slic3r/GUI/GCodeViewer.cpp | 7 +- src/slic3r/GUI/Tab.cpp | 87 ++----- tests/fff_print/test_gcodewriter.cpp | 2 - tests/fff_print/test_precise_seam.cpp | 1 - tests/fff_print/test_print.cpp | 17 +- tests/fff_print/test_skirt_brim.cpp | 4 - 32 files changed, 125 insertions(+), 964 deletions(-) delete mode 100644 src/libslic3r/FirstLayerPlane.cpp delete mode 100644 src/libslic3r/FirstLayerPlane.hpp diff --git a/resources/profiles/Custom/machine/fdm_belt_common.json b/resources/profiles/Custom/machine/fdm_belt_common.json index 19765b8d4e..96b3138832 100644 --- a/resources/profiles/Custom/machine/fdm_belt_common.json +++ b/resources/profiles/Custom/machine/fdm_belt_common.json @@ -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", diff --git a/resources/profiles/IdeaFormer/machine/fdm_belt_common.json b/resources/profiles/IdeaFormer/machine/fdm_belt_common.json index 60ef096de0..aacd36e118 100644 --- a/resources/profiles/IdeaFormer/machine/fdm_belt_common.json +++ b/resources/profiles/IdeaFormer/machine/fdm_belt_common.json @@ -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", diff --git a/resources/profiles/Printcepts/machine/fdm_belt_common.json b/resources/profiles/Printcepts/machine/fdm_belt_common.json index 2a392b4f60..96d38ac31a 100644 --- a/resources/profiles/Printcepts/machine/fdm_belt_common.json +++ b/resources/profiles/Printcepts/machine/fdm_belt_common.json @@ -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", diff --git a/resources/shaders/110/phong.fs b/resources/shaders/110/phong.fs index a47a24fdea..7d1e7bfe8c 100644 --- a/resources/shaders/110/phong.fs +++ b/resources/shaders/110/phong.fs @@ -41,6 +41,7 @@ struct SlopeDetection bool actived; float normal_z; mat3 volume_world_normal_matrix; + vec3 up_direction; }; uniform vec4 uniform_color; diff --git a/resources/shaders/140/phong.fs b/resources/shaders/140/phong.fs index 809621b76d..4cfae1e03e 100644 --- a/resources/shaders/140/phong.fs +++ b/resources/shaders/140/phong.fs @@ -44,6 +44,7 @@ struct SlopeDetection bool actived; float normal_z; mat3 volume_world_normal_matrix; + vec3 up_direction; }; uniform vec4 uniform_color; diff --git a/src/libslic3r/BeltGCode.cpp b/src/libslic3r/BeltGCode.cpp index a78c0cbfa6..45b0728810 100644 --- a/src/libslic3r/BeltGCode.cpp +++ b/src/libslic3r/BeltGCode.cpp @@ -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 diff --git a/src/libslic3r/BeltSliceStrategy.cpp b/src/libslic3r/BeltSliceStrategy.cpp index 8892b4e5de..aa01c3ca9e 100644 --- a/src/libslic3r/BeltSliceStrategy.cpp +++ b/src/libslic3r/BeltSliceStrategy.cpp @@ -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 diff --git a/src/libslic3r/BeltTransform.cpp b/src/libslic3r/BeltTransform.cpp index d1ea31d33c..168d9cde1c 100644 --- a/src/libslic3r/BeltTransform.cpp +++ b/src/libslic3r/BeltTransform.cpp @@ -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(), mx = bb.max.cast(); - 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 diff --git a/src/libslic3r/BeltTransform.hpp b/src/libslic3r/BeltTransform.hpp index 59669fe36a..01346464ae 100644 --- a/src/libslic3r/BeltTransform.hpp +++ b/src/libslic3r/BeltTransform.hpp @@ -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("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>(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); }; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 68a1434699..a108588f84 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -94,8 +94,6 @@ set(lisbslic3r_sources BeltSliceStrategy.hpp BeltTransform.cpp BeltTransform.hpp - FirstLayerPlane.cpp - FirstLayerPlane.hpp Brim.cpp BrimEarsPoint.hpp Brim.hpp diff --git a/src/libslic3r/FirstLayerPlane.cpp b/src/libslic3r/FirstLayerPlane.cpp deleted file mode 100644 index 7642d42267..0000000000 --- a/src/libslic3r/FirstLayerPlane.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#include "FirstLayerPlane.hpp" -#include "BeltTransform.hpp" -#include "BoundingBox.hpp" -#include "Point.hpp" -#include "PrintConfig.hpp" -#include "libslic3r.h" - -#include -#include -#include - -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 diff --git a/src/libslic3r/FirstLayerPlane.hpp b/src/libslic3r/FirstLayerPlane.hpp deleted file mode 100644 index 35b8346c82..0000000000 --- a/src/libslic3r/FirstLayerPlane.hpp +++ /dev/null @@ -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_ diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 46c1458c8b..8ad30a611a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -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(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 &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(bb.min.x()), unscale(bb.min.y())), - Vec2d(unscale(bb.max.x()), unscale(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 diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index c5268705c3..ccfd14dd39 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -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 m_first_layer_plane; // Plate origin, kept so a writer replaced during export can be given it again. std::unique_ptr 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. diff --git a/src/libslic3r/GCode/BeltBackTransform.cpp b/src/libslic3r/GCode/BeltBackTransform.cpp index 4b247323cd..e7257cf90e 100644 --- a/src/libslic3r/GCode/BeltBackTransform.cpp +++ b/src/libslic3r/GCode/BeltBackTransform.cpp @@ -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; diff --git a/src/libslic3r/GCode/BeltBackTransform.hpp b/src/libslic3r/GCode/BeltBackTransform.hpp index 878ae12b5a..69e32a4b2c 100644 --- a/src/libslic3r/GCode/BeltBackTransform.hpp +++ b/src/libslic3r/GCode/BeltBackTransform.hpp @@ -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 diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index fa418e1b62..1899e2358b 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -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("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; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index a5c5ce7914..cb8178d113 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -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).belt_tilt_angle; belt_z_origin = std::forward(other).belt_z_origin; machine_frame_transform_active = std::forward(other).machine_frame_transform_active; - preslice_remap_x = std::forward(other).preslice_remap_x; - preslice_remap_y = std::forward(other).preslice_remap_y; - preslice_remap_z = std::forward(other).preslice_remap_z; #if ENABLE_GCODE_VIEWER_STATISTICS time = std::forward(other).time; #endif diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index bc25c448dd..e1c1308076 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1560,13 +1560,12 @@ static std::vector s_Preset_machine_limits_options { static std::vector 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", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 9dc9ff3cd3..9aa9626e5e 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -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 *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 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++) { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 425912b9c2..5ee4815489 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -231,11 +231,10 @@ class ConstSupportLayerPtrsAdaptor : public ConstVectorOfPtrsAdaptor(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. diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 94725118c7..108e2cc796 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -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 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)); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ee23a88940..708e942359 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -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(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::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::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::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::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::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()) { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 1a9e3ea075..01ee8b4b0c 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -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, 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, preslice_remap_x)) - ((ConfigOptionEnum, preslice_remap_y)) - ((ConfigOptionEnum, preslice_remap_z)) - ((ConfigOptionBool, preslice_remap_global)) ((ConfigOptionEnum, gcode_remap_x)) ((ConfigOptionEnum, gcode_remap_y)) ((ConfigOptionEnum, gcode_remap_z)) ((ConfigOptionBool, gcode_back_transform)) ((ConfigOptionBool, belt_preslice_global)) - ((ConfigOptionEnum, first_layer_plane)) - ((ConfigOptionFloat, first_layer_plane_offset)) ((ConfigOptionFloat, first_layer_plane_thickness)) ((ConfigOptionFloat, belt_support_floor_offset)) ((ConfigOptionEnum, belt_support_floor_mode)) - ((ConfigOptionEnum, 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 diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 7a46f354fc..d11f0ec1a3 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -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; diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index b384a28ef3..46408fe886 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -343,8 +343,7 @@ static std::vector> 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(inst_shift.x()), unscale(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(inst_shift.x()), unscale(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(inst_shift.x()), unscale(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 diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index b03e6b5f03..49940229dc 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -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) { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index c64b4c8807..a2d0f3ea3e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -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>("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); diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index b0f36c33d9..9ef4a8f39d 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -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; diff --git a/tests/fff_print/test_precise_seam.cpp b/tests/fff_print/test_precise_seam.cpp index d5e2709cdd..b5cb913904 100644 --- a/tests/fff_print/test_precise_seam.cpp +++ b/tests/fff_print/test_precise_seam.cpp @@ -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 }, diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index c0e5572b97..f16a0ce662 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -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" }, diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp index 45086b9d2a..99e7860c3e 100644 --- a/tests/fff_print/test_skirt_brim.cpp +++ b/tests/fff_print/test_skirt_brim.cpp @@ -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 }, From dda58b07cdf8fa573141ad01443b8c35089fb805 Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Tue, 6 Oct 2026 22:14:25 -0500 Subject: [PATCH 2/3] Belt printer: supports reach the belt under a leading overhang The slicing frame of a belt object started at its lowest vertex, but the belt under the leading end of an overhang lies below that, by the overhang's length times the tilt's shear. Every support generator works in layers at z >= 0, so none of them could reach it: normal supports stopped at the object's own lowest layer, and the two tree generators each carried a stack of hacks to extend themselves below it (a post-hoc copy of the lowest base area in TreeSupport, "virtual belt raft layers" in TreeSupport3D/TreeModelVolumes), sized from the pre-rotation bbox and capped at global z = 0, which is only right for the trailing half of the belt. Start the frame at the lowest belt-floor point under the footprint instead, less a 10 mm margin along the belt for the base of a support column (BeltSliceStrategy::apply_preslice_transforms and BeltTransformPipeline::compute_belt_height_and_floor agree on it). The layers between it and the first vertex come out empty, which belt slicing already tolerates, and the generators need no extension at all: - normal supports: the generator anchors its layer grid at the frame origin, so run it in the object frame and shift the global belt Z offset onto the result afterwards, as organic supports already did. With the offset on the object layers a top contact at negative z turned the intermediate-layer count negative and the generator allocated layers until the kernel killed it (any overhang in the leading half of the belt). Drop the first-layer flange expansion on a belt: the first support layer is the leading tip of the support, not a flange, and inflating it put lines in the air ahead of the belt. - classic tree: a node now keeps dropping until its whole circle is in the belt, so the branch tapers to a tip on the belt instead of stopping, a radius above it, when its centre crosses. - organic: the belt is no longer a support blocker. A blocker is a collision, and a branch descending onto one slides off it, down the tilted belt and ahead of the part; the belt is where branches end, which the per-layer m_belt_floor clipping already does. The belt brim is generated after the parallel support step instead of inside it: belt_brim_obstacles() reads every object's layers and support layers, which another object's support step rebuilds (and, now, shifts) at the same time. This is the race behind the Windows arm64 segfault in "Belt brim of each object precedes its perimeters on its own filament". Also: the belt tilt axis moves to Developer mode as its own row (a shared line is shown by its first option's mode), first_layer_plane band thickness, belt_support_floor_mode, belt_preslice_global and gcode_back_transform are retired and presumed on, the gravity arrow is a plain line along the up direction, and the "Show raw G-code (belt only)" preview toggle is gone. Regression test: "Belt supports reach the belt under a leading overhang" slices a cube with a fin whose underside is parallel to the layers, 20 mm ahead of the cube and up to 41 mm of slicing Z above the belt, for normal, organic and classic tree supports, and checks that the lowest support layer sits on the belt beneath its own lines. Co-Authored-By: Claude Fable 5.1 --- src/libslic3r/BeltGCode.cpp | 12 +- src/libslic3r/BeltSliceStrategy.cpp | 18 ++ src/libslic3r/BeltTransform.cpp | 74 +++++--- src/libslic3r/BeltTransform.hpp | 13 ++ src/libslic3r/GCode.hpp | 10 +- src/libslic3r/GCode/BeltBackTransform.cpp | 7 +- src/libslic3r/GCode/BeltBackTransform.hpp | 2 +- src/libslic3r/GCode/GCodeProcessor.cpp | 4 +- src/libslic3r/Preset.cpp | 6 +- src/libslic3r/Print.cpp | 25 +-- src/libslic3r/Print.hpp | 11 +- src/libslic3r/PrintApply.cpp | 10 +- src/libslic3r/PrintConfig.cpp | 64 +------ src/libslic3r/PrintConfig.hpp | 11 -- src/libslic3r/PrintObject.cpp | 81 +++++---- src/libslic3r/PrintObjectSlice.cpp | 3 +- src/libslic3r/Support/SupportCommon.cpp | 8 +- src/libslic3r/Support/SupportMaterial.cpp | 5 +- src/libslic3r/Support/TreeModelVolumes.cpp | 59 ++----- src/libslic3r/Support/TreeSupport.cpp | 186 ++++----------------- src/libslic3r/Support/TreeSupport.hpp | 7 + src/libslic3r/Support/TreeSupport3D.cpp | 41 +---- src/slic3r/GUI/3DBed.cpp | 27 +-- src/slic3r/GUI/3DBed.hpp | 1 + src/slic3r/GUI/GCodeViewer.cpp | 66 ++------ src/slic3r/GUI/GCodeViewer.hpp | 6 - src/slic3r/GUI/GLCanvas3D.cpp | 24 --- src/slic3r/GUI/GUI_Preview.cpp | 11 -- src/slic3r/GUI/GUI_Preview.hpp | 1 - src/slic3r/GUI/Plater.cpp | 5 - src/slic3r/GUI/Plater.hpp | 1 - src/slic3r/GUI/Shortcuts.cpp | 1 - src/slic3r/GUI/Shortcuts.hpp | 2 +- src/slic3r/GUI/Tab.cpp | 31 +--- tests/fff_print/test_gcodewriter.cpp | 3 - tests/fff_print/test_print.cpp | 102 ++++++++++- tests/fff_print/test_skirt_brim.cpp | 92 +++++----- 37 files changed, 412 insertions(+), 618 deletions(-) diff --git a/src/libslic3r/BeltGCode.cpp b/src/libslic3r/BeltGCode.cpp index 45b0728810..1726fc6eac 100644 --- a/src/libslic3r/BeltGCode.cpp +++ b/src/libslic3r/BeltGCode.cpp @@ -25,7 +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_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). file.write_format("; belt_frame_tilt_decouple = %d\n", print.config().belt_frame_tilt_decouple.value ? 1 : 0); @@ -34,13 +33,10 @@ 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 (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; + // 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. // Adjust origin: transform through belt forward pipeline so that // the back-transform correctly recovers model-space positions. diff --git a/src/libslic3r/BeltSliceStrategy.cpp b/src/libslic3r/BeltSliceStrategy.cpp index aa01c3ca9e..8aac729f1b 100644 --- a/src/libslic3r/BeltSliceStrategy.cpp +++ b/src/libslic3r/BeltSliceStrategy.cpp @@ -38,6 +38,20 @@ void BeltSliceStrategy::apply_preslice_transforms(Transform3d &trafo, // coordinates rather than object-space coordinates, so volumes translated along // the slicer's Z axis would be silently excluded from the bound check. + // + // The lift is measured to the lowest point of the SUPPORT region, not of the + // mesh: the belt floor (z = shear * u in this rotated frame, u the from-axis + // coordinate) runs below every vertex, and under the leading end of an + // overhang it lies below the lowest vertex by up to the overhang's length + // times the shear. Supports have to reach that floor, and every support + // generator works in layers at z >= 0, so z = 0 has to be the lowest floor + // point under the footprint. The layers between it and the first vertex + // come out empty, which belt slicing already tolerates (the bottom corner + // of a tilted part is a point). Vertices on the belt have z == floor, so + // for a part resting on the belt this is simply the floor at its leading + // extreme, less the frame margin (see BeltTransformPipeline::frame_margin). + BeltTransformPipeline::BeltFloorParams floor; + const bool has_floor = BeltTransformPipeline::floor_shear(config, floor); double min_z = std::numeric_limits::max(); for (const ModelVolume *mv : model_volumes) { if (!mv->is_model_part()) continue; @@ -47,8 +61,12 @@ void BeltSliceStrategy::apply_preslice_transforms(Transform3d &trafo, Vec3d vm = v.cast(); Vec3d pt = vol_trafo * vm; min_z = std::min(min_z, pt.z()); + if (has_floor) + min_z = std::min(min_z, floor.shear_factor * (floor.from_axis == 0 ? pt.x() : pt.y())); } } + if (has_floor && min_z != std::numeric_limits::max()) + min_z -= BeltTransformPipeline::frame_margin(floor); const double z_shift_val = (min_z < 0. && min_z != std::numeric_limits::max()) ? -min_z : 0.; if (z_shift_val > 0.) { Transform3d z_shift = Transform3d::Identity(); diff --git a/src/libslic3r/BeltTransform.cpp b/src/libslic3r/BeltTransform.cpp index 168d9cde1c..d11e2fccdc 100644 --- a/src/libslic3r/BeltTransform.cpp +++ b/src/libslic3r/BeltTransform.cpp @@ -50,6 +50,37 @@ Transform3d BeltTransformPipeline::build_forward_transform(const PrintConfig &co // Template avoids duplicating the math for the two config types. namespace { +// Belt floor in the rotated slicer frame: the image of z_machine = 0 under R. +// R(+α, X): point (·, y, 0) → (·, cos α · y, sin α · y) ⇒ z = tan(α) · y_s +// R(+α, Y): point (x, ·, 0) → (cos α · x, ·, -sin α · x) ⇒ z = -tan(α) · x_s +// R(+α, Z): point (·, ·, 0) → (·, ·, 0); no tilt → no floor +void belt_floor_shear(BeltRotationAxis rot_axis, double angle_rad, BeltTransformPipeline::BeltFloorParams &out) +{ + double sin_a = std::sin(angle_rad), cos_a = std::cos(angle_rad); + switch (rot_axis) { + case BeltRotationAxis::X: + out.shear_factor = (std::abs(cos_a) > EPSILON) ? sin_a / cos_a : 0.; + out.from_axis = 1; // Y + break; + case BeltRotationAxis::Y: + out.shear_factor = (std::abs(cos_a) > EPSILON) ? -sin_a / cos_a : 0.; + out.from_axis = 0; // X + break; + case BeltRotationAxis::Z: + default: + out.shear_factor = 0.0; + out.from_axis = 1; + break; + } +} + +// Z of the belt floor directly under a point of the rotated (unshifted) frame. +inline double belt_floor_z(const BeltTransformPipeline::BeltFloorParams &fp, const Vec3d &pt) +{ + return fp.shear_factor * (fp.from_axis == 0 ? pt.x() : pt.y()); +} + + template BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl( const Config &config, const BoundingBoxf3 &bb, double original_height) @@ -93,38 +124,28 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl( default: unit_axis = Vec3d::UnitX(); break; } Matrix3d R = Eigen::AngleAxisd(angle_rad, unit_axis).toRotationMatrix(); + belt_floor_shear(rot_axis, angle_rad, result.floor_params); + // The slicing frame starts at the lowest point of the support region: the + // lowest belt-floor point under the footprint, not the lowest vertex. The + // belt under the leading end of an overhang lies below every vertex of the + // part, and supports have to be able to reach it (see + // BeltSliceStrategy::apply_preslice_transforms for the exact vertex-scan + // counterpart of this bbox estimate). double min_rz = std::numeric_limits::max(); double max_rz = std::numeric_limits::lowest(); for (int i = 0; i < 8; ++i) { Vec3d c((i & 1) ? bb.max.x() : bb.min.x(), (i & 2) ? bb.max.y() : bb.min.y(), (i & 4) ? bb.max.z() : bb.min.z()); - double z = (R * c).z(); + Vec3d rc = R * c; + double z = rc.z(); min_rz = std::min(min_rz, z); max_rz = std::max(max_rz, z); + min_rz = std::min(min_rz, belt_floor_z(result.floor_params, rc)); } + min_rz -= BeltTransformPipeline::frame_margin(result.floor_params); result.object_height = max_rz - min_rz; - // Belt floor in slicer-frame is the image of z_machine = 0 under R. - // R(+α, X): point (·, y, 0) → (·, cos α · y, sin α · y) ⇒ z = tan(α) · y_s - // R(+α, Y): point (x, ·, 0) → (cos α · x, ·, -sin α · x) ⇒ z = -tan(α) · x_s - // R(+α, Z): point (·, ·, 0) → (·, ·, 0); no tilt → no floor - double sin_a = std::sin(angle_rad), cos_a = std::cos(angle_rad); - switch (rot_axis) { - case BeltRotationAxis::X: - result.floor_params.shear_factor = (std::abs(cos_a) > EPSILON) ? sin_a / cos_a : 0.; - result.floor_params.from_axis = 1; // Y - break; - case BeltRotationAxis::Y: - result.floor_params.shear_factor = (std::abs(cos_a) > EPSILON) ? -sin_a / cos_a : 0.; - result.floor_params.from_axis = 0; // X - break; - case BeltRotationAxis::Z: - default: - result.floor_params.shear_factor = 0.0; - result.floor_params.from_axis = 1; - break; - } result.floor_params.z_shift = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.); return result; @@ -144,4 +165,15 @@ BeltTransformPipeline::BeltHeightResult BeltTransformPipeline::compute_belt_heig return compute_belt_height_and_floor_impl(config, bbox, original_height); } +bool BeltTransformPipeline::floor_shear(const PrintConfig &config, BeltFloorParams &out) +{ + out = BeltFloorParams{}; + const BeltRotationAxis rot_axis = config.belt_slice_rotation.value; + const double rot_angle = config.belt_slice_rotation_angle.value; + if (rot_axis == BeltRotationAxis::None || std::abs(rot_angle) <= EPSILON) + return false; + belt_floor_shear(rot_axis, Geometry::deg2rad(rot_angle), out); + return std::abs(out.shear_factor) > EPSILON; +} + } // namespace Slic3r diff --git a/src/libslic3r/BeltTransform.hpp b/src/libslic3r/BeltTransform.hpp index 01346464ae..7409fb2c7f 100644 --- a/src/libslic3r/BeltTransform.hpp +++ b/src/libslic3r/BeltTransform.hpp @@ -108,6 +108,19 @@ public: double z_shift = 0.0; }; + // Shear factor and from-axis of the belt floor in the rotated slicer frame + // (z_floor = shear_factor * u, u = the from-axis coordinate), for the + // rotation the config selects. z_shift is left at 0. Returns false (and + // zero shear) when the config has no tilt. + static bool floor_shear(const PrintConfig &config, BeltFloorParams &out); + + // How far below the lowest belt-floor point under the footprint the slicing + // frame starts, in slicing Z. A support column meeting the belt is wider at + // its base than at its tip, so under a leading overhang the base reaches ahead + // of the part along the belt, and the layers that trim it to the belt plane + // lie below that lowest point: 10 mm along the belt. + static double frame_margin(const BeltFloorParams &fp) { return 10. * std::abs(fp.shear_factor); } + // Result of computing belt height + floor params. struct BeltHeightResult { double object_height; // Effective object height after shear/scale diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index ccfd14dd39..4aa96f9a4c 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -957,14 +957,10 @@ protected: return on_first_layer() ? 0 : layer_id(); } - // 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. + // Band thickness for the *effective layer index*: one first layer height, so + // "the first N layers" means the same height above the belt as on a flat bed. 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; + const double band = m_config.initial_layer_print_height.value; return band > 0. ? band : 0.2; } diff --git a/src/libslic3r/GCode/BeltBackTransform.cpp b/src/libslic3r/GCode/BeltBackTransform.cpp index e7257cf90e..3d2cabc718 100644 --- a/src/libslic3r/GCode/BeltBackTransform.cpp +++ b/src/libslic3r/GCode/BeltBackTransform.cpp @@ -10,12 +10,7 @@ bool BeltBackTransform::init_from_config(const PrintConfig &config) m_active = false; m_inverse = Transform3d::Identity(); - if (!config.belt_printer.value || !config.gcode_back_transform.value) - return false; - - // 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) + if (!config.belt_printer.value) return false; // Build the forward pipeline (the rotation) and store its inverse. diff --git a/src/libslic3r/GCode/BeltBackTransform.hpp b/src/libslic3r/GCode/BeltBackTransform.hpp index 69e32a4b2c..3956450a9f 100644 --- a/src/libslic3r/GCode/BeltBackTransform.hpp +++ b/src/libslic3r/GCode/BeltBackTransform.hpp @@ -14,7 +14,7 @@ namespace Slic3r { // Initialized once from PrintConfig, then applied per-point in // BeltKinematics::to_machine() before axis remapping. // -// Active when gcode_back_transform and belt_preslice_global are both set. +// Active on belt printers with a non-identity pre-slice rotation. class BeltBackTransform { public: diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 1899e2358b..1ff76dd9fa 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3220,8 +3220,8 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) const auto *belt = config.option("belt_printer"); if (belt != nullptr) { static const char *belt_keys[] = { - "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_printer", "belt_slice_rotation", "belt_slice_rotation_angle", + "gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "belt_frame_tilt_decouple", "belt_frame_tilt_angle", }; for (const char *key : belt_keys) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e1c1308076..4b2b28d4a8 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1561,11 +1561,9 @@ static std::vector 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", - "gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "gcode_back_transform", + "gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "belt_frame_tilt_decouple", "belt_frame_tilt_angle", - "belt_preslice_global", - "first_layer_plane_thickness", - "belt_support_floor_offset", "belt_support_floor_mode", + "belt_support_floor_offset", "enable_belt_purge_tower", "gcode_flavor", "gcode_skip_config_block", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 9aa9626e5e..9f7adaf86d 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -176,8 +176,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "gcode_remap_z", // 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_thickness", // Only inflates the GUI bed volume, like printable_area. "belt_printer_infinite_y", //BBS @@ -388,12 +386,10 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n // Belt printer transform options change the mesh geometry before slicing. || opt_key == "belt_printer" || opt_key == "belt_slice_rotation" - || opt_key == "belt_slice_rotation_angle" - || opt_key == "belt_preslice_global") { + || opt_key == "belt_slice_rotation_angle") { osteps.emplace_back(posSlice); } else if ( - opt_key == "belt_support_floor_offset" - || opt_key == "belt_support_floor_mode") { + opt_key == "belt_support_floor_offset") { osteps.emplace_back(posSupportMaterial); } else if ( opt_key == "print_sequence" @@ -1908,14 +1904,6 @@ StringObjectException Print::validate(std::vector *warnin "which is the edge it is meant to anchor. Set the gap to 0 when using leading " "brim length."), "brim_object_gap", object->model_object()); - - // Unconditional: this suppresses the WHOLE belt brim, not just the apron, so a - // user asking for any brim at all needs to be told they are getting none. - if (! object->belt_brim_instances_compatible()) - warn(L("This object's copies are spaced along the belt, so they would each need " - "their own brim and none is generated. Print them as separate objects, or " - "arrange the copies side by side across the belt."), - "brim_type", object->model_object()); } if (this->has_belt_brim() && m_objects.size() > 1) warn(L("Leading brim length extends ahead of each object along the belt, and Arrange does " @@ -3061,7 +3049,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) std::set 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_preslice_global.value; + bool belt_no_share = m_config.belt_printer.value; if (!use_cache) { for (int index = 0; index < object_count; index++) { @@ -3221,7 +3209,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) for (int i = range.begin(); i < range.end(); i++) { PrintObject* obj = m_objects[i]; if (need_slicing_objects.count(obj) != 0) { - obj->generate_support_material(); + // The belt brim follows sequentially below. + obj->generate_support_material(false); } else { if (obj->set_started(posSupportMaterial)) @@ -3230,6 +3219,10 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) } } ); + // The belt brim keeps clear of every object's layers and support layers, + // so it runs once no support step is rebuilding them any more. + for (PrintObject *obj : m_objects) + obj->generate_belt_brim(); if (m_pipeline_plugin_active) for (size_t i = 0; i < m_objects.size(); ++i) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 5ee4815489..f60f6b4e19 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -439,7 +439,6 @@ public: unsigned int belt_brim_filament() const; // False when this object's instances sit at different points ALONG the belt, which // would need a separate set of bands each. Public so validate() can explain it. - bool belt_brim_instances_compatible() const; const std::vector& belt_brim_by_layer() const { return m_belt_brim_by_layer; } const std::vector& belt_brim_areas_by_layer() const { return m_belt_brim_areas_by_layer; } const std::vector& belt_brim_prologue() const { return m_belt_brim_prologue; } @@ -583,7 +582,13 @@ private: void ironing(); bool need_z_contouring() const; void contour_z(); - void generate_support_material(); + // with_belt_brim = false leaves the belt brim to generate_belt_brim(), for a + // caller that runs the support step of several objects in parallel. + void generate_support_material(bool with_belt_brim = true); + // The belt brim keeps clear of every object's layers and support layers, so + // it has to run after all support steps finished. A no-op unless a support + // step left it pending. + void generate_belt_brim(); void estimate_curled_extrusions(); void simplify_extrusion_path(); @@ -712,6 +717,8 @@ private: // Belt printer: global Z offset applied to this object's layers for shear positioning. double m_belt_global_z_offset { 0.0 }; + // generate_support_material(false) finished and generate_belt_brim() has not run yet. + bool m_belt_brim_pending { false }; // Belt printer: min_z of mesh after belt shear (before Z-shift), for z_offset calc. double m_belt_min_z { 0.0 }; // Belt printer: XY correction from global pre-slice mode, applied to G-code origin. diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 108e2cc796..39d5716b08 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1842,9 +1842,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ for (ModelObject *model_object : m_model.objects) { ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(*model_object)); // 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_preslice_global.value; + // Belt printers: force each instance into its own PrintObject so each + // gets independent layer Z values (its bed position is folded into them). + bool belt_force_separate = m_config.belt_printer.value; model_object_status.print_instances = print_objects_from_model_object(*model_object, this->shrinkage_compensation(), belt_force_separate); std::vector old; old.reserve(print_object_status_db.count(*model_object)); @@ -1933,9 +1933,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Belt printer global mode: when any object's instances shifted, // recompute m_belt_global_z_offset for ALL objects (it depends on // min_shift across all objects, so one move affects everyone). - if (belt_instances_shifted - && m_config.belt_printer.value - && m_config.belt_preslice_global.value) { + if (belt_instances_shifted && m_config.belt_printer.value) { for (PrintObject *object : m_objects) update_apply_status(object->invalidate_step(posSlice)); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 708e942359..8e5b2e2b0f 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -398,12 +398,6 @@ static t_config_enum_values s_keys_map_RemapAxis { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(RemapAxis) -static t_config_enum_values s_keys_map_BeltSupportFloorMode { - { "none", int(BeltSupportFloorMode::None) }, - { "generator_only", int(BeltSupportFloorMode::GeneratorOnly) }, -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltSupportFloorMode) - static t_config_enum_values s_keys_map_SupportMaterialPattern { { "rectilinear", smpRectilinear }, { "rectilinear-grid", smpRectilinearGrid }, @@ -7390,7 +7384,7 @@ void PrintConfigDef::init_fff_params() def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values = {"none", "x", "y", "z"}; def->enum_labels = {L("None"), L("X"), L("Y"), L("Z")}; - def->mode = comAdvanced; + def->mode = comDevelop; def->set_default_value(new ConfigOptionEnum(BeltRotationAxis::X)); def = this->add("belt_slice_rotation_angle", coFloat); @@ -7451,38 +7445,6 @@ void PrintConfigDef::init_fff_params() // by per-axis keys: it is derived from the belt tilt (belt_slice_rotation axis // + angle, or belt_frame_tilt_angle when decoupled) in MachineFrameTransform. - def = this->add("gcode_back_transform", coBool); - def->label = L("G-code back-transform"); - def->category = L("Printable space"); - def->tooltip = L("Undo the pre-slice mesh transform before applying the G-code axis remap " - "and machine-frame shear/scale. Required for the standard belt-printer " - "rotation pipeline."); - def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("belt_preslice_global", coBool); - def->label = L("Global mesh transforms"); - def->category = L("Printable space"); - def->tooltip = L("When enabled, pre-slice belt transforms (remap, shear, scale) account for " - "each object's bed position, producing correct machine coordinates without " - "relying on origin snap. Each instance gets its own PrintObject."); - def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("first_layer_plane_thickness", coFloat); - def->label = L("First layer band thickness"); - def->category = L("Printable space"); - 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; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(-1.0)); - // Belt support floor debug controls def = this->add("belt_support_floor_offset", coFloat); def->label = L("Support Floor Z offset"); @@ -7494,19 +7456,6 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0)); - { - auto def = this->add("belt_support_floor_mode", coEnum); - def->label = L("Floor mode"); - def->category = L("Printable space"); - def->tooltip = L("Controls belt floor awareness for supports. 'None' disables belt floor logic. " - "'Generator only' stops support generation at the belt floor plane."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values = {"none", "generator_only"}; - def->enum_labels = {L("None"), L("Generator only")}; - def->mode = comDevelop; - def->set_default_value(new ConfigOptionEnum(BeltSupportFloorMode::GeneratorOnly)); - } - def = this->add("enable_belt_purge_tower", coBool); def->label = L("Enable belt purge tower"); def->category = L("Multimaterial"); @@ -9440,9 +9389,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va //BBS: handle legacy options if (opt_key == "curr_bed_type" && value == "SuperTack Plate") { value = "Supertack Plate"; - } else if (opt_key == "belt_support_floor_mode" && (value == "clip_only" || value == "both")) { - // Never implemented; both behaved like "none". - value = "none"; } else if (opt_key == "enable_wipe_tower") { opt_key = "enable_prime_tower"; } else if (opt_key == "wipe_tower_width") { @@ -9682,11 +9628,13 @@ 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 printer keys retired before the first release: the global-mode and + // back-transform switches are presumed on, and the pre-slice axis remap, the + // support Z offset mode, the support floor mode (always on) and the first-layer + // plane evaluator 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", + "belt_preslice_global", "gcode_back_transform", "belt_support_floor_mode", "first_layer_plane_thickness", }; if (ignore.find(opt_key) != ignore.end()) { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 01ee8b4b0c..05ee97a806 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -294,12 +294,6 @@ enum class RemapAxis RevX = 6, RevY = 7, RevZ = 8, // Reversed: max - pos }; -enum class BeltSupportFloorMode -{ - None, // No belt floor awareness - GeneratorOnly, // Only in tree support drop_nodes/contact_points -}; - enum SupportMaterialPattern { smpDefault, smpRectilinear, smpRectilinearGrid, smpHoneycomb, @@ -746,7 +740,6 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(IroningType) 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(SupportMaterialPattern) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern) @@ -1875,11 +1868,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionEnum, gcode_remap_x)) ((ConfigOptionEnum, gcode_remap_y)) ((ConfigOptionEnum, gcode_remap_z)) - ((ConfigOptionBool, gcode_back_transform)) - ((ConfigOptionBool, belt_preslice_global)) - ((ConfigOptionFloat, first_layer_plane_thickness)) ((ConfigOptionFloat, belt_support_floor_offset)) - ((ConfigOptionEnum, belt_support_floor_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 diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d11f0ec1a3..583a72766c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -984,7 +984,7 @@ void PrintObject::detect_overhangs_for_lift() } } -void PrintObject::generate_support_material() +void PrintObject::generate_support_material(bool with_belt_brim) { if (this->set_started(posSupportMaterial)) { this->clear_support_layers(); @@ -1032,12 +1032,25 @@ void PrintObject::generate_support_material() // those must exist before ToolOrdering is built at psWipeTower - one step // ahead of psSkirtBrim. The brim options already invalidate // posSupportMaterial, so this needs no extra invalidation edges. - make_belt_brim(*this); - m_print->throw_if_canceled(); + m_belt_brim_pending = true; + if (with_belt_brim) + this->generate_belt_brim(); this->set_done(posSupportMaterial); } } +void PrintObject::generate_belt_brim() +{ + if (! m_belt_brim_pending) + return; + // belt_brim_obstacles() looks up the layers and support layers of every object + // on the plate by print_z. Another object's support step rebuilds those (and + // temporarily shifts its layer Z values), so this must not overlap with it. + make_belt_brim(*this); + m_print->throw_if_canceled(); + m_belt_brim_pending = false; +} + void PrintObject::estimate_curled_extrusions() { if (this->set_started(posEstimateCurledExtrusions)) { @@ -1245,8 +1258,6 @@ bool PrintObject::has_belt_brim() const // this keeps it brimless whatever its config says, so a brim on the parts never blocks purging. if (m_config.belt_purge_tower_object.value) return false; - if (! this->belt_brim_instances_compatible()) - return false; if (m_config.brim_type == btNoBrim) return false; // An inner-only brim has no leading/extra geometry: leading_brim_length and @@ -1280,30 +1291,6 @@ unsigned int PrintObject::belt_brim_filament() const return brim_filament == 0 ? 1u : brim_filament; } -bool PrintObject::belt_brim_instances_compatible() const -{ - // One set of bands is shared by every instance of this object, so they must all sit at - // the same height on the belt. Moving an instance ALONG the belt axis changes its - // physical belt-floor Z and would put its brim at the wrong height; moving it ACROSS - // the belt does not, so side-by-side copies are fine. - // - // belt_force_separate() in PrintApply.cpp already gives one instance per PrintObject - // whenever a global belt flag is set, which the shipped belt profiles do - this only - // matters for configurations that do not. - if (m_instances.size() <= 1) - return true; - // From the config, not m_slicing_params: this runs while those can be stale. A tilt - // about Y runs the belt along X, any other tilt along Y (see compute_belt_height_and_floor). - const int axis = m_print->config().belt_slice_rotation.value == BeltRotationAxis::Y ? 0 : 1; - const Point &ref = m_instances.front().shift; - for (const PrintInstance &inst : m_instances) { - const coord_t along = axis == 0 ? inst.shift.x() - ref.x() : inst.shift.y() - ref.y(); - if (std::abs(along) > SCALED_EPSILON) - return false; - } - return true; -} - void PrintObject::clear_belt_brim() { m_belt_brim_by_layer.clear(); @@ -4741,11 +4728,41 @@ void PrintObject::_generate_support_material() tree_support.generate(); } else { - PrintObjectSupportMaterial support_material(this, m_slicing_params); - support_material.generate(*this); + // The normal generator anchors its layer grid at the slicing frame origin + // (SlicingParameters: first layer at first_print_layer_height, raft at + // z = 0), so it has to see the object layers in that frame. On a belt the + // object layers carry the global Z offset (PrintObject::slice()), which is + // negative for the leading half of the belt: a top contact below z = 0 + // then turns the intermediate-layer count negative and the generator + // allocates layers until memory runs out. Lift the offset off the object + // layers and the belt floor for the duration of the run and put it back + // on everything, including the new support layers, afterwards (organic + // tree support is shifted the same way below). + const double global_z = m_belt_global_z_offset; + const bool unshift = std::abs(global_z) > EPSILON; + auto shift_object_frame = [this, global_z](double sign) { + for (Layer *layer : m_layers) + layer->print_z += sign * global_z; + m_slicing_params.belt_floor_z_shift += sign * global_z; + }; + if (unshift) + shift_object_frame(-1.); + try { + PrintObjectSupportMaterial support_material(this, m_slicing_params); + support_material.generate(*this); + } catch (...) { + if (unshift) + shift_object_frame(1.); + throw; + } + if (unshift) { + shift_object_frame(1.); + for (SupportLayer *sl : m_support_layers) + sl->print_z += global_z; + } } // Global Z offset for support layers: - // - Normal support: layers already inherit global_z_offset from object layers. + // - Normal support: generated in the object frame above and shifted afterwards. // - Non-organic tree support (slim/strong/hybrid): plan_layer_heights() reads // from globally-offset object layers, so support layers already have it. // - Organic tree support: generate_tree_support_3D() computes its own Z values diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 46408fe886..5f70887c9c 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -976,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_preslice_global=" << pcfg.belt_preslice_global.value << " object=" << this->model_object()->name; if (pcfg.belt_printer.value) { @@ -1026,7 +1025,7 @@ void PrintObject::slice() global_z_offset += centering_z_corr; } - 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, so // objects at different bed positions print at different machine Z values diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index f7d173bb60..481213fe48 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -320,7 +320,13 @@ SupportGeneratorLayersPtr generate_raft_base( // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); - const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); + // On a belt the first support layer is the leading tip of the support, a sliver + // where the belt crosses the layer, not a flange on a flat bed: inflating it + // puts lines in the air ahead of the belt crossing (and into the belt behind + // it). The belt brim takes the adhesion role instead. + const bool belt_floor_active = std::abs(slicing_params.belt_floor_shear_factor) > EPSILON; + const float inflate_factor_1st_layer = belt_floor_active ? 0.f : + std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index d8b712b55e..17dc858262 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -2768,8 +2768,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_ //const auto expansion_to_slice = m_support_material_flow.scaled_spacing() / 2 + 25; const SupportGridParams grid_params(*m_object_config, m_support_params.support_material_flow); const bool buildplate_only = ! buildplate_covered.empty(); - const bool has_belt_floor = std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON - && m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly; + const bool has_belt_floor = std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON; // Allocate empty surface areas, one per object layer. layer_support_areas.assign(object.total_layer_count(), Polygons()); @@ -3311,8 +3310,6 @@ static void trim_support_layers_by_belt_floor( BeltFloorContext ctx; if (!ctx.init(slicing_params, print_config)) return; - if (print_config.belt_support_floor_mode.value != BeltSupportFloorMode::GeneratorOnly) - return; tbb::parallel_for(tbb::blocked_range(0, support_layers.size()), [&](const tbb::blocked_range &range) { diff --git a/src/libslic3r/Support/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp index f3e322154a..33d379c2b5 100644 --- a/src/libslic3r/Support/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -117,58 +117,20 @@ TreeModelVolumes::TreeModelVolumes( m_increase_until_radius = config.increase_radius_until_radius; m_radius_0 = config.getRadius(0); m_raft_layers = config.raft_layers; - // Belt printer: add virtual belt raft layers below the object, matching - // the extra layers added in generate_support_areas() so both use the - // same layer indexing. - { - const auto &sp2 = print_object.slicing_parameters(); - const auto &pcfg2 = print_object.print()->config(); - double belt_sf = sp2.belt_floor_shear_factor; - if (std::abs(belt_sf) > EPSILON && std::abs(print_object.belt_global_z_offset()) > EPSILON - && pcfg2.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { - double bb_min_z = std::abs(belt_remapped_bbox(*print_object.model_object(), pcfg2).min.z()); - double extra_depth = bb_min_z + 10.; - int num_extra = std::max(0, (int)std::ceil(extra_depth / sp2.layer_height)); - if (num_extra > 0) { - std::vector belt_layers; - belt_layers.reserve(num_extra); - for (int i = num_extra; i >= 1; --i) - belt_layers.push_back(sp2.first_object_layer_height - i * sp2.layer_height); - m_raft_layers.insert(m_raft_layers.begin(), belt_layers.begin(), belt_layers.end()); - } - } - } - // Belt floor: add belt surface polygons to anti_overhang so support is - // never generated inside the belt. + // Support blockers are consumed in the same index space as m_layer_outlines + // (object layer i lives at index num_raft_layers + i), but + // slice_support_blockers() returns them in object-layer space. Shift them. // - // This MUST run after m_raft_layers is final. m_anti_overhang is consumed - // in the same index space as m_layer_outlines -- object layer i lives at - // index num_raft_layers + i -- but slice_support_blockers() returns it in - // object-layer space. Without the shift below, every entry lands - // num_raft_layers too low: with the belt raft that is tens of layers, so - // the belt suppression is applied to the wrong layers entirely and the - // topmost object layers get none at all. + // The belt surface is deliberately NOT a blocker. A blocker is a collision, + // and a branch descending onto a collision slides off it: on a belt that + // walks the branch down the tilted surface, ahead of the part, until it + // reaches the bottom layer floating in mid-air. The belt is where branches + // END: organic_draw_branches() clips their slices with m_belt_floor and the + // first clipped slice is the contact. { const size_t num_raft = m_raft_layers.size(); - const size_t num_obj = print_object.layer_count(); if (num_raft > 0 && ! m_anti_overhang.empty()) - // Shift the support blockers into the same space. m_anti_overhang.insert(m_anti_overhang.begin(), num_raft, Polygons{}); - const auto &sp = print_object.slicing_parameters(); - const auto &pcfg = print_object.print()->config(); - BeltFloorContext ctx; - ctx.init_local(sp, pcfg, print_object.belt_global_z_offset()); - if (ctx.is_active() - && std::abs(print_object.belt_global_z_offset()) > EPSILON - && pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { - if (m_anti_overhang.size() < num_raft + num_obj) - m_anti_overhang.resize(num_raft + num_obj, Polygons{}); - for (size_t i = 0; i < num_obj; ++i) { - const double print_z = print_object.get_layer(i)->print_z - - print_object.belt_global_z_offset(); - append(m_anti_overhang[num_raft + i], ctx.surface_polygon(print_z)); - } - } } m_current_outline_idx = 0; @@ -192,8 +154,7 @@ TreeModelVolumes::TreeModelVolumes( const auto &pcfg2 = print_object.print()->config(); BeltFloorContext ctx; ctx.init_local(slicing_params, pcfg2, print_object.belt_global_z_offset()); - if (ctx.is_active() - && pcfg2.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { + if (ctx.is_active()) { m_belt_floor = ctx.compute_per_layer_floors(num_layers, [&](size_t layer_idx) -> double { // Object layers: local print_z (subtract global offset). if (layer_idx >= num_raft_layers) diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 31b93292a7..f8b1ad1f26 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -716,6 +716,25 @@ double TreeSupport::belt_floor_print_z(const Point &pos_slicing) const return ctx.floor_print_z(pos_slicing); } +bool TreeSupport::belt_node_landed(const Point &pos_slicing, double radius, double print_z) const +{ + BeltFloorContext ctx; + if (!ctx.init(m_slicing_params, *m_print_config)) + return false; + return print_z <= ctx.floor_print_z(pos_slicing) - std::abs(ctx.shear_factor()) * std::max(0., radius); +} + +bool TreeSupport::belt_polygon_landed(const ExPolygon &poly, double print_z) const +{ + BeltFloorContext ctx; + if (!ctx.init(m_slicing_params, *m_print_config)) + return false; + double min_floor = std::numeric_limits::max(); + for (const Point &pt : poly.contour.points) + min_floor = std::min(min_floor, ctx.floor_print_z(pt)); + return print_z <= min_floor; +} + #define SUPPORT_SURFACES_OFFSET_PARAMETERS jtSquare, 0. void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) { @@ -1616,8 +1635,7 @@ void TreeSupport::generate_toolpaths() // reads as a stray brim/skirt. Gate those layer_id==0 special cases off when // the belt floor is active; false on non-belt printers so behavior is unchanged. BeltFloorContext belt_ctx; - const bool belt_floor_active = belt_ctx.init(m_slicing_params, *m_print_config) - && m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly; + const bool belt_floor_active = belt_ctx.init(m_slicing_params, *m_print_config); // generate tree support tool paths tbb::parallel_for( @@ -1898,133 +1916,6 @@ void TreeSupport::generate() - // Belt floor: extend support below the object's first layer by creating - // additional support layers with geometry copied from the lowest content - // layer and clipped at the belt surface. These layers bypass the tree - // algorithm entirely — they're pure geometry added after draw_circles(). - { - BeltFloorContext ctx; - if (ctx.init(m_slicing_params, *m_print_config) - && m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { - const auto &sp = m_slicing_params; - // Find the lowest non-empty, non-brim support layer. - ExPolygons source_areas; - double source_z = 0; - int layers_with_content = 0; - for (size_t i = 0; i < m_object->support_layer_count(); ++i) { - SupportLayer *sl = m_object->get_support_layer(i); - if (sl && !sl->base_areas.empty()) { - layers_with_content++; - if (layers_with_content >= 2) { - source_areas = sl->base_areas; - source_z = sl->print_z; - break; - } - } - } - // Fallback to first content layer. - if (source_areas.empty()) { - for (size_t i = 0; i < m_object->support_layer_count(); ++i) { - SupportLayer *sl = m_object->get_support_layer(i); - if (sl && !sl->base_areas.empty()) { - source_areas = sl->base_areas; - source_z = sl->print_z; - break; - } - } - } - // ORCA-Belt calibration: a counter-rotated calibration object - // stands on a support wedge that lies entirely below the object's - // first layer, where the tree pipeline has no layers at all — so - // no support content can exist yet. Seed the extension directly - // from the floating portion of the first layer (anything more - // than one layer height above the belt floor). For objects whose - // first layer rests on the belt the floating region is empty and - // behavior is unchanged. - double first_z = m_object->support_layer_count() > 0 ? m_object->get_support_layer(0)->print_z : 0.; - bool seeded = false; - if (source_areas.empty() && m_object_config->enable_support.value && !m_object->layers().empty()) { - // The layer grid may start with an empty ghost layer just below - // the object (grid rounding against the belt global Z offset) — - // anchor the seed to the first layer that has geometry. Object - // layer print_z and the floor plane are both in the globally - // offset frame here (belt_floor_z_shift was adjusted alongside - // the layer Z values in PrintObject::slice()). - const Layer *first_layer = nullptr; - for (const Layer *l : m_object->layers()) - if (!l->lslices_extrudable.empty()) { first_layer = l; break; } - if (first_layer != nullptr) { - ExPolygons floating = diff_ex(first_layer->lslices_extrudable, - ctx.surface_polygon(first_layer->bottom_z() - first_layer->height)); - if (!floating.empty()) { - source_areas = std::move(floating); - first_z = first_layer->bottom_z(); - seeded = true; - } - } - } - if (!source_areas.empty()) { - BoundingBoxf3 bb = belt_remapped_bbox(*m_object->model_object(), m_object->print()->config()); - double from_extent = std::abs(bb.min(ctx.from_axis())); - double bb_min_z = std::abs(bb.min.z()); - // Depth = from-axis extent + pre-shear bbox Z offset (ensure_on_bed - // distance) + 10mm safety margin. The 10mm is a bodge to avoid - // small cutoff artifacts — ideally computed exactly from belt geometry. - double extra_depth = std::min(from_extent + bb_min_z + 10., std::max(0., first_z)); - if (seeded) { - // Seeded wedge: the depth is known exactly — down to the lowest - // belt-floor point under the floating footprint. The bbox - // heuristic above under-estimates it for meshes centered - // around their origin (every object loaded through the GUI). - double min_floor = first_z; - for (const ExPolygon &ep : source_areas) - for (const Point &pt : ep.contour.points) - min_floor = std::min(min_floor, ctx.floor_print_z(pt)); - extra_depth = std::min(std::max(0., first_z), first_z - min_floor + 2.); - } - int num_extra = std::max(0, (int)std::ceil(extra_depth / sp.layer_height)); - // Seeded wedge: top layers become a dense support interface so the - // object's floating first layer bridges a roof, not sparse infill. - const int interface_layers = seeded ? std::max(0, m_object_config->support_interface_top_layers.value) : 0; - ExPolygons prev_areas = source_areas; - // Build belt extension layers (lowest Z first). - SupportLayerPtrs belt_ext_layers; - for (int i = num_extra; i >= 1 && !prev_areas.empty(); --i) { - double print_z = first_z - i * sp.layer_height; - if (print_z < -sp.layer_height) continue; - Polygons belt_surface = ctx.surface_polygon(print_z); - ExPolygons clipped = diff_ex(source_areas, belt_surface); - if (clipped.empty()) continue; - SupportLayer *sl = new SupportLayer(0, 0, m_object, sp.layer_height, print_z, -1); - sl->base_areas = clipped; - // Populate area_groups — generate_toolpaths() iterates these, - // not base_areas directly. - // Note: base areas only get infill when support_base_pattern - // is explicitly set (with the default pattern tree bases are - // walls-only) — the calibration flow sets rectilinear. - const bool roof = i <= interface_layers; - for (auto &expoly : sl->base_areas) { - sl->area_groups.emplace_back(&expoly, roof ? SupportLayer::RoofType : SupportLayer::BaseType, 0); - if (roof) - sl->area_groups.back().interface_id = i & 1; - } - sl->lslices = clipped; - sl->lslices_bboxes.reserve(clipped.size()); - for (const ExPolygon &ep : clipped) - sl->lslices_bboxes.emplace_back(get_extents(ep)); - belt_ext_layers.push_back(sl); - } - // Insert at the front of support_layers (they're already in Z order). - if (!belt_ext_layers.empty()) { - auto &sl_vec = m_object->support_layers(); - sl_vec.insert(sl_vec.begin(), belt_ext_layers.begin(), belt_ext_layers.end()); - for (size_t i = 0; i < sl_vec.size(); ++i) - sl_vec[i]->set_id(i); - } - } - } - } - profiler.stage_start(STAGE_GENERATE_TOOLPATHS); m_object->print()->set_status(70, _u8L("Generating support")); generate_toolpaths(); @@ -2288,8 +2179,7 @@ void TreeSupport::draw_circles() // the Z=0 belt plane around the support footprint. false on non-belt printers, // so behavior there is unchanged. BeltFloorContext belt_ctx; - const bool belt_floor_active = belt_ctx.init(m_slicing_params, *m_print_config) - && m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly; + const bool belt_floor_active = belt_ctx.init(m_slicing_params, *m_print_config); if (m_object->support_layer_count() <= m_raft_layers) return; @@ -2484,8 +2374,7 @@ void TreeSupport::draw_circles() // is 0 so init() and init_local() coincide — this is a no-op there. { BeltFloorContext ctx; - if (ctx.init(m_slicing_params, *m_print_config) - && m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { + if (ctx.init(m_slicing_params, *m_print_config)) { Polygons belt_surface = ctx.surface_polygon(ts_layer->print_z); base_areas = diff_ex(base_areas, belt_surface); roof_areas = diff_ex(roof_areas, belt_surface); @@ -2962,9 +2851,7 @@ void TreeSupport::drop_nodes() const size_t tip_layers = base_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle. const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution; const bool support_on_buildplate_only = config.support_on_build_plate_only.value; - const auto belt_floor_mode = m_print_config->belt_support_floor_mode.value; - const bool has_belt_floor = std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON - && belt_floor_mode == BeltSupportFloorMode::GeneratorOnly; + const bool has_belt_floor = std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON; const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config); SupportNode::diameter_angle_scale_factor = diameter_angle_scale_factor; float DO_NOT_MOVER_UNDER_MM = is_slim ? 0 : 5; // do not move contact points under 5mm @@ -3200,10 +3087,12 @@ void TreeSupport::drop_nodes() node_parent = p_node->parent ? p_node : neighbour; // Make sure the next pass doesn't drop down either of these (since that already happened). node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node); - // Belt floor: don't drop merged node below belt surface. - // Treat as object-surface termination (not buildplate) so - // the node gets floor/interface areas instead of base pads. - if (has_belt_floor && print_z_next <= belt_floor_print_z(next_position)) { + // Belt floor: a merged node ends once its whole circle is in the belt + // (its slices are clipped to the belt plane in draw_circles(), so it + // tapers to a tip on the belt). Treat as object-surface termination + // (not buildplate) so the node gets floor/interface areas instead of + // base pads. + if (has_belt_floor && belt_node_landed(next_position, std::max(node.radius, neighbour->radius), print_z_next)) { std::scoped_lock lock(m_ts_data->m_mutex); node_parent->to_buildplate = false; neighbour->valid = false; @@ -3287,9 +3176,9 @@ void TreeSupport::drop_nodes() ExPolygons overhangs_next = diff_clipped({ node.overhang }, get_collision(0, obj_layer_nr_next)); for(auto& overhang:overhangs_next) { Point next_pt = overhang.contour.centroid(); - // Belt floor: don't drop polygon node below belt surface. + // Belt floor: a polygon node ends once all of it is in the belt. // Treat as object-surface termination (not buildplate). - if (has_belt_floor && print_z_next <= belt_floor_print_z(next_pt)) { + if (has_belt_floor && belt_polygon_landed(overhang, print_z_next)) { p_node->to_buildplate = false; continue; } @@ -3440,9 +3329,11 @@ void TreeSupport::drop_nodes() if (is_outside) { next_layer_vertex = candidate_vertex; } } } - // Belt floor: don't drop regular node below belt surface. + // Belt floor: a node ends once its whole circle is in the belt; until + // then it keeps dropping and draw_circles() clips each layer's circle + // to the belt plane, so the branch tapers to a tip on the belt. // Treat as object-surface termination (not buildplate). - if (has_belt_floor && print_z_next <= belt_floor_print_z(next_layer_vertex)) { + if (has_belt_floor && belt_node_landed(next_layer_vertex, node.radius, print_z_next)) { p_node->to_buildplate = false; return; // from parallel_for_each lambda } @@ -3771,9 +3662,7 @@ void TreeSupport::generate_contact_points() const coordf_t max_bridge_length = scale_(config.max_bridge_length.value); coord_t radius_scaled = scale_(base_radius); bool on_buildplate_only = m_object_config->support_on_build_plate_only.value; - const auto belt_floor_mode = m_print_config->belt_support_floor_mode.value; - const bool has_belt_floor = std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON - && belt_floor_mode == BeltSupportFloorMode::GeneratorOnly; + const bool has_belt_floor = std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON; //First generate grid points to cover the entire area of the print. BoundingBox bounding_box = m_object->bounding_box(); @@ -4044,8 +3933,7 @@ TreeSupportData::TreeSupportData(const PrintObject &object, coordf_t xy_distance BeltFloorContext ctx; double local_print_z = layer->print_z - object.belt_global_z_offset(); if (ctx.init_local(object.slicing_parameters(), object.print()->config(), - object.belt_global_z_offset()) - && object.print()->config().belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { + object.belt_global_z_offset())) { Polygons belt_surface = ctx.surface_polygon(local_print_z); for (auto &p : belt_surface) outline.emplace_back(ExPolygon(p)); diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index d0855e6cb6..c6acd806e0 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -463,6 +463,13 @@ private: // Belt printer: compute the belt floor print_z at a given XY position (in slicing coords). // Returns -infinity if belt floor is not active. double belt_floor_print_z(const Point &pos_slicing) const; + // Whether a node's whole circle (radius in mm) sits at or below the belt at + // print_z. The belt is a tilted plane, so the circle's leading edge crosses it + // |shear| * radius lower than its centre; stopping a node when its centre crosses + // would leave that edge floating a radius above the belt. + bool belt_node_landed(const Point &pos_slicing, double radius, double print_z) const; + // The same for a polygon: every point of it is at or below the belt. + bool belt_polygon_landed(const ExPolygon &poly, double print_z) const; diff --git a/src/libslic3r/Support/TreeSupport3D.cpp b/src/libslic3r/Support/TreeSupport3D.cpp index 189e855ed9..dc284ba94f 100644 --- a/src/libslic3r/Support/TreeSupport3D.cpp +++ b/src/libslic3r/Support/TreeSupport3D.cpp @@ -3462,37 +3462,6 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons // this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generate_initial_areas() have knowledge of the existence of multiple meshes being processed. //FIXME this is a copy // Contains config settings to avoid loading them in every function. This was done to improve readability of the code. - // Belt printer: add virtual "belt raft" layers below the object so - // organic branches can extend below the model's first layer and - // terminate at the belt surface instead of creating a flat base at Z=0. - { - PrintObject &po = *print.get_object(processing.second.front()); - const auto &sp = po.slicing_parameters(); - const auto &pcfg = po.print()->config(); - BeltFloorContext ctx; - ctx.init_local(sp, pcfg, po.belt_global_z_offset()); - if (ctx.is_active() && std::abs(po.belt_global_z_offset()) > EPSILON - && pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { - // z_shift_local is the belt surface height at Y=0 in local coords. - // Extend below the belt so the base expansion and build-plate - // termination happen inside the belt region and get clipped. - // Use the distance from the pre-shear bbox min Z to the part's - // post-shear min Z, plus 10mm for base expansion headroom. - double bb_min_z = std::abs(belt_remapped_bbox(*po.model_object(), pcfg).min.z()); - double extra_depth = bb_min_z + 10.; - int num_extra = std::max(0, (int)std::ceil(extra_depth / sp.layer_height)); - if (num_extra > 0) { - // Insert belt raft layers at the front, from lowest Z to highest. - std::vector belt_layers; - belt_layers.reserve(num_extra); - for (int i = num_extra; i >= 1; --i) - belt_layers.push_back(sp.first_object_layer_height - i * sp.layer_height); - // Prepend to existing raft_layers (if any). - auto &rl = processing.first.raft_layers; - rl.insert(rl.begin(), belt_layers.begin(), belt_layers.end()); - } - } - } const TreeSupportSettings &config = processing.first; BOOST_LOG_TRIVIAL(info) << "Processing support tree mesh group " << counter + 1 << " of " << grouped_meshes.size() << " containing " << grouped_meshes[counter].second.size() << " meshes."; auto t_start = std::chrono::high_resolution_clock::now(); @@ -3687,8 +3656,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons const auto &pcfg = print_object.print()->config(); BeltFloorContext ctx; ctx.init_local(sp, pcfg, print_object.belt_global_z_offset()); - if (ctx.is_active() - && pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { + if (ctx.is_active()) { tbb::parallel_for_each(layers_sorted.begin(), layers_sorted.end(), [&](SupportGeneratorLayer *layer) { if (!layer || layer->polygons.empty()) return; @@ -3984,13 +3952,6 @@ void organic_draw_branches( const double tiny_area = tiny_area_threshold(); //FIXME parallelize? for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++i) { - // ORCA: safety offset when trimming collision/bed to improve robustness. - slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true), ApplySafetyOffset::Yes); // FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist); - slices[i] = intersection(slices[i], volumes.m_bed_area, ApplySafetyOffset::Yes); - // Belt floor: clip branch slices against the belt surface plane. - LayerIndex belt_idx = layer_begin + i; - if (belt_idx < LayerIndex(volumes.m_belt_floor.size()) && !volumes.m_belt_floor[belt_idx].empty()) - slices[i] = diff(slices[i], volumes.m_belt_floor[belt_idx]); remove_small(slices[i], tiny_area); } diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index b28f9c5d1c..d7fd871f27 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -36,6 +36,7 @@ #include #include "libslic3r/Preset.hpp" #include "libslic3r/Config.hpp" +#include #if BOOST_VERSION >= 107800 #include @@ -766,25 +767,25 @@ void Bed3D::render_gravity_arrow(const Transform3d& view_matrix, const Transform m_gravity_arrow.reset(); return; } - const Vec3d gravity_dir = -up_dir; - // Build the arrow model (same dimensions as the axis arrows) - if (!m_gravity_arrow.is_initialized()) { - const float stem_length = Axes::DefaultStemLength; - const float tip_radius = Axes::DefaultTipRadius; - const float tip_length = Axes::DefaultTipLength; - const float stem_radius = stem_length / 75.f; // same ratio as axis cylinders - m_gravity_arrow.init_from(stilized_arrow(16, tip_radius, tip_length, stem_radius, stem_length)); + // A plain line along the tilted "up" direction -- the way the layers lean, i.e. + // the gantry -- drawn like the bed axes (no tip: the other direction is not + // possible) and shorter than them, so it reads as a hint inside the YZ corner. + const float length = 0.6f * m_axes.get_total_length(); + if (!m_gravity_arrow.is_initialized() || m_gravity_arrow_length != length) { + m_gravity_arrow.reset(); + m_gravity_arrow.init_from(smooth_cylinder(16, /*Radius*/ length / 75.f, length)); + m_gravity_arrow_length = length; } - // The arrow model points along +Z by default. Compute rotation to align with gravity_dir. - // Rotation axis = cross(+Z, gravity_dir), angle = acos(dot(+Z, gravity_dir)) + // The cylinder model points along +Z. Compute the rotation that aligns it with + // up_dir: rotation axis = cross(+Z, up_dir), angle = acos(dot(+Z, up_dir)). Vec3d from = Vec3d::UnitZ(); - Vec3d to = gravity_dir; + Vec3d to = up_dir; double dot = from.dot(to); Transform3d rot = Transform3d::Identity(); if (dot < -0.9999) { - // Nearly opposite — rotate 180° around X + // Nearly opposite -- rotate 180 degrees around X rot = Eigen::AngleAxisd(M_PI, Vec3d::UnitX()) * rot; } else if (dot < 0.9999) { Vec3d axis = from.cross(to).normalized(); @@ -800,7 +801,7 @@ void Bed3D::render_gravity_arrow(const Transform3d& view_matrix, const Transform shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); - Transform3d model_matrix = rot; + Transform3d model_matrix = Eigen::Translation3d(m_axes.get_origin()) * rot; shader->set_uniform("view_model_matrix", camera.get_view_matrix() * model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 497617a2e3..eeabe7da82 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -116,6 +116,7 @@ private: GLModel m_model; Vec3d m_model_offset{ Vec3d::Zero() }; GLModel m_gravity_arrow; + float m_gravity_arrow_length{ 0.f }; Axes m_axes; float m_scale_factor{ 1.0f }; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 49940229dc..0af44aebf2 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1318,13 +1318,11 @@ std::vector GCodeViewer::get_plater_extruder() // Belt printers: compute the full machine->model back-transform from the print // config, so the "designed" (upright) G-code preview maps each toolpath vertex // back to Cartesian space. The G-code forward pipeline is (BeltKinematics:: -// to_machine_coords): gcode = MachineFrame( AxisRemap( X ) ), with X = model if -// gcode_back_transform (write already un-rotated to Cartesian) else BeltForward( -// model). So the inverse is: -// model = [BeltForward^-1 if !gcode_back_transform] . AxisRemap^-1 . MachineFrame^-1 -// All parts are config-driven affines -> handles any rotation/shear/scale/axis- -// remap combination. (origin-snap is a per-instance translation that only shifts -// position, not orientation, so it is intentionally omitted.) +// to_machine_coords): gcode = MachineFrame( AxisRemap( X ) ), with X the model +// already un-rotated to Cartesian by the back-transform. So the inverse is: +// model = AxisRemap^-1 . MachineFrame^-1 +// (origin-snap is a per-instance translation that only shifts position, not +// orientation, so it is intentionally omitted.) static Transform3d compute_belt_back_transform(const PrintConfig& cfg) { if (!cfg.belt_printer.value) @@ -1360,11 +1358,7 @@ static Transform3d compute_belt_back_transform(const PrintConfig& cfg) } const Transform3d ar_inv = ar.inverse(); - Transform3d bf_inv = Transform3d::Identity(); - if (!cfg.gcode_back_transform.value) - bf_inv = BeltTransformPipeline::build_forward_transform(cfg).inverse(); - - return bf_inv * ar_inv * mf_inv; + return ar_inv * mf_inv; } //BBS: always load shell at preview @@ -1393,11 +1387,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness"))); // avoid processing if called with the same gcode_result. - // On a belt printer the toolpath geometry fed to libvgcode also depends on the - // designed/raw view state (the back-transform is applied in convert), so the - // same result is converted again only when that view has been toggled. - const bool same_belt_view = !m_belt_view_enabled || m_last_belt_show_designed == m_belt_show_designed; - if (m_last_result_id == gcode_result.id && wxGetApp().is_editor() && same_belt_view) { + if (m_last_result_id == gcode_result.id && wxGetApp().is_editor()) { //BBS: add logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": the same id %1%, return directly, result %2% ") % m_last_result_id % (&gcode_result); @@ -1439,22 +1429,20 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const } // convert data from PrusaSlicer format to libvgcode format. - // Belt printers: when the "designed (upright) view" is active, back-transform - // the toolpath geometry into model/Cartesian space using the general belt - // inverse (handles any mesh rotation + shear + axis remap). When off, the raw - // machine-frame G-code is shown (useful for debugging the transform itself). + // Belt printers: back-transform the toolpath geometry into model/Cartesian + // space using the general belt inverse (handles the mesh rotation, shear and + // axis remap), so the part is shown upright, the way it was designed. const bool is_belt = m_belt_view_enabled && print.config().belt_printer.value; - Transform3d belt_inv = (is_belt && m_belt_show_designed) - ? compute_belt_back_transform(print.config()) : Transform3d::Identity(); + Transform3d belt_inv = is_belt ? 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) + if (is_belt && 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 + const bool apply_belt = is_belt && !belt_inv.matrix().isApprox(Transform3d::Identity().matrix()); if (apply_belt) { // The linear belt back-transform recovers the print's shape and orientation but not @@ -1691,7 +1679,6 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const //BBS: move the id to the end of reset m_last_result_id = gcode_result.id; - m_last_belt_show_designed = m_belt_show_designed; m_gcode_result = &gcode_result; m_move_type_counts.fill(0); for (auto& move_type_times : m_move_type_times) @@ -5146,33 +5133,6 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv if (m_nozzle_nums > 1 && (m_viewer.get_view_type() == libvgcode::EViewType::Summary || m_viewer.get_view_type() == libvgcode::EViewType::ColorPrint)) // ORCA show only on summary and filament tab render_legend_color_arr_recommen(window_padding); - // Belt printer: toggle for viewing designed (upright) vs. machine-frame G-code. - // Rendered with a separator and hint text so users can find it easily. - if (m_belt_view_enabled) { - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - ImGui::Dummy({ window_padding, 0 }); - ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.f, 0.59f, 0.53f, 1.f), "%s", _u8L("Belt printer").c_str()); - ImGui::Dummy({ window_padding, 0 }); - ImGui::SameLine(); - // Checked = show the raw machine-frame G-code (designed/upright view off). Worded to - // match the canvas-toolbar menu item "Show raw G-code (belt only)". m_belt_show_designed - // is the inverse of this checkbox, so bind a temporary and flip it on change. - bool show_raw = !m_belt_show_designed; - const std::string key = wxGetApp().shortcuts().display(Shortcut::ToggleBeltRawGcode); - const std::string label = _u8L("Show raw G-code (belt only)") + (key.empty() ? std::string() : " [" + key + "]"); - if (ImGui::Checkbox(label.c_str(), &show_raw)) { - m_belt_show_designed = !show_raw; - // The designed-view back-transform is baked into the toolpath geometry at load - // time, so the toggle only takes effect once the preview is re-converted. Defer - // the refresh to the next event-loop tick (CallAfter) to avoid re-entering the - // preview load from inside legend rendering. - if (Plater* plater = wxGetApp().plater()) - plater->CallAfter([plater]() { plater->refresh_belt_view(); }); - } - } legend_height = ImGui::GetCurrentWindow()->Size.y; imgui.end(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 06d275a187..9cd669372f 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -199,8 +199,6 @@ private: std::vector m_plater_extruder; bool m_gl_data_initialized{ false }; unsigned int m_last_result_id{ 0 }; - // Belt printers: the view the loaded result was converted for (see load_as_gcode). - bool m_last_belt_show_designed{ true }; //BBS: save m_gcode_result as well const GCodeProcessorResult* m_gcode_result; std::array(EMoveType::Count)> m_move_type_counts{}; @@ -265,8 +263,6 @@ mutable bool m_no_render_path { false }; bool m_belt_view_enabled = false; float m_belt_angle_deg = 0.f; - bool m_belt_show_designed = true; // Toggle: designed (upright, back-transformed) view by default; - // turn off (hotkey B) to inspect the raw machine-frame G-code. libvgcode::Viewer m_viewer; // ORCA: section view, as the viewer has it. What it cuts away casts no shadow. @@ -406,8 +402,6 @@ public: void set_belt_printer(bool enabled, float angle_deg) { m_belt_view_enabled = enabled; m_belt_angle_deg = angle_deg; } bool is_belt_view() const { return m_belt_view_enabled && m_belt_angle_deg > 0.f; } - void toggle_belt_show_designed() { if (m_belt_view_enabled) m_belt_show_designed = !m_belt_show_designed; } - bool is_belt_show_designed() const { return m_belt_show_designed; } size_t get_extruders_count() { return m_extruders_count; } void push_combo_style(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6a7cc39079..668d2cc2b4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3778,16 +3778,6 @@ bool GLCanvas3D::handle_shortcut(const KeyChord& chord) m_dirty = true; request_extra_frame(); break; - case Shortcut::ToggleBeltRawGcode: - // Same state as the legend checkbox and the canvas-toolbar menu item. The designed-view - // back-transform is baked into the toolpaths at load time, so the preview is re-converted. - if (m_gcode_viewer.is_belt_view()) { - m_gcode_viewer.toggle_belt_show_designed(); - if (Plater* plater = wxGetApp().plater()) - plater->refresh_belt_view(); - m_dirty = true; - } - break; case Shortcut::ToggleOneLayerMode: get_gcode_viewer().get_layers_slider()->switch_one_layer_mode(); m_dirty = true; @@ -10190,20 +10180,6 @@ void GLCanvas3D::_render_canvas_toolbar() ImGui::TextColored(enable ? ImVec4(1,1,1,1) : ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled), "%s", into_u8(condition ? ImGui::VisibleIcon : ImGui::HiddenIcon).c_str()); }; - // Belt printers, G-code preview only: toggle the designed (upright) view vs the raw - // machine-frame G-code. Same state as the shortcut and the legend checkbox; the reload is - // deferred (CallAfter) so the preview is not rebuilt mid-render. - if (m_canvas_type == ECanvasType::CanvasPreview && m_gcode_viewer.is_belt_view()) { - create_menu_item( _utf8(L("Show raw G-code (belt only)")), - true, - !m_gcode_viewer.is_belt_show_designed(), // eye lit = raw machine-frame G-code (designed view off) - [this, p]{ - m_gcode_viewer.toggle_belt_show_designed(); - p->CallAfter([p]{ p->refresh_belt_view(); }); - } - ); - ImGui::Separator(); - } create_menu_item( _utf8(L("3D Navigator")), m_canvas_type != ECanvasType::CanvasAssembleView, // not work on assembly diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index f52bd20cc2..1968df996f 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -369,17 +369,6 @@ void Preview::reload_print(bool only_gcode) m_only_gcode = only_gcode; } -void Preview::refresh_belt_view() -{ - // Re-run the G-code preview conversion so the belt "designed view" toggle takes effect - // (the back-transform is baked into the toolpath geometry in GCodeViewer::load_as_gcode, - // whose same-result cache also keys on the view state, so the re-convert runs). - // Reset m_loaded_print to bypass the "already loaded" guard the way reload_print does, but - // keep the current layer (Z) range and only-gcode mode so the view doesn't jump on toggle. - m_loaded_print = nullptr; - load_print(true /*keep_z_range*/, m_only_gcode); -} - //BBS: always load shell at preview void Preview::load_shells(const Print& print, bool force_previewing) { diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index acb3211fd9..2ad1f8788c 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -149,7 +149,6 @@ public: void load_print(bool keep_z_range = false, bool only_gcode = false); void reload_print(bool only_gcode = false); // Belt printers: re-convert the G-code preview so the "designed view" toggle takes effect. - void refresh_belt_view(); //BBS: always load shell at preview void load_shells(const Print& print, bool force_previewing = false); void reset_shells(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3684020f8a..09f3cd2b8f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -17885,11 +17885,6 @@ void Plater::reload_print() p->preview->reload_print(); } -void Plater::refresh_belt_view() -{ - p->preview->refresh_belt_view(); -} - // BBS wxString Plater::get_project_name() { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 83d028549b..e7d94bc606 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -388,7 +388,6 @@ public: // Belt printers: re-run the G-code preview conversion so the "designed view" toggle // (hotkey B / legend checkbox) takes effect; the back-transform is applied to the // toolpath geometry at load time. Keeps the current layer range and only-gcode mode. - void refresh_belt_view(); // SoftFever void calib_pa(const Calib_Params& params); diff --git a/src/slic3r/GUI/Shortcuts.cpp b/src/slic3r/GUI/Shortcuts.cpp index 2395d31778..8773c6f364 100644 --- a/src/slic3r/GUI/Shortcuts.cpp +++ b/src/slic3r/GUI/Shortcuts.cpp @@ -149,7 +149,6 @@ constexpr std::array shortcut_table = {{ SHORTCUT(ShowWireframe, "show_wireframe", L("Show/Hide wireframe"), CANVAS, { WXK_RETURN, CTRL_SHIFT }), SHORTCUT(ToggleGcodeWindow, "toggle_gcode_window", L("On/Off G-code window"), PREVIEW, { 'C' }), SHORTCUT(ToggleOneLayerMode, "toggle_one_layer_mode", L("On/Off one layer mode of the vertical slider"), PREVIEW, { 'L' }), - SHORTCUT(ToggleBeltRawGcode, "toggle_belt_raw_gcode", L("Show raw G-code (belt only)"), PREVIEW, { 'B' }), // Application SHORTCUT(Preferences, "preferences", L("Preferences"), GLOBAL, PREFERENCES_CHORD), diff --git a/src/slic3r/GUI/Shortcuts.hpp b/src/slic3r/GUI/Shortcuts.hpp index 6a58d64971..0e61554e2f 100644 --- a/src/slic3r/GUI/Shortcuts.hpp +++ b/src/slic3r/GUI/Shortcuts.hpp @@ -46,7 +46,7 @@ enum class Shortcut : uint8_t { // Camera ViewDefault, ViewTop, ViewBottom, ViewFront, ViewRear, ViewLeft, ViewRight, ViewPlate, ZoomIn, ZoomOut, Mouse3DSettings, // Display - ShowLabels, ShowWireframe, ToggleGcodeWindow, ToggleOneLayerMode, ToggleBeltRawGcode, + ShowLabels, ShowWireframe, ToggleGcodeWindow, ToggleOneLayerMode, // Application Preferences, Search, SwitchView, CollapseSidebar, ReloadDevicePage, KeyboardShortcuts, // Speed Dial diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a2d0f3ea3e..96152efb3d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -5250,25 +5250,12 @@ void TabPrinter::build_fff() // Belt tilt: the sole mesh-side transform and the single source of truth for // the physical tilt (drives bed rendering and support gravity tilt too). // Isometric rotation, no distortion; the back-transform inverts it before the - // machine-frame remap. - { - Line line = { L("Belt tilt"), - L("Belt tilt axis and angle, applied as a mesh rotation before " - "slicing. Also drives bed rendering and support gravity tilt. " - "Isometric (no distortion); the back-transform inverts it before " - "the machine-frame remap.") }; - 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")); - 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"); - 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). + // machine-frame remap. The angle is what a user checks against the machine; + // the axis is a profile-level kinematics choice, so it is Develop-only. They + // are separate rows because a shared line is shown by its first option's mode. + belt_og->append_single_option_line("belt_slice_rotation_angle", "printer_basic_information_belt_printer#tilt-angle"); + belt_og->append_single_option_line("belt_slice_rotation", "printer_basic_information_belt_printer#tilt-axis"); 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_floor_mode", "printer_basic_information_belt_printer#floor-mode"); // Machine-frame transform: the shear (cot) + scale (1/sin) that map // Cartesian G-code into the printer's physical machine frame are derived @@ -6326,6 +6313,7 @@ void TabPrinter::toggle_options() bool expert_or_above = (m_mode >= comExpert); toggle_line("belt_printer_infinite_y", is_belt); // Belt tilt: the sole mesh-side belt transform (visible by default in belt mode). + toggle_line("belt_slice_rotation_angle", is_belt); toggle_line("belt_slice_rotation", is_belt); // Remap, back-transform, and global mesh-transforms toggles are gated by belt @@ -6333,9 +6321,8 @@ void TabPrinter::toggle_options() // 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 : {"gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "gcode_back_transform"}) + for (auto el : {"gcode_remap_x", "gcode_remap_y", "gcode_remap_z"}) toggle_line(el, is_belt); - toggle_line("belt_preslice_global", is_belt); // Rotation is the only mesh-side belt transform. Gray out its angle when no // rotation axis is selected. @@ -6348,10 +6335,8 @@ void TabPrinter::toggle_options() toggle_line("belt_frame_tilt_angle", is_belt && expert_or_above && m_config->opt_bool("belt_frame_tilt_decouple")); - toggle_line("first_layer_plane_thickness", is_belt); - for (auto el : {"belt_support_floor_mode", "belt_support_floor_offset"}) - toggle_line(el, is_belt); + toggle_line("belt_support_floor_offset", is_belt); const bool support_parallel_printheads = printer_cfg.opt_bool("support_parallel_printheads"); toggle_line("parallel_printheads_count", support_parallel_printheads); diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index 9ef4a8f39d..3dbb3a986b 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -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.gcode_back_transform.value = true; config.gcode_remap_x.value = RemapAxis::PosX; config.gcode_remap_y.value = RemapAxis::PosZ; config.gcode_remap_z.value = RemapAxis::PosY; @@ -1026,10 +1025,8 @@ SCENARIO("Belt: the first travel does not lift through the uninitialised origin" // Machine-frame + slicer->world back-transform config (X tilt, 45 deg). PrintConfig belt_config; belt_config.belt_printer.value = true; - 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_preslice_global.value = true; belt_config.belt_frame_tilt_decouple.value = false; belt_config.belt_frame_tilt_angle.value = 45.0; diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index f16a0ce662..57232d5a11 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -30,6 +30,10 @@ #include "libslic3r/BuildVolume.hpp" #include "libslic3r/Support/TreeModelVolumes.hpp" #include "libslic3r/Support/TreeSupportCommon.hpp" +#include "libslic3r/Support/BeltFloorContext.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Polyline.hpp" +#include #include "libslic3r/Polygon.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/GCodeReader.hpp" @@ -1041,16 +1045,12 @@ TEST_CASE("Belt-only keys at non-default values leave non-belt G-code unchanged" { "belt_printer_infinite_y", 0 }, { "belt_slice_rotation", "y" }, { "belt_slice_rotation_angle", 30 }, - { "belt_preslice_global", 0 }, { "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_thickness", 1 }, { "belt_support_floor_offset", -5 }, - { "belt_support_floor_mode", "none" }, { "enable_belt_purge_tower", 1 }, { "belt_purge_tower_width", 10 }, { "leading_brim_length", 10 }, @@ -1242,3 +1242,97 @@ TEST_CASE("Organic tree supports place a support blocker at its own height above CHECK(collides(last + 1)); CHECK(collides(last + num_raft)); } + +// A part with an overhang on its LEADING side (the end that prints first) needs +// supports below the object's own lowest slicing layer: the belt under that overhang +// is reached before the object's first contact with it, so the support layers sit at +// a lower slicing Z than any object layer. A generator that stops at the object's +// first layer, or at global Z = 0, leaves those supports floating above the belt. +TEST_CASE("Belt supports reach the belt under a leading overhang", "[Print][belt][Support][Regression]") +{ + // default resolves to organic for tree support; tree_hybrid is the classic tree. + const char *support_type = GENERATE("normal(auto)", "tree(auto)"); + const char *support_style = GENERATE("default", "organic", "tree_hybrid"); + if (std::string(support_type) == "normal(auto)" && std::string(support_style) != "default") + return; // organic and tree_hybrid are tree styles + DYNAMIC_SECTION(support_type << " / " << support_style) { + // A 20 mm cube with a 2 mm thick fin that leaves its top edge and reaches + // 20 mm toward -Y, the end of the part that prints first, climbing at 45 deg + // as it goes (from z = 18 at the cube to z = 38 at the tip). With the layers + // leaning toward -Y at 45 deg the fin's underside is parallel to the layers: + // a ceiling 20 x 28 mm in one layer, with nothing but air between it and the + // belt, which lies up to 41 mm (of slicing Z) below the object's own lowest + // point. Support has to span all of it. + indexed_triangle_set its = its_make_cube(20., 20., 20.); + indexed_triangle_set fin = its_make_cube(20., 20., 2.); + Transform3d shear = Transform3d::Identity(); + shear.matrix() << 1., 0., 0., 0., + 0., 1., 0., -20., + 0., -1., 1., 38., + 0., 0., 0., 1.; + its_transform(fin, shear); + its_merge(its, fin); + TriangleMesh mesh(std::move(its)); + + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "belt_printer", 1 }, + { "belt_slice_rotation", "x" }, + { "belt_slice_rotation_angle", 45 }, + { "gcode_remap_x", "rev_x" }, + { "gcode_remap_y", "pos_z" }, + { "gcode_remap_z", "pos_y" }, + { "layer_height", 0.2 }, + { "initial_layer_print_height", 0.2 }, + { "skirt_loops", 0 }, + { "z_hop", 0 }, + { "enable_support", 1 }, + { "support_type", support_type }, + { "support_style", support_style }, + { "support_threshold_angle", 30 }, + { "machine_start_gcode", "T[initial_tool]\n" }, + { "layer_change_gcode", "G92 E0\n" }, + }); + Print print; + Model model; + init_print({ mesh }, print, model, config); + // On the bed, not at its corner: organic tree support clips its branches to + // the bed outline, and the fixture leaves the object at the origin. + model.objects.front()->instances.front()->set_offset(Vec3d(100., 100., 0.)); + print.apply(model, config); + print.set_status_silent(); + print.process(); + + const PrintObject &object = *print.objects().front(); + REQUIRE(! object.layers().empty()); + BeltFloorContext floor; + REQUIRE(floor.init(object.slicing_parameters(), print.config())); + + // The lowest support layer that prints anything, and the belt floor beneath it. + const SupportLayer *lowest = nullptr; + for (const SupportLayer *layer : object.support_layers()) + if (! layer->support_fills.empty() && (lowest == nullptr || layer->print_z < lowest->print_z)) + lowest = layer; + REQUIRE(lowest != nullptr); + double floor_under_lowest = std::numeric_limits::max(); + for (const ExtrusionEntity *entity : lowest->support_fills.flatten().entities) + for (const Polyline &pl : entity->as_polylines()) + for (const Point &pt : pl.points) + floor_under_lowest = std::min(floor_under_lowest, floor.floor_print_z(pt)); + // The object's lowest geometry. The slicing frame starts at the lowest + // belt-floor point under the footprint, so the layers below the leading + // tip of the overhang are empty. + double first_object_z = std::numeric_limits::max(); + for (const Layer *layer : object.layers()) + if (! layer->lslices.empty()) { first_object_z = layer->print_z; break; } + REQUIRE(first_object_z < std::numeric_limits::max()); + INFO("lowest support z " << lowest->print_z << ", floor under it " << floor_under_lowest + << ", first object layer " << first_object_z); + // Well below the object's own lowest layer (the belt under the tip of the fin + // is ~41 mm of slicing Z below the cube's leading edge, which rests on it)... + CHECK(lowest->print_z < first_object_z - 5.); + // ...and resting on the belt: within a few layers of the floor beneath its own lines. + CHECK(lowest->print_z - floor_under_lowest < 4. * 0.2 + EPSILON); + CHECK(lowest->print_z - floor_under_lowest > -0.2 - EPSILON); + } +} diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp index 99e7860c3e..34f71a71a7 100644 --- a/tests/fff_print/test_skirt_brim.cpp +++ b/tests/fff_print/test_skirt_brim.cpp @@ -715,9 +715,10 @@ static double first_role_z(const std::string &gcode, const std::string &role) return z; } -// Number of object layers that carry a belt brim band. Each such band is emitted as one -// contiguous brim pass, so for a single object whose first-contact layer carries a band -// (the apron prologue folds into that layer's pass) this equals role_passes(gcode, "brim"). +// Number of object layers that carry a belt brim band. Every band prints at its own +// layer Z, so this equals role_layers(gcode, "brim") (plus any apron bands below the +// first object layer). It is not a pass count: the bands on the empty lead-in layers +// ahead of the object's first contact print back to back, so they fold into one pass. static int nonempty_belt_brim_layers(const PrintObject &object) { int n = 0; @@ -727,6 +728,30 @@ static int nonempty_belt_brim_layers(const PrintObject &object) return n; } +// Number of distinct Z heights at which `role` extrudes: one per layer that prints it. +static int role_layers(const std::string &gcode, const std::string &role) +{ + std::set zs; + GCodeReader reader; + reader.parse_buffer(gcode, [&](GCodeReader &self, const GCodeReader::GCodeLine &line) { + if (! line.extruding(self) || line.dist_XY(self) <= EPSILON) + return; + if (line.comment().find(role) != std::string_view::npos) + zs.insert(std::lround(self.z() * 1000.)); + }); + return int(zs.size()); +} + +// Apron bands below the object's first layer that print something. +static int belt_brim_apron_bands(const PrintObject &object) +{ + int n = 0; + for (const BeltBrimBand &band : object.belt_brim_prologue()) + if (! band.fills.empty()) + ++ n; + return n; +} + // For each active tool, the ordinal (1-based, over extruding moves) of the FIRST move whose // role comment contains `role`. Lets a per-object ordering check key off the object's // unique wall filament. @@ -801,12 +826,15 @@ TEST_CASE("Belt brim on an object layer precedes its perimeters, once", "[SkirtB CHECK(seq[0] == "brim"); CHECK(seq[1] == "perimeter"); - // Exactly once: every band is one contiguous pass (the apron prologue folds into the - // first layer's), so the pass count equals the number of layers carrying a band - not - // twice it, which double-emission would give, nor fewer, which a dropped band would. - const int bands = nonempty_belt_brim_layers(*print.objects().front()); + // Exactly once: every band prints at its own layer Z, so the number of Z heights with + // brim equals the number of bands - not fewer, which a dropped band would give. (A + // double emission would print twice at one Z: the pass count below catches that for + // the bands that sit on layers with perimeters.) + const PrintObject &object = *print.objects().front(); + const int bands = nonempty_belt_brim_layers(object); REQUIRE(bands > 0); - CHECK(role_passes(gc, "brim") == bands); + CHECK(role_layers(gc, "brim") == bands + belt_brim_apron_bands(object)); + CHECK(role_passes(gc, "brim") <= bands); } // B - single extruder (filament id 1). Every band must survive the 1-based -> 0-based @@ -826,9 +854,10 @@ TEST_CASE("Belt brim on a single extruder emits every band once", "[SkirtBrim][b init_print({ cube(20) }, print, model, config); const std::string gc = gcode(print); - const int expected = nonempty_belt_brim_layers(*print.objects().front()); + const PrintObject &object = *print.objects().front(); + const int expected = nonempty_belt_brim_layers(object) + belt_brim_apron_bands(object); REQUIRE(expected > 0); - CHECK(role_passes(gc, "brim") == expected); + CHECK(role_layers(gc, "brim") == expected); CHECK(belt_tools_for_role(gc, "brim") == std::set{ 0 }); // filament 1 -> tool 0 } @@ -1231,49 +1260,6 @@ TEST_CASE("Belt apron survives another object printing at the same Z", "[SkirtBr CHECK(two >= 1.8 * one); } -TEST_CASE("Belt brim allows instances placed across the belt", "[SkirtBrim][belt]") -{ - // Only movement ALONG the belt changes an instance's belt-floor Z, so copies placed - // side by side ACROSS it share one set of bands and must still get a brim. The first - // version of this guard refused every multi-instance object outright, silently - // dropping the brim. - // - // The global belt flags are off here so the instances stay in one PrintObject; with - // them on, PrintApply splits each instance into its own object and the case cannot - // arise at all. - auto multi_instance_has_brim = [](double dx, double dy) { - DynamicPrintConfig config = belt_brim_config(); - config.set_deserialize_strict({ - { "belt_preslice_global", 0 }, - { "brim_type", "outer_only" }, - { "brim_width", 4 }, - { "brim_object_gap", 0 }, - }); - Print print; - Model model; - ModelObject *object = model.add_object(); - object->name += "object.stl"; - object->add_volume(cube(20)); - object->add_instance()->set_offset(Vec3d(80., 80., 0.)); - object->add_instance()->set_offset(Vec3d(80. + dx, 80. + dy, 0.)); - object->ensure_on_bed(); - print.auto_assign_extruders(object); - print.apply(model, config); - print.validate(); - print.set_status_silent(); - print.process(); - REQUIRE(print.objects().size() == 1); - REQUIRE(print.objects().front()->instances().size() == 2); - return print.objects().front()->has_belt_brim(); - }; - - // X is across the belt when the tilt is about X, since the shear then runs along Y. - CHECK(multi_instance_has_brim(40., 0.)); - // Y is along the belt: the copies sit at different belt heights and would each need - // their own bands, so the brim is refused (and validate() warns). - CHECK_FALSE(multi_instance_has_brim(0., 40.)); -} - TEST_CASE("Belt brim coexists with support material", "[SkirtBrim][belt]") { // Supports put extra layers into the same z stream as the apron bands, which is what From a2f5a8ce2bb4bfd9e3089cc51b1fac632c9aad51 Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Wed, 7 Oct 2026 00:10:24 -0500 Subject: [PATCH 3/3] Belt printer: size the layer range from the on-bed box The belt object height is estimated from a bounding box swept through the tilt rotation. raw_bounding_box() has the instance's Z offset removed, which did not matter while the estimate was the box's rotated Z extent (a Z shift moves every corner alike), but the frame now starts at the lowest belt-floor point under the footprint, and a point's rotated z and the floor under it move in opposite directions under a Z shift: the offset box under-estimated the height by twice the object's height above the bed, so the layers stopped at the part's diagonal and every part came out as a wedge (GUI and CLI alike; the unit tests never checked the top). Use the box of the mesh in the frame it is sliced in (trafo_centered(), Z as placed on the bed), and have the leading overhang test check that the whole part is sliced. Co-Authored-By: Claude Fable 5.1 --- src/libslic3r/PrintObject.cpp | 19 ++++++++++++++++++- tests/fff_print/test_print.cpp | 16 ++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 583a72766c..4f46921c3f 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -4141,7 +4141,17 @@ void PrintObject::update_slicing_parameters() BeltTransformPipeline::BeltFloorParams belt_floor; const auto &pcfg = this->print()->config(); if (pcfg.belt_printer.value) { - const BoundingBoxf3 bb = this->model_object()->raw_bounding_box(); + // The box of the mesh in the frame it is sliced in: XY centred and Z as + // placed on the bed (trafo_centered()). raw_bounding_box() has the + // instance's Z offset removed, and the belt floor is not invariant to a + // Z shift (a point's z and the floor under it move in opposite + // directions under the rotation), so an offset box under-estimates the + // height by twice the shift and the layers stop part way up the object. + BoundingBoxf3 bb; + const Transform3d trafo = this->trafo_centered(); + for (const ModelVolume *v : this->model_object()->volumes) + if (v->is_model_part()) + bb.merge(v->mesh().transformed_bounding_box(trafo * v->get_matrix())); auto hr = BeltTransformPipeline::compute_belt_height_and_floor(pcfg, bb, object_height); object_height = hr.object_height; belt_floor = hr.floor_params; @@ -4202,6 +4212,13 @@ 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) { + // Z as placed on the bed, XY around the instance origin: the belt floor + // depends on where the box sits in Z (see update_slicing_parameters()). + if (! model_object.instances.empty()) { + bb = model_object.instance_bounding_box(0, false); + const Vec3d off = model_object.instances.front()->get_offset(); + bb.translate(-off.x(), -off.y(), 0.); + } 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; diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index 57232d5a11..be5311fd7b 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -1305,6 +1305,22 @@ TEST_CASE("Belt supports reach the belt under a leading overhang", "[Print][belt const PrintObject &object = *print.objects().front(); REQUIRE(! object.layers().empty()); + // The whole part is sliced: the layers lean at 45 deg, so the part spans + // (y + z) / sqrt(2) of slicing Z, and every layer in that span has geometry. + { + double lo = std::numeric_limits::max(), hi = std::numeric_limits::lowest(); + for (const stl_vertex &v : mesh.its.vertices) { + lo = std::min(lo, v.y() + v.z()); + hi = std::max(hi, v.y() + v.z()); + } + const double span = (hi - lo) / std::sqrt(2.); + size_t nonempty = 0; + for (const Layer *layer : object.layers()) + if (! layer->lslices.empty()) + ++ nonempty; + INFO("non-empty object layers " << nonempty << ", slicing span " << span << " mm"); + CHECK(double(nonempty) * 0.2 > span - 0.6); + } BeltFloorContext floor; REQUIRE(floor.init(object.slicing_parameters(), print.config()));