From 8b2e28817d03cf4cad7b5870798904765a6db1a8 Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Sat, 8 Aug 2026 12:33:41 -0500 Subject: [PATCH] fix non 45 degree slicing methods after regression created while cleaning up UI --- src/CMakeLists.txt | 8 +++- src/libslic3r/GCode/MachineFrameTransform.cpp | 33 +++++++++-------- src/libslic3r/GCode/MachineFrameTransform.hpp | 2 +- src/libslic3r/PrintConfig.cpp | 6 +-- tests/fff_print/test_gcodewriter.cpp | 37 +++++++++++++++++++ 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 79b49cfd16..1e650df95b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -90,6 +90,12 @@ if (SLIC3R_GUI) # list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc) find_package(wxInspector REQUIRED) + # wxInspector 1.0.0 installs its headers but accidentally declares the + # INSTALL_INTERFACE include directory PRIVATE, so its imported target does + # not expose them to consumers. Restore the package prefix include path until + # the upstream export is fixed. + get_filename_component(WXINSPECTOR_PREFIX "${wxInspector_DIR}/../../.." ABSOLUTE) + target_include_directories(wxInspector::wxInspector INTERFACE "${WXINSPECTOR_PREFIX}/include") list(APPEND wxWidgets_LIBRARIES "wxInspector::wxInspector") message(STATUS "wx libs: ${wxWidgets_LIBRARIES}") @@ -175,7 +181,7 @@ endif () # Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries. if (SLIC3R_GUI) # target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES}) -target_link_libraries(OrcaSlicer libslic3r_gui) +target_link_libraries(OrcaSlicer libslic3r_gui wxInspector::wxInspector) if (MSVC) # Generate debug symbols even in release mode. target_link_options(OrcaSlicer PUBLIC "$<$:/DEBUG>") diff --git a/src/libslic3r/GCode/MachineFrameTransform.cpp b/src/libslic3r/GCode/MachineFrameTransform.cpp index 3d322b4303..af09ba7353 100644 --- a/src/libslic3r/GCode/MachineFrameTransform.cpp +++ b/src/libslic3r/GCode/MachineFrameTransform.cpp @@ -29,31 +29,34 @@ bool MachineFrameTransform::init_from_config(const PrintConfig &config) return false; const double angle_rad = Geometry::deg2rad(angle_deg); - const double cos_a = std::cos(angle_rad); - if (std::abs(cos_a) <= EPSILON) + const double sin_a = std::sin(angle_rad); + if (std::abs(sin_a) <= EPSILON) return false; - const double tan_a = std::sin(angle_rad) / cos_a; - const double inv_cos = 1.0 / cos_a; + const double cot_a = std::cos(angle_rad) / sin_a; + const double inv_sin = 1.0 / std::abs(sin_a); - // Couple the height axis (Z) to the belt-feed axis and stretch the belt-feed - // axis by 1/cos so a unit slicing move maps to the correct belt travel. The - // shear sign matches the belt-floor slope derived from the same rotation in - // BeltTransformPipeline::compute_belt_height_and_floor: - // tilt about X: feed axis Y, Z += +tan·Y, scale Y *= 1/cos - // tilt about Y: feed axis X, Z += -tan·X, scale X *= 1/cos + // This stage runs after the conventional belt axis swap. For an X-axis + // slicing rotation, remapped Y is model height and remapped Z is travel + // along the belt. Convert those Cartesian coordinates to machine axes with + // the established belt-printer convention: + // machine gantry = model height / sin(a) + // machine belt = model belt + model height * cot(a) + // The Y-rotation case is the same mapping on X/Z, with the rotation sign. + // At 45 degrees tan/cot and sin/cos are equal, which previously hid the + // incorrect complementary-angle formulas used by this unified transform. Matrix3d shear = Matrix3d::Identity(); Matrix3d scale = Matrix3d::Identity(); if (axis == BeltRotationAxis::X) { - shear(2, 1) = tan_a; // Z from Y - scale(1, 1) = inv_cos; // Y + shear(2, 1) = cot_a; // Z from Y + scale(1, 1) = inv_sin; // Y } else { // BeltRotationAxis::Y - shear(2, 0) = -tan_a; // Z from X - scale(0, 0) = inv_cos; // X + shear(2, 0) = -cot_a; // Z from X + scale(0, 0) = inv_sin; // X } // Apply shear first, then scale (the historical default ShearThenScale order: // result = scale * shear * p). For the canonical 45°/X belt this maps - // (x,y,z) -> (x, y/cos, y + z), matching the previous per-axis config. + // (x,y,z) -> (x, y/sin, y + z), matching the previous per-axis config. Transform3d combined = Transform3d::Identity(); combined.linear() = scale * shear; diff --git a/src/libslic3r/GCode/MachineFrameTransform.hpp b/src/libslic3r/GCode/MachineFrameTransform.hpp index 744a7976c5..83519bdbe4 100644 --- a/src/libslic3r/GCode/MachineFrameTransform.hpp +++ b/src/libslic3r/GCode/MachineFrameTransform.hpp @@ -15,7 +15,7 @@ namespace Slic3r { // // Derived entirely from the single belt tilt (belt_slice_rotation axis + // belt_slice_rotation_angle): a shear coupling the height axis to the belt-feed -// axis (factor tan a) plus a 1/cos a scale on the belt-feed axis. The expert +// axis (factor cot a) plus a 1/sin a scale on the gantry-height axis. The expert // belt_frame_tilt_decouple flag lets the machine-frame angle differ from the // pre-slice rotation angle via belt_frame_tilt_angle. class MachineFrameTransform diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f641699d91..d0ce14ec35 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -7216,9 +7216,9 @@ void PrintConfigDef::init_fff_params() def = this->add("gcode_back_transform", coBool); def->label = L("G-code back-transform"); def->category = L("Printable space"); - def->tooltip = L("Reverse the shear/scale transform applied during slicing so G-code " - "coordinates are in the machine's physical coordinate space. " - "Requires at least one shear axis with global mode enabled."); + 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)); diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index 6af11d8d23..22ca387bbb 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -20,12 +20,49 @@ #include #include #include "libslic3r/BeltGCodeWriter.hpp" +#include "libslic3r/BeltTransform.hpp" #include "libslic3r/GCodeReader.hpp" #include "libslic3r/PrintConfig.hpp" using namespace Slic3r; using namespace Slic3r::Test; +TEST_CASE("Belt machine coordinates retain a non-45-degree slicing angle", "[GCodeWriter][belt]") +{ + PrintConfig config; + 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; + config.gcode_remap_z.value = RemapAxis::PosY; + + BeltGCodeWriter writer; + writer.set_belt_back_transform(config); + writer.set_machine_frame_transform(config); + writer.set_axis_remap(int(config.gcode_remap_x.value), + int(config.gcode_remap_y.value), + int(config.gcode_remap_z.value)); + + // Start with a point in the unrotated model frame, then feed the writer the + // same rotated coordinate produced by the pre-slice mesh transform. The + // back-transform must recover the model point before the axis swap and + // machine-frame shear/scale are applied. + const Vec3d model(4., 10., 3.); + Transform3d forward = BeltTransformPipeline::build_forward_transform(config); + const Vec3d machine = writer.to_machine_coords(forward * model); + + // The conventional X-tilt remap produces (x, z, y). At 30 degrees the + // gantry coordinate is z/sin(30) and belt travel is y + z*cot(30). + // The complementary tan/inv-cos formulas accidentally used by the unified + // transform are indistinguishable at 45 degrees, but fail this case. + REQUIRE_THAT(machine.x(), Catch::Matchers::WithinAbs(4., 1e-9)); + REQUIRE_THAT(machine.y(), Catch::Matchers::WithinAbs(3. / std::sin(Geometry::deg2rad(30.)), 1e-9)); + REQUIRE_THAT(machine.z(), Catch::Matchers::WithinAbs(10. + 3. / std::tan(Geometry::deg2rad(30.)), 1e-9)); +} + // Arrange on a finite bed, not an unbounded InfiniteBed: the latter places items // near INT64_MIN/4 (~2.3e18), which reaches ClipperLib's coordinate limit and throws // "Coordinate outside allowed range" on Windows/arm64. A 500x500 bed keeps coordinates