mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-28 04:18:41 +00:00
fix non 45 degree slicing methods (#15181)
# Description During the UI/UX improvements about a month ago, I got the transforms wrong, and slicing at anything other than a 45 degree angle was affected. Validated on a baby belt pro at 30 & 45 degrees. [How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
This commit is contained in:
@@ -90,6 +90,12 @@ if (SLIC3R_GUI)
|
|||||||
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
|
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
|
||||||
|
|
||||||
find_package(wxInspector REQUIRED)
|
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")
|
list(APPEND wxWidgets_LIBRARIES "wxInspector::wxInspector")
|
||||||
|
|
||||||
message(STATUS "wx libs: ${wxWidgets_LIBRARIES}")
|
message(STATUS "wx libs: ${wxWidgets_LIBRARIES}")
|
||||||
@@ -175,7 +181,7 @@ endif ()
|
|||||||
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
|
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
|
||||||
if (SLIC3R_GUI)
|
if (SLIC3R_GUI)
|
||||||
# target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES})
|
# 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)
|
if (MSVC)
|
||||||
# Generate debug symbols even in release mode.
|
# Generate debug symbols even in release mode.
|
||||||
target_link_options(OrcaSlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
target_link_options(OrcaSlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
||||||
|
|||||||
@@ -29,31 +29,34 @@ bool MachineFrameTransform::init_from_config(const PrintConfig &config)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const double angle_rad = Geometry::deg2rad(angle_deg);
|
const double angle_rad = Geometry::deg2rad(angle_deg);
|
||||||
const double cos_a = std::cos(angle_rad);
|
const double sin_a = std::sin(angle_rad);
|
||||||
if (std::abs(cos_a) <= EPSILON)
|
if (std::abs(sin_a) <= EPSILON)
|
||||||
return false;
|
return false;
|
||||||
const double tan_a = std::sin(angle_rad) / cos_a;
|
const double cot_a = std::cos(angle_rad) / sin_a;
|
||||||
const double inv_cos = 1.0 / cos_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
|
// This stage runs after the conventional belt axis swap. For an X-axis
|
||||||
// axis by 1/cos so a unit slicing move maps to the correct belt travel. The
|
// slicing rotation, remapped Y is model height and remapped Z is travel
|
||||||
// shear sign matches the belt-floor slope derived from the same rotation in
|
// along the belt. Convert those Cartesian coordinates to machine axes with
|
||||||
// BeltTransformPipeline::compute_belt_height_and_floor:
|
// the established belt-printer convention:
|
||||||
// tilt about X: feed axis Y, Z += +tan·Y, scale Y *= 1/cos
|
// machine gantry = model height / sin(a)
|
||||||
// tilt about Y: feed axis X, Z += -tan·X, scale X *= 1/cos
|
// 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 shear = Matrix3d::Identity();
|
||||||
Matrix3d scale = Matrix3d::Identity();
|
Matrix3d scale = Matrix3d::Identity();
|
||||||
if (axis == BeltRotationAxis::X) {
|
if (axis == BeltRotationAxis::X) {
|
||||||
shear(2, 1) = tan_a; // Z from Y
|
shear(2, 1) = cot_a; // Z from Y
|
||||||
scale(1, 1) = inv_cos; // Y
|
scale(1, 1) = inv_sin; // Y
|
||||||
} else { // BeltRotationAxis::Y
|
} else { // BeltRotationAxis::Y
|
||||||
shear(2, 0) = -tan_a; // Z from X
|
shear(2, 0) = -cot_a; // Z from X
|
||||||
scale(0, 0) = inv_cos; // X
|
scale(0, 0) = inv_sin; // X
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply shear first, then scale (the historical default ShearThenScale order:
|
// Apply shear first, then scale (the historical default ShearThenScale order:
|
||||||
// result = scale * shear * p). For the canonical 45°/X belt this maps
|
// 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();
|
Transform3d combined = Transform3d::Identity();
|
||||||
combined.linear() = scale * shear;
|
combined.linear() = scale * shear;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Slic3r {
|
|||||||
//
|
//
|
||||||
// Derived entirely from the single belt tilt (belt_slice_rotation axis +
|
// 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
|
// 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
|
// belt_frame_tilt_decouple flag lets the machine-frame angle differ from the
|
||||||
// pre-slice rotation angle via belt_frame_tilt_angle.
|
// pre-slice rotation angle via belt_frame_tilt_angle.
|
||||||
class MachineFrameTransform
|
class MachineFrameTransform
|
||||||
|
|||||||
@@ -7216,9 +7216,9 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def = this->add("gcode_back_transform", coBool);
|
def = this->add("gcode_back_transform", coBool);
|
||||||
def->label = L("G-code back-transform");
|
def->label = L("G-code back-transform");
|
||||||
def->category = L("Printable space");
|
def->category = L("Printable space");
|
||||||
def->tooltip = L("Reverse the shear/scale transform applied during slicing so G-code "
|
def->tooltip = L("Undo the pre-slice mesh transform before applying the G-code axis remap "
|
||||||
"coordinates are in the machine's physical coordinate space. "
|
"and machine-frame shear/scale. Required for the standard belt-printer "
|
||||||
"Requires at least one shear axis with global mode enabled.");
|
"rotation pipeline.");
|
||||||
def->mode = comExpert;
|
def->mode = comExpert;
|
||||||
def->set_default_value(new ConfigOptionBool(true));
|
def->set_default_value(new ConfigOptionBool(true));
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,49 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include "libslic3r/BeltGCodeWriter.hpp"
|
#include "libslic3r/BeltGCodeWriter.hpp"
|
||||||
|
#include "libslic3r/BeltTransform.hpp"
|
||||||
#include "libslic3r/GCodeReader.hpp"
|
#include "libslic3r/GCodeReader.hpp"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
using namespace Slic3r::Test;
|
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
|
// 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
|
// 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
|
// "Coordinate outside allowed range" on Windows/arm64. A 500x500 bed keeps coordinates
|
||||||
|
|||||||
Reference in New Issue
Block a user