From 6a2d690f459e265963eb81ddf3aa9a615e95bc82 Mon Sep 17 00:00:00 2001 From: Joseph Robertson <32232925+HarrierPigeon@users.noreply.github.com> Date: Mon, 18 May 2026 19:01:43 -0500 Subject: [PATCH] Decouple Slicing From Machine Frame Logic (#21) * minor logic swap * first attempt, has a race condition * fixed the offset issue * found a solution, I think things work now (at least once I quash this race condition) * still chasing down race conditions * add manual shear / scale order strategy swap * tweak manual shear, fix ui uninitialization crash * fix z height / g-code desync issue * fix shear then scale cutoff planes * getting closer * fix support termination planes * fix incorrect offsets in shear-then-scale mode * test - fix overextrusion due to model/layer scale --- .claude/scheduled_tasks.lock | 1 - src/libslic3r/BeltGCode.cpp | 52 ++++++- src/libslic3r/BeltGCodeWriter.cpp | 26 +++- src/libslic3r/BeltGCodeWriter.hpp | 9 +- src/libslic3r/BeltSliceStrategy.cpp | 17 +- src/libslic3r/BeltSliceStrategy.hpp | 9 +- src/libslic3r/BeltTransform.cpp | 73 +++++++-- src/libslic3r/BeltTransform.hpp | 4 +- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 14 ++ src/libslic3r/GCode/MachineFrameTransform.cpp | 147 ++++++++++++++++++ src/libslic3r/GCode/MachineFrameTransform.hpp | 43 +++++ src/libslic3r/Preset.cpp | 9 ++ src/libslic3r/Print.cpp | 12 ++ src/libslic3r/PrintConfig.cpp | 65 ++++++++ src/libslic3r/PrintConfig.hpp | 30 ++++ src/libslic3r/PrintObject.cpp | 14 +- src/libslic3r/PrintObjectSlice.cpp | 53 ++++++- src/slic3r/GUI/Tab.cpp | 87 +++++++++++ 19 files changed, 621 insertions(+), 46 deletions(-) delete mode 100644 .claude/scheduled_tasks.lock create mode 100644 src/libslic3r/GCode/MachineFrameTransform.cpp create mode 100644 src/libslic3r/GCode/MachineFrameTransform.hpp diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index cce3208834..0000000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"597ab29b-cd70-40f4-95d9-fee98b3968ad","pid":1159657,"acquiredAt":1775936463783} \ No newline at end of file diff --git a/src/libslic3r/BeltGCode.cpp b/src/libslic3r/BeltGCode.cpp index a34acf95bd..767d280579 100644 --- a/src/libslic3r/BeltGCode.cpp +++ b/src/libslic3r/BeltGCode.cpp @@ -17,6 +17,7 @@ void BeltGCode::init_belt_writer(Print &print, bool is_bbl_printers) belt_writer->set_belt_angle(print.config().belt_printer_angle.value); // Axis remap and build volume max are set by base GCode after init_belt_writer returns. belt_writer->set_belt_back_transform(print.config()); + belt_writer->set_machine_frame_transform(print.config()); m_writer = std::move(belt_writer); // Per-axis origin snap config. @@ -52,12 +53,33 @@ void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print) file.write_format("; belt_scale_y_angle = %.1f\n", print.config().belt_scale_y_angle.value); file.write_format("; belt_scale_z = %s\n", full_cfg.opt_serialize("belt_scale_z").c_str()); file.write_format("; belt_scale_z_angle = %.1f\n", print.config().belt_scale_z_angle.value); + file.write_format("; belt_mesh_transform_order = %s\n", full_cfg.opt_serialize("belt_mesh_transform_order").c_str()); // 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 configs + file.write_format("; gcode_shear_x = %s\n", full_cfg.opt_serialize("gcode_shear_x").c_str()); + file.write_format("; gcode_shear_x_angle = %.1f\n", print.config().gcode_shear_x_angle.value); + file.write_format("; gcode_shear_x_from = %s\n", full_cfg.opt_serialize("gcode_shear_x_from").c_str()); + file.write_format("; gcode_shear_y = %s\n", full_cfg.opt_serialize("gcode_shear_y").c_str()); + file.write_format("; gcode_shear_y_angle = %.1f\n", print.config().gcode_shear_y_angle.value); + file.write_format("; gcode_shear_y_from = %s\n", full_cfg.opt_serialize("gcode_shear_y_from").c_str()); + file.write_format("; gcode_shear_z = %s\n", full_cfg.opt_serialize("gcode_shear_z").c_str()); + file.write_format("; gcode_shear_z_angle = %.1f\n", print.config().gcode_shear_z_angle.value); + file.write_format("; gcode_shear_z_from = %s\n", full_cfg.opt_serialize("gcode_shear_z_from").c_str()); + file.write_format("; gcode_scale_x = %s\n", full_cfg.opt_serialize("gcode_scale_x").c_str()); + file.write_format("; gcode_scale_x_angle = %.1f\n", print.config().gcode_scale_x_angle.value); + file.write_format("; gcode_scale_y = %s\n", full_cfg.opt_serialize("gcode_scale_y").c_str()); + file.write_format("; gcode_scale_y_angle = %.1f\n", print.config().gcode_scale_y_angle.value); + file.write_format("; gcode_scale_z = %s\n", full_cfg.opt_serialize("gcode_scale_z").c_str()); + file.write_format("; gcode_scale_z_angle = %.1f\n", print.config().gcode_scale_z_angle.value); + file.write_format("; belt_gcode_transform_order = %s\n", full_cfg.opt_serialize("belt_gcode_transform_order").c_str()); + file.write_format("; post_gcode_remap_x = %s\n", full_cfg.opt_serialize("post_gcode_remap_x").c_str()); + file.write_format("; post_gcode_remap_y = %s\n", full_cfg.opt_serialize("post_gcode_remap_y").c_str()); + file.write_format("; post_gcode_remap_z = %s\n", full_cfg.opt_serialize("post_gcode_remap_z").c_str()); } void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift) @@ -67,20 +89,31 @@ void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift) // back_transform(T * origin) = origin (correct machine position). // This replaces the bbox-based axis snap with an exact formula. // - // Two flags trigger this path: + // Flags that trigger this path: // belt_preslice_global — full pipeline (scale * shear * remap) is global // preslice_remap_global — only the pre-slice remap is global + // belt_shear_z_global — Z-row shear treated as global (matches per-axis + // Z-offset added in PrintObjectSlice.cpp) // The XY origin adjustment uses the FULL forward transform either way, // because the back_transform applied during G-code emission is always the - // inverse of the full pipeline. When only the remap is configured, both - // flags produce identical math (T == R). + // inverse of the full pipeline. Without pre-multiplication under + // ShearThenScale order with sy != 1, machine_y of bed position cy ends up + // at cy/sy instead of cy, which also leaves the object bottom off the belt + // plane. bool use_global = m_config.belt_preslice_global.value || (m_config.preslice_remap_global.value - && BeltTransformPipeline::has_preslice_remap(m_config)); + && BeltTransformPipeline::has_preslice_remap(m_config)) + || (m_config.belt_shear_z_global.value + && m_config.belt_shear_z.value != BeltShearMode::None); if (use_global && m_config.belt_printer.value) { auto *belt_writer = dynamic_cast(m_writer.get()); if (belt_writer) { - // Clear snap — not needed with computed corrections + // The per-object lift (z_shift_val = max(0, -m_belt_min_z)) added by + // BeltSliceStrategy::apply_to_trafo is already compensated inside + // global_z_offset (via the shear_min_z term in PrintObjectSlice.cpp's + // preslice_global branch). Snap was previously used here for the same + // purpose, but with both active the lift gets subtracted twice. Clear + // any leftover snap state from a prior instance. for (int a = 0; a < 3; ++a) belt_writer->set_origin_snap(a, false, 0., 0.); } @@ -125,7 +158,12 @@ void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift) unscale(inst_shift.y()), obj->belt_global_z_offset()); - // Compute this instance's machine-space bbox min + // Compute this instance's bbox min in the Cartesian frame (post back_transform + // + axis_remap, before machine_frame_transform). Using to_cartesian instead of + // to_machine_coords ensures the 8 axis-aligned bbox corners coincide with the + // geometry's extreme points — a property that breaks under shear, which would + // mis-normalize non-cubic shapes (inverted cone, benchy) by their bbox-volume + // corners rather than their actual lowest geometry point. BoundingBoxf3 bb = obj->model_object()->raw_bounding_box(); Vec3d mn = bb.min.cast(), mx = bb.max.cast(); Vec3d inst_min(std::numeric_limits::max(), @@ -135,7 +173,7 @@ void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift) Vec3d c((i & 1) ? mx.x() : mn.x(), (i & 2) ? mx.y() : mn.y(), (i & 4) ? mx.z() : mn.z()); - Vec3d mc = belt_writer->to_machine_coords(full * c + shift); + Vec3d mc = belt_writer->to_cartesian(full * c + shift); for (int a = 0; a < 3; ++a) inst_min[a] = std::min(inst_min[a], mc[a]); } diff --git a/src/libslic3r/BeltGCodeWriter.cpp b/src/libslic3r/BeltGCodeWriter.cpp index ea90f2588e..1987d0127a 100644 --- a/src/libslic3r/BeltGCodeWriter.cpp +++ b/src/libslic3r/BeltGCodeWriter.cpp @@ -34,6 +34,11 @@ void BeltGCodeWriter::set_belt_back_transform(const PrintConfig &config) m_belt_back_transform.init_from_config(config); } +void BeltGCodeWriter::set_machine_frame_transform(const PrintConfig &config) +{ + m_machine_frame_transform.init_from_config(config); +} + void BeltGCodeWriter::set_origin_snap(int axis, bool enable, double offset, double bbox_min) { if (axis >= 0 && axis < 3) { @@ -43,17 +48,26 @@ void BeltGCodeWriter::set_origin_snap(int axis, bool enable, double offset, doub } } +Vec3d BeltGCodeWriter::to_cartesian(const Vec3d &pos) const +{ + // back_transform → axis_remap, no origin_snap, no machine_frame_transform. + return apply_axis_remap(m_belt_back_transform.apply(pos)); +} + Vec3d BeltGCodeWriter::to_machine_coords(const Vec3d &pos) const { - // Step 1: Undo the shear/scale applied during slicing. - Vec3d p = m_belt_back_transform.apply(pos); - // Step 2: Apply axis remap (uses inherited base class method). - Vec3d result = apply_axis_remap(p); - // Step 3: Per-axis origin snap. + // Step 1+2: To Cartesian (back_transform + axis_remap). + Vec3d result = to_cartesian(pos); + // Step 3: Per-axis origin snap (computed in the Cartesian frame). for (int i = 0; i < 3; ++i) if (m_origin_snap[i]) result[i] -= (m_origin_bbox_min[i] - m_origin_offset[i]); - return result; + // Step 4: Machine-frame transform (gcode_shear / gcode_scale / post_gcode_remap) + // applied LAST so it acts as a global linear transform on the placed coords. + // Order matters: putting it before origin_snap would feed sheared bbox corners + // into the snap's per-object min calculation, mis-normalizing non-cubic geometries + // (the corners of the original bbox aren't extreme points of the sheared shape). + return m_machine_frame_transform.apply(result); } // ---- Overridden movement methods ------------------------------------------ diff --git a/src/libslic3r/BeltGCodeWriter.hpp b/src/libslic3r/BeltGCodeWriter.hpp index 828dd43bb3..60fd93f3e0 100644 --- a/src/libslic3r/BeltGCodeWriter.hpp +++ b/src/libslic3r/BeltGCodeWriter.hpp @@ -2,6 +2,7 @@ #include "GCodeWriter.hpp" #include "GCode/BeltBackTransform.hpp" +#include "GCode/MachineFrameTransform.hpp" namespace Slic3r { @@ -21,8 +22,13 @@ public: void set_belt_angle(double angle_deg); bool is_belt_printer() const { return m_belt_angle_rad != 0.; } void set_belt_back_transform(const PrintConfig &config); + void set_machine_frame_transform(const PrintConfig &config); void set_origin_snap(int axis, bool enable, double offset, double bbox_min); Vec3d to_machine_coords(const Vec3d &pos) const; + // back_transform + axis_remap only (no origin_snap, no machine_frame_transform). + // Used by on_set_origin for bbox computation in the Cartesian frame, where + // axis-aligned bbox corners coincide with the geometry's extreme points. + Vec3d to_cartesian(const Vec3d &pos) const; // First-layer plane: when set to a non-null active evaluator, travel // speed selection consults the plane per-move and uses @@ -47,7 +53,8 @@ protected: private: double m_belt_angle_rad = 0.; - BeltBackTransform m_belt_back_transform; + BeltBackTransform m_belt_back_transform; + MachineFrameTransform m_machine_frame_transform; bool m_origin_snap[3] = {false, false, false}; double m_origin_offset[3] = {0., 0., 0.}; double m_origin_bbox_min[3] = {0., 0., 0.}; diff --git a/src/libslic3r/BeltSliceStrategy.cpp b/src/libslic3r/BeltSliceStrategy.cpp index c1a300869e..850a4ad221 100644 --- a/src/libslic3r/BeltSliceStrategy.cpp +++ b/src/libslic3r/BeltSliceStrategy.cpp @@ -1,6 +1,7 @@ #include "BeltSliceStrategy.hpp" #include +#include namespace Slic3r { @@ -15,6 +16,7 @@ BeltSliceStrategy::BeltSliceStrategy(const PrintConfig &config) { m_shear = BeltTransformPipeline::build_shear_matrix(config, &m_has_shear); m_scale = BeltTransformPipeline::build_scale_matrix(config, &m_has_scale); + m_order = config.belt_mesh_transform_order.value; } void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, @@ -22,10 +24,13 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, bool has_remap, double *out_belt_min_z) const { - // Shear + scale (belt-only; pre-slice remap is handled separately). + // ScaleThenShear: applied to a point, scale runs first then shear (m_shear * m_scale). + // ShearThenScale: applied to a point, shear runs first then scale (m_scale * m_shear). if (m_has_shear || m_has_scale) { Transform3d belt_xform = Transform3d::Identity(); - belt_xform.linear() = m_scale * m_shear; + belt_xform.linear() = (m_order == BeltTransformOrder::ScaleThenShear) + ? Matrix3d(m_shear * m_scale) + : Matrix3d(m_scale * m_shear); trafo = belt_xform * trafo; } @@ -47,8 +52,12 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, z_shift.matrix()(2, 3) = belt_z_shift_val; trafo = z_shift * trafo; } - if (out_belt_min_z) - *out_belt_min_z = (min_z != std::numeric_limits::max()) ? min_z : 0.; + if (out_belt_min_z) { + double new_val = (min_z != std::numeric_limits::max()) ? min_z : 0.; + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_min_z tid=" << std::this_thread::get_id() + << " target=" << out_belt_min_z << " old=" << *out_belt_min_z << " new=" << new_val; + *out_belt_min_z = new_val; + } } } diff --git a/src/libslic3r/BeltSliceStrategy.hpp b/src/libslic3r/BeltSliceStrategy.hpp index ef6e038ee7..c2362e3552 100644 --- a/src/libslic3r/BeltSliceStrategy.hpp +++ b/src/libslic3r/BeltSliceStrategy.hpp @@ -34,10 +34,11 @@ public: private: explicit BeltSliceStrategy(const PrintConfig &config); - bool m_has_shear = false; - bool m_has_scale = false; - Matrix3d m_shear = Matrix3d::Identity(); - Matrix3d m_scale = Matrix3d::Identity(); + bool m_has_shear = false; + bool m_has_scale = false; + Matrix3d m_shear = Matrix3d::Identity(); + Matrix3d m_scale = Matrix3d::Identity(); + BeltTransformOrder m_order = BeltTransformOrder::ScaleThenShear; }; } // namespace Slic3r diff --git a/src/libslic3r/BeltTransform.cpp b/src/libslic3r/BeltTransform.cpp index 9bb66fcfb6..6c0838b2eb 100644 --- a/src/libslic3r/BeltTransform.cpp +++ b/src/libslic3r/BeltTransform.cpp @@ -103,9 +103,15 @@ Transform3d BeltTransformPipeline::build_forward_transform(const PrintConfig &co bool scale_active = false; Matrix3d scale = build_scale_matrix(config, &scale_active); - // Pipeline: scale * shear * pre_remap + // Match the mesh-side ordering selected by belt_mesh_transform_order so + // BeltBackTransform inverts the same composition that BeltSliceStrategy + // applied to the mesh. + // ScaleThenShear: applied to p, scale runs first then shear (shear * scale). + // ShearThenScale: applied to p, shear runs first then scale (scale * shear). Transform3d combined = Transform3d::Identity(); - combined.linear() = scale * shear; + combined.linear() = (config.belt_mesh_transform_order.value == BeltTransformOrder::ScaleThenShear) + ? Matrix3d(shear * scale) + : Matrix3d(scale * shear); combined = combined * pre_remap; return combined; } @@ -160,19 +166,30 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl( BeltTransformPipeline::BeltHeightResult result; result.object_height = original_height; - // Extract Z-axis shear/scale config. + // Extract Z-axis shear/scale + per-axis scale + transform order from config. BeltShearMode z_shear_mode; double z_shear_angle; BeltScaleMode z_scale_mode; double z_scale_angle; int z_shear_from; + BeltScaleMode from_scale_mode; // scale on the shear's source axis + double from_scale_angle; + BeltTransformOrder order; if constexpr (std::is_same_v) { - z_shear_mode = config.belt_shear_z.value; - z_shear_angle = config.belt_shear_z_angle.value; - z_scale_mode = config.belt_scale_z.value; - z_scale_angle = config.belt_scale_z_angle.value; - z_shear_from = int(config.belt_shear_z_from.value); + z_shear_mode = config.belt_shear_z.value; + z_shear_angle = config.belt_shear_z_angle.value; + z_scale_mode = config.belt_scale_z.value; + z_scale_angle = config.belt_scale_z_angle.value; + z_shear_from = int(config.belt_shear_z_from.value); + order = config.belt_mesh_transform_order.value; + if (z_shear_from == 0) { + from_scale_mode = config.belt_scale_x.value; + from_scale_angle = config.belt_scale_x_angle.value; + } else { + from_scale_mode = config.belt_scale_y.value; + from_scale_angle = config.belt_scale_y_angle.value; + } } else { // DynamicPrintConfig path auto get_shear = [&](const char *key) { @@ -191,11 +208,23 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl( auto *opt = config.template option>(key); return opt ? int(opt->value) : 1; }; + auto get_order = [&](const char *key) { + auto *opt = config.template option>(key); + return opt ? opt->value : BeltTransformOrder::ScaleThenShear; + }; z_shear_mode = get_shear("belt_shear_z"); z_shear_angle = get_float("belt_shear_z_angle"); z_scale_mode = get_scale("belt_scale_z"); z_scale_angle = get_float("belt_scale_z_angle"); z_shear_from = get_axis("belt_shear_z_from"); + order = get_order("belt_mesh_transform_order"); + if (z_shear_from == 0) { + from_scale_mode = get_scale("belt_scale_x"); + from_scale_angle = get_float("belt_scale_x_angle"); + } else { + from_scale_mode = get_scale("belt_scale_y"); + from_scale_angle = get_float("belt_scale_y_angle"); + } } bool has_z_shear = z_shear_mode != BeltShearMode::None; @@ -206,7 +235,8 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl( double shear_factor = has_z_shear ? BeltTransformPipeline::compute_shear_factor(z_shear_mode, z_shear_angle) : 0.; - double scale_z = BeltTransformPipeline::compute_scale_factor(z_scale_mode, z_scale_angle); + double scale_z = BeltTransformPipeline::compute_scale_factor(z_scale_mode, z_scale_angle); + double scale_from = BeltTransformPipeline::compute_scale_factor(from_scale_mode, from_scale_angle); if (has_z_shear && std::abs(shear_factor) > EPSILON) { int from = z_shear_from; @@ -214,14 +244,29 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl( double max_rz = std::numeric_limits::lowest(); for (double vz : {bb.min.z(), bb.max.z()}) for (double vs : {bb.min(from), bb.max(from)}) { - double new_z = scale_z * (vz + shear_factor * vs); + // Mesh-frame new_z computed per ordering. + // scale-then-shear: Z_s = sz*Z_m + s_from*tan(α)*from_m + // shear-then-scale: Z_s = sz*(Z_m + tan(α)*from_m) + double new_z = (order == BeltTransformOrder::ScaleThenShear) + ? scale_z * vz + scale_from * shear_factor * vs + : scale_z * (vz + shear_factor * vs); min_rz = std::min(min_rz, new_z); max_rz = std::max(max_rz, new_z); } - result.object_height = max_rz - min_rz; - result.floor_params.shear_factor = shear_factor; - result.floor_params.from_axis = from; - result.floor_params.z_shift = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.); + result.object_height = max_rz - min_rz; + // Effective slicer-frame slope of the belt surface (Z_m=0 line): + // scale-then-shear: Z_s = tan(α) * Y_s → slope = tan(α) + // shear-then-scale: Z_s = sz/s_from * tan(α) * Y_s → slope = sz*tan(α)/s_from + // The downstream cutoff formula `Y_s = (print_z - z_shift) / slope` + // and floor_print_z(Y_s) = slope * Y_s + z_shift use this slope. + double effective_shear = (order == BeltTransformOrder::ScaleThenShear) + ? shear_factor + : (std::abs(scale_from) > EPSILON + ? scale_z * shear_factor / scale_from + : shear_factor); + result.floor_params.shear_factor = effective_shear; + result.floor_params.from_axis = from; + result.floor_params.z_shift = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.); } else { result.object_height = original_height * scale_z; } diff --git a/src/libslic3r/BeltTransform.hpp b/src/libslic3r/BeltTransform.hpp index 3c0665c252..f15a9995c3 100644 --- a/src/libslic3r/BeltTransform.hpp +++ b/src/libslic3r/BeltTransform.hpp @@ -105,7 +105,9 @@ public: // Also sets has_scale_out if non-null. static Matrix3d build_scale_matrix(const PrintConfig &config, bool *has_scale_out = nullptr); - // Combined forward transform: scale * shear * pre_remap. + // Combined forward transform. Shear/scale order is selected by + // belt_mesh_transform_order so the result matches what BeltSliceStrategy + // applied to the mesh (BeltBackTransform inverts this). // Does NOT include the per-object Z-shift. static Transform3d build_forward_transform(const PrintConfig &config); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index a887336361..f9910dc894 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -221,6 +221,8 @@ set(lisbslic3r_sources GCode/AvoidCrossingPerimeters.hpp GCode/BeltBackTransform.cpp GCode/BeltBackTransform.hpp + GCode/MachineFrameTransform.cpp + GCode/MachineFrameTransform.hpp GCode/ConflictChecker.cpp GCode/ConflictChecker.hpp GCode/CoolingBuffer.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c3c3532364..b2aba1e35c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6462,6 +6462,20 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, auto _mm3_per_mm = path.mm3_per_mm * this->config().print_flow_ratio; _mm3_per_mm *= filament_flow_ratio; + // Belt printer: compensate for the volume change introduced by the mesh + // forward transform. path.mm3_per_mm is derived from slicer-frame layer + // height × line width, but a slicer-frame slab of volume V maps under the + // back-transform to a machine-frame region of volume V / |det(T)|. When + // det(T) > 1 — e.g. belt_scale_y = 1/cos(α) ≈ 1.155 at α = 30° — the + // machine slab holds less plastic than the slicer thinks it is filling + // and we over-extrude by det(T). Pure shear has det 1, so this is a + // no-op until scale factors deviate from 1. + if (m_config.belt_printer.value) { + double det = std::abs(BeltTransformPipeline::build_forward_transform(m_config).linear().determinant()); + if (det > EPSILON) + _mm3_per_mm /= det; + } + if (path.role() == erTopSolidInfill) { _mm3_per_mm *= m_config.top_solid_infill_flow_ratio; } else if (path.role() == erBottomSurface) { diff --git a/src/libslic3r/GCode/MachineFrameTransform.cpp b/src/libslic3r/GCode/MachineFrameTransform.cpp new file mode 100644 index 0000000000..10a658c7f7 --- /dev/null +++ b/src/libslic3r/GCode/MachineFrameTransform.cpp @@ -0,0 +1,147 @@ +#include "MachineFrameTransform.hpp" +#include "../BeltTransform.hpp" +#include "../BoundingBox.hpp" + +namespace Slic3r { + +namespace { + +// Build the post-gcode axis-remap transform (mirrors BeltTransformPipeline::build_preslice_remap +// but reads post_gcode_remap_* keys). Includes Rev-mode translation derived from build volume. +Transform3d build_post_gcode_remap(const PrintConfig &config) +{ + Transform3d remap = Transform3d::Identity(); + + int rx = int(config.post_gcode_remap_x.value); + int ry = int(config.post_gcode_remap_y.value); + int rz = int(config.post_gcode_remap_z.value); + + if (rx == int(RemapAxis::PosX) && ry == int(RemapAxis::PosY) && rz == int(RemapAxis::PosZ)) + return remap; + + auto remap_column = [](int r) -> Vec3d { + int axis = r % 3; + Vec3d col = Vec3d::Zero(); + if (r < 3) col[axis] = 1.0; + else if (r < 6) col[axis] = -1.0; + else col[axis] = -1.0; // Rev: max - pos + return col; + }; + + Matrix3d lin; + lin.col(0) = remap_column(rx); + lin.col(1) = remap_column(ry); + lin.col(2) = remap_column(rz); + remap.linear() = lin; + + if (rx >= 6 || ry >= 6 || 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 trans = Vec3d::Zero(); + auto add_rev = [&](int r, int out) { + if (r >= 6) trans[out] = vol_max[r % 3]; + }; + add_rev(rx, 0); + add_rev(ry, 1); + add_rev(rz, 2); + remap.translation() = trans; + } + + return remap; +} + +// Build the 3x3 shear matrix from gcode_shear_* keys. +Matrix3d build_gcode_shear_matrix(const PrintConfig &config, bool &active) +{ + struct AxisShear { BeltShearMode mode; double angle; int from; }; + AxisShear axes[3] = { + { config.gcode_shear_x.value, config.gcode_shear_x_angle.value, int(config.gcode_shear_x_from.value) }, + { config.gcode_shear_y.value, config.gcode_shear_y_angle.value, int(config.gcode_shear_y_from.value) }, + { config.gcode_shear_z.value, config.gcode_shear_z_angle.value, int(config.gcode_shear_z_from.value) }, + }; + + Matrix3d shear = Matrix3d::Identity(); + active = false; + for (int row = 0; row < 3; ++row) { + if (axes[row].mode != BeltShearMode::None) { + double factor = BeltTransformPipeline::compute_shear_factor(axes[row].mode, axes[row].angle); + if (std::abs(factor) > EPSILON) { + shear(row, axes[row].from) += factor; + active = true; + } + } + } + return shear; +} + +// Build the 3x3 diagonal scale matrix from gcode_scale_* keys. +Matrix3d build_gcode_scale_matrix(const PrintConfig &config, bool &active) +{ + double sx = BeltTransformPipeline::compute_scale_factor(config.gcode_scale_x.value, config.gcode_scale_x_angle.value); + double sy = BeltTransformPipeline::compute_scale_factor(config.gcode_scale_y.value, config.gcode_scale_y_angle.value); + double sz = BeltTransformPipeline::compute_scale_factor(config.gcode_scale_z.value, config.gcode_scale_z_angle.value); + + active = (std::abs(sx - 1.) > EPSILON || + std::abs(sy - 1.) > EPSILON || + std::abs(sz - 1.) > EPSILON); + + Matrix3d scale = Matrix3d::Identity(); + if (active) { + scale(0, 0) = sx; + scale(1, 1) = sy; + scale(2, 2) = sz; + } + return scale; +} + +bool has_post_gcode_remap(const PrintConfig &config) +{ + return int(config.post_gcode_remap_x.value) != int(RemapAxis::PosX) || + int(config.post_gcode_remap_y.value) != int(RemapAxis::PosY) || + int(config.post_gcode_remap_z.value) != int(RemapAxis::PosZ); +} + +} // namespace + +bool MachineFrameTransform::init_from_config(const PrintConfig &config) +{ + m_active = false; + m_transform = Transform3d::Identity(); + + if (!config.belt_printer.value) + return false; + + Transform3d post_remap = build_post_gcode_remap(config); + bool shear_active = false; + Matrix3d shear = build_gcode_shear_matrix(config, shear_active); + bool scale_active = false; + Matrix3d scale = build_gcode_scale_matrix(config, scale_active); + + if (!shear_active && !scale_active && !has_post_gcode_remap(config)) + return false; + + // Compose per belt_gcode_transform_order: + // ScaleThenShear: applied to p, scale runs first then shear (shear * scale). + // ShearThenScale: applied to p, shear runs first then scale (scale * shear). + Transform3d combined = Transform3d::Identity(); + combined.linear() = (config.belt_gcode_transform_order.value == BeltTransformOrder::ScaleThenShear) + ? Matrix3d(shear * scale) + : Matrix3d(scale * shear); + combined = combined * post_remap; + + if (combined.isApprox(Transform3d::Identity())) + return false; + + m_transform = combined; + m_active = true; + return true; +} + +Vec3d MachineFrameTransform::apply(const Vec3d &pos) const +{ + if (!m_active) + return pos; + return m_transform * pos; +} + +} // namespace Slic3r diff --git a/src/libslic3r/GCode/MachineFrameTransform.hpp b/src/libslic3r/GCode/MachineFrameTransform.hpp new file mode 100644 index 0000000000..4a2f189802 --- /dev/null +++ b/src/libslic3r/GCode/MachineFrameTransform.hpp @@ -0,0 +1,43 @@ +#ifndef slic3r_MachineFrameTransform_hpp_ +#define slic3r_MachineFrameTransform_hpp_ + +#include "../libslic3r.h" +#include "../Point.hpp" +#include "../PrintConfig.hpp" + +namespace Slic3r { + +// Post-stage machine-frame transform for belt printers. +// +// Applied in BeltGCodeWriter::to_machine_coords AFTER the back-transform +// and the existing gcode_remap_* axis remap, and BEFORE per-axis origin snap. +// Maps Cartesian (axis-permuted) G-code coordinates into the printer's +// physical machine frame using a parallel set of options: +// gcode_shear_x/y/z + _angle + _from +// gcode_scale_x/y/z + _angle +// post_gcode_remap_x/y/z +// +// Composition matches the mesh-side pipeline: shear * scale * post_remap. +class MachineFrameTransform +{ +public: + MachineFrameTransform() = default; + + // Initialize from belt printer config. Returns true if a non-identity + // transform was computed. Inactive when belt_printer is disabled or + // all three sub-stages are identity. + bool init_from_config(const PrintConfig &config); + + // Apply the transform to a point. Returns pos unchanged if not active. + Vec3d apply(const Vec3d &pos) const; + + bool is_active() const { return m_active; } + +private: + bool m_active = false; + Transform3d m_transform = Transform3d::Identity(); +}; + +} // namespace Slic3r + +#endif // slic3r_MachineFrameTransform_hpp_ diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 6c6382f91d..a62628b3cd 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1326,8 +1326,17 @@ static std::vector s_Preset_printer_options { "belt_shear_y", "belt_shear_y_angle", "belt_shear_y_from", "belt_shear_y_global", "belt_shear_z", "belt_shear_z_angle", "belt_shear_z_from", "belt_shear_z_global", "belt_scale_x", "belt_scale_x_angle", "belt_scale_y", "belt_scale_y_angle", "belt_scale_z", "belt_scale_z_angle", + "belt_mesh_transform_order", "preslice_remap_x", "preslice_remap_y", "preslice_remap_z", "preslice_remap_global", "gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "gcode_back_transform", + "gcode_shear_x", "gcode_shear_x_angle", "gcode_shear_x_from", + "gcode_shear_y", "gcode_shear_y_angle", "gcode_shear_y_from", + "gcode_shear_z", "gcode_shear_z_angle", "gcode_shear_z_from", + "gcode_scale_x", "gcode_scale_x_angle", + "gcode_scale_y", "gcode_scale_y_angle", + "gcode_scale_z", "gcode_scale_z_angle", + "belt_gcode_transform_order", + "post_gcode_remap_x", "post_gcode_remap_y", "post_gcode_remap_z", "belt_preslice_global", "first_layer_plane", "first_layer_plane_offset", "first_layer_plane_thickness", "belt_origin_snap_x", "belt_origin_offset_x", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 09a8a50923..de5621a67a 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -104,6 +104,15 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "gcode_remap_x", "gcode_remap_y", "gcode_remap_z", + // Machine-frame transforms (only affect G-code output, not slicing). + "gcode_shear_x", "gcode_shear_x_angle", "gcode_shear_x_from", + "gcode_shear_y", "gcode_shear_y_angle", "gcode_shear_y_from", + "gcode_shear_z", "gcode_shear_z_angle", "gcode_shear_z_from", + "gcode_scale_x", "gcode_scale_x_angle", + "gcode_scale_y", "gcode_scale_y_angle", + "gcode_scale_z", "gcode_scale_z_angle", + "belt_gcode_transform_order", + "post_gcode_remap_x", "post_gcode_remap_y", "post_gcode_remap_z", "belt_origin_snap_x", "belt_origin_offset_x", "belt_origin_snap_y", "belt_origin_offset_y", "belt_origin_snap_z", "belt_origin_offset_z", @@ -310,6 +319,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "belt_scale_y_angle" || opt_key == "belt_scale_z" || opt_key == "belt_scale_z_angle" + || opt_key == "belt_mesh_transform_order" + || 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") { diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 5a9b63fc0f..ed0892ea04 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -307,6 +307,12 @@ static t_config_enum_values s_keys_map_BeltAxis { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltAxis) +static t_config_enum_values s_keys_map_BeltTransformOrder { + { "scale_then_shear", int(BeltTransformOrder::ScaleThenShear) }, + { "shear_then_scale", int(BeltTransformOrder::ShearThenScale) }, +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltTransformOrder) + static t_config_enum_values s_keys_map_BeltScaleMode { { "none", int(BeltScaleMode::None) }, { "inv_sin", int(BeltScaleMode::InvSin) }, @@ -6320,6 +6326,24 @@ void PrintConfigDef::init_fff_params() add_belt_scale_mode("belt_scale_z", "Function", BeltScaleMode::None); add_belt_scale_angle("belt_scale_z_angle", "Angle"); + auto add_belt_transform_order = [this](const char *key, const char *label, const char *tooltip) { + auto def = this->add(key, coEnum); + def->label = L(label); + def->category = L("Printable space"); + def->tooltip = L(tooltip); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = {"scale_then_shear", "shear_then_scale"}; + def->enum_labels = {L("Scale, then shear"), L("Shear, then scale")}; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(BeltTransformOrder::ScaleThenShear)); + }; + + add_belt_transform_order("belt_mesh_transform_order", "Mesh transform order", + "Order in which the mesh shear and scale matrices are composed before slicing. " + "'Scale, then shear' applies scale first and then shear (current default). " + "'Shear, then scale' applies shear first and then scale. The g-code back-transform " + "follows the same order so that it correctly inverts the mesh transform."); + // G-code axis remap with sign auto add_belt_remap = [this](const char *key, const char *label, const char *tooltip, RemapAxis default_axis) { auto def = this->add(key, coEnum); @@ -6367,6 +6391,47 @@ void PrintConfigDef::init_fff_params() 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); 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); + // Machine-frame G-code transforms: applied AFTER back-transform and gcode_remap, + // before per-axis origin snap. Maps Cartesian G-code to the printer's physical machine frame. + add_belt_shear_mode ("gcode_shear_x", "Function", BeltShearMode::None); + add_belt_shear_angle("gcode_shear_x_angle", "Angle"); + add_belt_axis_enum ("gcode_shear_x_from", "From", "Source axis for X shear in the machine-frame stage.", BeltAxis::Z); + + add_belt_shear_mode ("gcode_shear_y", "Function", BeltShearMode::None); + add_belt_shear_angle("gcode_shear_y_angle", "Angle"); + add_belt_axis_enum ("gcode_shear_y_from", "From", "Source axis for Y shear in the machine-frame stage.", BeltAxis::Z); + + add_belt_shear_mode ("gcode_shear_z", "Function", BeltShearMode::None); + add_belt_shear_angle("gcode_shear_z_angle", "Angle"); + add_belt_axis_enum ("gcode_shear_z_from", "From", "Source axis for Z shear in the machine-frame stage.", BeltAxis::Y); + + add_belt_scale_mode ("gcode_scale_x", "Function", BeltScaleMode::None); + add_belt_scale_angle("gcode_scale_x_angle", "Angle"); + + add_belt_scale_mode ("gcode_scale_y", "Function", BeltScaleMode::None); + add_belt_scale_angle("gcode_scale_y_angle", "Angle"); + + add_belt_scale_mode ("gcode_scale_z", "Function", BeltScaleMode::None); + add_belt_scale_angle("gcode_scale_z_angle", "Angle"); + + add_belt_transform_order("belt_gcode_transform_order", "G-code transform order", + "Order in which the machine-frame shear and scale matrices are composed when " + "applied to G-code coordinates. 'Scale, then shear' applies scale first and then " + "shear (current default). 'Shear, then scale' applies shear first and then scale."); + + add_belt_remap("post_gcode_remap_x", "X", + "Axis remap in the machine-frame stage. Applied AFTER gcode_remap, " + "to put coordinates into the printer's physical axis labelling. Default +X: no change.", + RemapAxis::PosX); + add_belt_remap("post_gcode_remap_y", "Y", + "Axis remap in the machine-frame stage. Applied AFTER gcode_remap, " + "to put coordinates into the printer's physical axis labelling. Default +Y: no change.", + RemapAxis::PosY); + add_belt_remap("post_gcode_remap_z", "Z", + "Axis remap in the machine-frame stage. Applied AFTER gcode_remap, " + "to put coordinates into the printer's physical axis labelling. Default +Z: no change.", + RemapAxis::PosZ); + def = this->add("gcode_back_transform", coBool); def->label = L("G-code back-transform"); def->category = L("Printable space"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 601cfcbb7e..904ecd742d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -183,6 +183,15 @@ enum class BeltAxis Z = 2, }; +// Order in which the belt shear and scale matrices are composed. +// ScaleThenShear: applied to a point p, the result is shear(scale(p)). +// ShearThenScale: applied to a point p, the result is scale(shear(p)). +enum class BeltTransformOrder +{ + ScaleThenShear = 0, + ShearThenScale = 1, +}; + enum class RemapAxis { PosX = 0, PosY = 1, PosZ = 2, @@ -571,6 +580,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltShearMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltScaleMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltAxis) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltTransformOrder) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(RemapAxis) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportFloorMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportZOffsetMode) @@ -1535,6 +1545,26 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionEnum, gcode_remap_x)) ((ConfigOptionEnum, gcode_remap_y)) ((ConfigOptionEnum, gcode_remap_z)) + ((ConfigOptionEnum, gcode_shear_x)) + ((ConfigOptionFloat, gcode_shear_x_angle)) + ((ConfigOptionEnum, gcode_shear_x_from)) + ((ConfigOptionEnum, gcode_shear_y)) + ((ConfigOptionFloat, gcode_shear_y_angle)) + ((ConfigOptionEnum, gcode_shear_y_from)) + ((ConfigOptionEnum, gcode_shear_z)) + ((ConfigOptionFloat, gcode_shear_z_angle)) + ((ConfigOptionEnum, gcode_shear_z_from)) + ((ConfigOptionEnum, gcode_scale_x)) + ((ConfigOptionFloat, gcode_scale_x_angle)) + ((ConfigOptionEnum, gcode_scale_y)) + ((ConfigOptionFloat, gcode_scale_y_angle)) + ((ConfigOptionEnum, gcode_scale_z)) + ((ConfigOptionFloat, gcode_scale_z_angle)) + ((ConfigOptionEnum, belt_mesh_transform_order)) + ((ConfigOptionEnum, belt_gcode_transform_order)) + ((ConfigOptionEnum, post_gcode_remap_x)) + ((ConfigOptionEnum, post_gcode_remap_y)) + ((ConfigOptionEnum, post_gcode_remap_z)) ((ConfigOptionBool, gcode_back_transform)) ((ConfigOptionBool, belt_preslice_global)) ((ConfigOptionEnum, first_layer_plane)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 78b75a8bbb..596053f685 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -3,6 +3,8 @@ #include "Point.hpp" #include "Print.hpp" #include "BeltTransform.hpp" + +#include #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "ElephantFootCompensation.hpp" @@ -425,11 +427,15 @@ std::vector> PrintObject::detect_extruder_geometric_unprintables() // 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal). void PrintObject::make_perimeters() { + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] make_perimeters request tid=" << std::this_thread::get_id() << " obj=" << this; // prerequisites this->slice(); - if (! this->set_started(posPerimeters)) + if (! this->set_started(posPerimeters)) { + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] make_perimeters SKIP tid=" << std::this_thread::get_id() << " obj=" << this << " (already started/done)"; return; + } + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] make_perimeters ENTER tid=" << std::this_thread::get_id() << " obj=" << this; m_print->set_status(15, L("Generating walls")); BOOST_LOG_TRIVIAL(info) << "Generating walls..." << log_memory_info(); @@ -527,6 +533,7 @@ void PrintObject::make_perimeters() m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end"; + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] make_perimeters EXIT tid=" << std::this_thread::get_id() << " obj=" << this; this->set_done(posPerimeters); } @@ -822,7 +829,9 @@ void PrintObject::detect_overhangs_for_lift() void PrintObject::generate_support_material() { + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] generate_support_material request tid=" << std::this_thread::get_id() << " obj=" << this; if (this->set_started(posSupportMaterial)) { + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] generate_support_material ENTER tid=" << std::this_thread::get_id() << " obj=" << this; this->clear_support_layers(); if(!has_support() && !m_print->get_no_check_flag()) { @@ -863,7 +872,10 @@ void PrintObject::generate_support_material() this->_generate_support_material(); m_print->throw_if_canceled(); } + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] generate_support_material EXIT tid=" << std::this_thread::get_id() << " obj=" << this; this->set_done(posSupportMaterial); + } else { + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] generate_support_material SKIP tid=" << std::this_thread::get_id() << " obj=" << this << " (already started/done)"; } } diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 23e3583a2f..4454ce87ea 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -837,8 +838,12 @@ void groupingVolumesForBrim(PrintObject* object, LayerPtrs& layers, int firstLay // Resulting expolygons of layer regions are marked as Internal. void PrintObject::slice() { - if (! this->set_started(posSlice)) + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] slice request tid=" << std::this_thread::get_id() << " obj=" << this; + if (! this->set_started(posSlice)) { + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] slice SKIP tid=" << std::this_thread::get_id() << " obj=" << this << " (already started/done)"; return; + } + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] slice ENTER tid=" << std::this_thread::get_id() << " obj=" << this; //BBS: add flag to reload scene for shell rendering m_print->set_status(5, L("Slicing mesh"), PrintBase::SlicingStatus::RELOAD_SCENE); std::vector layer_height_profile; @@ -942,11 +947,25 @@ void PrintObject::slice() Vec3d d(unscale(inst_shift.x()), unscale(inst_shift.y()), 0.); Vec3d c = T.linear() * d - d; - global_z_offset = c.z(); + // Per-object shape contribution: BeltSliceStrategy::apply_to_trafo + // lifts the mesh by max(0, -m_belt_min_z) to keep slicer-frame Z + // above 0. Two objects at the same bed position but different + // m_belt_min_z (e.g. cube vs inverted-cone tip) otherwise end up + // at the same print_z, which causes the inverted-cone tip to + // start on the same layer as the cube's lowest sheared corner. + double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg) + ? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.; + double shear_min_z = m_belt_min_z - belt_surface_z; + + global_z_offset = c.z() + shear_min_z; + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_global_xy_correction tid=" << std::this_thread::get_id() + << " obj=" << this << " old=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y() + << ") new=(" << c.x() << "," << c.y() << ")"; m_belt_global_xy_correction = Vec2d(c.x(), c.y()); BOOST_LOG_TRIVIAL(warning) << "Belt preslice_global: correction=(" - << c.x() << ", " << c.y() << ", " << c.z() << ")"; + << c.x() << ", " << c.y() << ", " << c.z() << ")" + << " shear_min_z=" << shear_min_z << " (m_belt_min_z=" << m_belt_min_z << ")"; } else { struct GAxis { BeltShearMode mode; double angle; int from; bool global; }; GAxis gaxes[3] = { @@ -959,13 +978,27 @@ void PrintObject::slice() // (X/Y row shears with global would offset X/Y, not Z — not useful here.) const auto &za = gaxes[2]; // Z row if (za.global && za.mode != BeltShearMode::None && za.from < 2) { - double factor = BeltTransformPipeline::compute_shear_factor(za.mode, za.angle); + // Use the full forward-transform correction (same formula as + // preslice_global) so the per-bed-position offset matches what + // BeltGCode::on_set_origin's T.linear() pre-multiplication + // expects after back-transform. The simple `cy*tan(α)` form + // is exact only for ScaleThenShear; under ShearThenScale with + // sy != 1 it leaves the object bottom off the belt plane by + // cy*tan(α)*(sy-1)/sy. + Transform3d T = BeltTransformPipeline::build_forward_transform(pcfg); + Vec3d d(unscale(inst_shift.x()), unscale(inst_shift.y()), 0.); + Vec3d c = T.linear() * d - d; double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg) ? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.; double shear_min_z = m_belt_min_z - belt_surface_z; - Point phys = inst_shift; - double center_on_axis = (za.from == 0) ? unscale(phys.x()) : unscale(phys.y()); - global_z_offset += center_on_axis * factor + shear_min_z; + global_z_offset += c.z() + shear_min_z; + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_global_xy_correction tid=" << std::this_thread::get_id() + << " obj=" << this << " old=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y() + << ") new=(" << c.x() << "," << c.y() << ")"; + m_belt_global_xy_correction = Vec2d(c.x(), c.y()); + BOOST_LOG_TRIVIAL(warning) << "Belt per-axis Z-shear-global: correction=(" + << c.x() << ", " << c.y() << ", " << c.z() << ")" + << " shear_min_z=" << shear_min_z << " (m_belt_min_z=" << m_belt_min_z << ")"; } // Pre-slice remap global mode: when on, the remap accounts for the @@ -983,6 +1016,8 @@ void PrintObject::slice() BOOST_LOG_TRIVIAL(warning) << "Belt global: z_offset=" << global_z_offset << " (relative to min across " << this->print()->objects().size() << " objects)"; + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_global_z_offset tid=" << std::this_thread::get_id() + << " obj=" << this << " old=" << m_belt_global_z_offset << " new=" << global_z_offset; m_belt_global_z_offset = global_z_offset; if (std::abs(global_z_offset) > EPSILON) { for (Layer *layer : m_layers) @@ -1003,6 +1038,10 @@ void PrintObject::slice() } // BBS + BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] slice EXIT tid=" << std::this_thread::get_id() << " obj=" << this + << " layers=" << m_layers.size() << " belt_min_z=" << m_belt_min_z + << " belt_global_z_offset=" << m_belt_global_z_offset + << " belt_xy=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y() << ")"; this->set_done(posSlice); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index e707386576..92302c583e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4491,6 +4491,7 @@ void TabPrinter::build_fff() line.append_option(optgroup->get_option("belt_scale_z_angle")); optgroup->append_line(line); } + optgroup->append_single_option_line("belt_mesh_transform_order"); { Line line = { L("Pre-slice axis remap"), L("Remap model axes before slicing so the slicer's coordinate system matches " @@ -4548,6 +4549,63 @@ void TabPrinter::build_fff() line.append_option(optgroup->get_option("belt_support_z_offset_mode")); optgroup->append_line(line); } + + // Machine-frame transforms: applied to G-code after back-transform and + // gcode_remap, before per-axis origin snap. Maps Cartesian G-code into + // the printer's physical machine frame. + { + auto mf = page->new_optgroup(L("Machine frame transforms"), L"param_advanced"); + { + Line line = { L("G-code shear X"), L("Shear applied to the X axis in the machine-frame stage (after back-transform and gcode_remap).") }; + line.append_option(mf->get_option("gcode_shear_x")); + line.append_option(mf->get_option("gcode_shear_x_angle")); + line.append_option(mf->get_option("gcode_shear_x_from")); + mf->append_line(line); + } + { + Line line = { L("G-code shear Y"), L("Shear applied to the Y axis in the machine-frame stage (after back-transform and gcode_remap).") }; + line.append_option(mf->get_option("gcode_shear_y")); + line.append_option(mf->get_option("gcode_shear_y_angle")); + line.append_option(mf->get_option("gcode_shear_y_from")); + mf->append_line(line); + } + { + Line line = { L("G-code shear Z"), L("Shear applied to the Z axis in the machine-frame stage (after back-transform and gcode_remap).") }; + line.append_option(mf->get_option("gcode_shear_z")); + line.append_option(mf->get_option("gcode_shear_z_angle")); + line.append_option(mf->get_option("gcode_shear_z_from")); + mf->append_line(line); + } + { + Line line = { L("G-code scale X"), L("Scale applied to the X axis in the machine-frame stage.") }; + line.append_option(mf->get_option("gcode_scale_x")); + line.append_option(mf->get_option("gcode_scale_x_angle")); + mf->append_line(line); + } + { + Line line = { L("G-code scale Y"), L("Scale applied to the Y axis in the machine-frame stage.") }; + line.append_option(mf->get_option("gcode_scale_y")); + line.append_option(mf->get_option("gcode_scale_y_angle")); + mf->append_line(line); + } + { + Line line = { L("G-code scale Z"), L("Scale applied to the Z axis in the machine-frame stage.") }; + line.append_option(mf->get_option("gcode_scale_z")); + line.append_option(mf->get_option("gcode_scale_z_angle")); + mf->append_line(line); + } + mf->append_single_option_line("belt_gcode_transform_order"); + { + Line line = { L("Post-gcode axis remap"), + L("Axis remap in the machine-frame stage. Applied AFTER gcode_remap, " + "to put coordinates into the printer's physical axis labelling.") }; + line.append_option(mf->get_option("post_gcode_remap_x")); + line.append_option(mf->get_option("post_gcode_remap_y")); + line.append_option(mf->get_option("post_gcode_remap_z")); + mf->append_line(line); + } + } + option = optgroup->get_option("thumbnails"); option.opt.full_width = true; optgroup->append_single_option_line(option, "printer_basic_information_advanced#g-code-thumbnails"); @@ -5395,6 +5453,7 @@ void TabPrinter::toggle_options() toggle_line("belt_printer_infinite_y", is_belt); for (auto el : {"belt_shear_x", "belt_shear_y", "belt_shear_z", "belt_scale_x", "belt_scale_y", "belt_scale_z", + "belt_mesh_transform_order", "belt_origin_snap_x", "belt_origin_snap_y", "belt_origin_snap_z"}) toggle_line(el, is_belt); @@ -5435,6 +5494,34 @@ void TabPrinter::toggle_options() auto scz = m_config->option>("belt_scale_z")->value; toggle_option("belt_scale_z_angle", is_belt && scz != BeltScaleMode::None); + // Machine-frame transforms: shown only in belt mode. + for (auto el : {"gcode_shear_x", "gcode_shear_y", "gcode_shear_z", + "gcode_scale_x", "gcode_scale_y", "gcode_scale_z", + "belt_gcode_transform_order", + "post_gcode_remap_x"}) + toggle_line(el, is_belt); + + auto gsx = m_config->option>("gcode_shear_x")->value; + toggle_option("gcode_shear_x_angle", is_belt && gsx != BeltShearMode::None); + toggle_option("gcode_shear_x_from", is_belt && gsx != BeltShearMode::None); + + auto gsy = m_config->option>("gcode_shear_y")->value; + toggle_option("gcode_shear_y_angle", is_belt && gsy != BeltShearMode::None); + toggle_option("gcode_shear_y_from", is_belt && gsy != BeltShearMode::None); + + auto gsz = m_config->option>("gcode_shear_z")->value; + toggle_option("gcode_shear_z_angle", is_belt && gsz != BeltShearMode::None); + toggle_option("gcode_shear_z_from", is_belt && gsz != BeltShearMode::None); + + auto gscx = m_config->option>("gcode_scale_x")->value; + toggle_option("gcode_scale_x_angle", is_belt && gscx != BeltScaleMode::None); + + auto gscy = m_config->option>("gcode_scale_y")->value; + toggle_option("gcode_scale_y_angle", is_belt && gscy != BeltScaleMode::None); + + auto gscz = m_config->option>("gcode_scale_z")->value; + toggle_option("gcode_scale_z_angle", is_belt && gscz != BeltScaleMode::None); + // Origin snap is superseded by belt_preslice_global toggle_option("belt_origin_offset_x", is_belt && m_config->opt_bool("belt_origin_snap_x") && !belt_global); toggle_option("belt_origin_offset_y", is_belt && m_config->opt_bool("belt_origin_snap_y") && !belt_global);