diff --git a/src/libslic3r/BeltGCode.cpp b/src/libslic3r/BeltGCode.cpp index 3ba2f79e55..f26c7f87ff 100644 --- a/src/libslic3r/BeltGCode.cpp +++ b/src/libslic3r/BeltGCode.cpp @@ -1,5 +1,6 @@ #include "BeltGCode.hpp" -#include "BeltGCodeWriter.hpp" +#include "GCodeWriter.hpp" +#include "GCode/BeltKinematics.hpp" #include "BeltTransform.hpp" #include "Print.hpp" @@ -10,11 +11,12 @@ void BeltGCode::init_belt_writer(Print &print, bool is_bbl_printers) if (!print.config().belt_printer.value) return; - auto belt_writer = std::make_unique(); + auto belt_writer = std::make_unique(); belt_writer->set_is_bbl_machine(is_bbl_printers); - // 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()); + // Axis remap and build volume max are set by base GCode after init_belt_writer + // returns; set_kinematics() replays them, so install order does not matter. + install_belt_kinematics(*belt_writer, print.config()); + belt_writer->set_force_normal_lift(true); m_writer = std::move(belt_writer); } diff --git a/src/libslic3r/BeltGCode.hpp b/src/libslic3r/BeltGCode.hpp index 2a3718b49f..f52c62c28f 100644 --- a/src/libslic3r/BeltGCode.hpp +++ b/src/libslic3r/BeltGCode.hpp @@ -7,7 +7,7 @@ namespace Slic3r { // Belt-printer-specific GCode export. // // Inherits from GCode and overrides virtual hooks to: -// - Create a BeltGCodeWriter instead of a plain GCodeWriter +// - Install a BeltKinematics on the GCodeWriter // - Write belt configuration to the G-code header // - Adjust the origin for global pre-slice transforms when switching instances // - Disable arc fitting (G2/G3 not supported on belt printers) diff --git a/src/libslic3r/BeltGCodeWriter.cpp b/src/libslic3r/BeltGCodeWriter.cpp deleted file mode 100644 index 4824d76f69..0000000000 --- a/src/libslic3r/BeltGCodeWriter.cpp +++ /dev/null @@ -1,277 +0,0 @@ -#include "BeltGCodeWriter.hpp" -#include "FirstLayerPlane.hpp" -#include "Geometry.hpp" -#include - -namespace Slic3r { - -namespace { - -// Decide whether a particular destination point gets first-layer treatment. -// When the plane evaluator is active, distance from the plane wins; otherwise -// fall back to the layer-coarse m_is_first_layer flag set by the caller. -inline bool belt_point_on_first_layer( - const FirstLayerPlane *plane, - double first_layer_thickness_mm, - bool layer_first_flag, - const Vec3d &point_slicing_mm) -{ - if (plane && plane->is_active()) - return plane->is_first_layer(point_slicing_mm, first_layer_thickness_mm); - return layer_first_flag; -} - -} // namespace - -// ---- Belt configuration --------------------------------------------------- - -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); -} - -Vec3d BeltGCodeWriter::to_machine_coords(const Vec3d &pos) const -{ - // Step 1+2: To Cartesian (back_transform + axis_remap). - // In world-coordinates mode (PA line / PA pattern calibration) the input - // already describes a point relative to the belt surface, so the - // slicer->world back-transform is skipped and only the machine kinematics - // (axis remap + frame shear/scale) are applied. - Vec3d after_back = m_world_coordinates ? pos : m_belt_back_transform.apply(pos); - Vec3d result = apply_axis_remap(after_back); - Vec3d after_remap = result; - // Step 3: Machine-frame transform (belt frame tilt) applied LAST so it acts - // as a global linear transform on the placed coords. - Vec3d final = m_machine_frame_transform.apply(result); - - // [BELT-DEBUG] One-shot log per layer transition (i.e. when the input Z - // crosses an integer mm boundary) to keep the log volume manageable while - // still capturing one sample per ~5 layers. Shows the full pipeline so - // Case A vs Case B can be compared step-by-step. - static thread_local int s_last_logged_z = std::numeric_limits::min(); - int z_bucket = static_cast(std::floor(pos.z() * 5.0)); // every 0.2mm - if (z_bucket != s_last_logged_z) { - s_last_logged_z = z_bucket; - BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] to_machine_coords" - << " slicer_in=(" << pos.x() << "," << pos.y() << "," << pos.z() << ")" - << " after_back=(" << after_back.x() << "," << after_back.y() << "," << after_back.z() << ")" - << " after_remap=(" << after_remap.x() << "," << after_remap.y() << "," << after_remap.z() << ")" - << " final=(" << final.x() << "," << final.y() << "," << final.z() << ")" - << " mft_active=" << m_machine_frame_transform.is_active() - << " back_active=" << m_belt_back_transform.is_active(); - } - return final; -} - -// ---- Overridden movement methods ------------------------------------------ - -std::string BeltGCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment) -{ - m_pos(0) = point(0); - m_pos(1) = point(1); - - this->set_current_position_clear(true); - Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset }; - - // Belt printer: transform to machine coordinates (XY travel also needs Z due to YZ rotation) - Vec3d machine = to_machine_coords(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z())); - - GCodeG1Formatter w; - w.emit_xyz(machine); - const bool first_layer_for_point = belt_point_on_first_layer( - m_first_layer_plane, m_first_layer_thickness_mm, m_is_first_layer, - Vec3d(point.x(), point.y(), m_pos.z())); - auto speed = first_layer_for_point - ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) - : this->config.travel_speed.get_at(m_cached_extruder_idx); - w.emit_f(speed * 60.0); - w.emit_comment(GCodeWriter::full_gcode_comment, comment); - return w.string(); -} - -std::string BeltGCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase) -{ - // Belt printer: force NormalLift since SpiralLift and SlopeLift compute - // slope angles that don't account for the YZ coordinate rotation. - return GCodeWriter::lazy_lift(LiftType::NormalLift, spiral_vase); -} - -std::string BeltGCodeWriter::eager_lift(const LiftType type) -{ - // Belt printer: force NormalLift (SpiralLift/SlopeLift don't account for YZ rotation). - return GCodeWriter::eager_lift(LiftType::NormalLift); -} - -std::string BeltGCodeWriter::_travel_to_z(double z, const std::string &comment) -{ - m_pos(2) = z; - - double speed = this->config.travel_speed_z.get_at(m_cached_extruder_idx); - if (speed == 0.) { - const bool first_layer_for_point = belt_point_on_first_layer( - m_first_layer_plane, m_first_layer_thickness_mm, m_is_first_layer, - Vec3d(m_pos.x(), m_pos.y(), z)); - speed = first_layer_for_point ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) - : this->config.travel_speed.get_at(m_cached_extruder_idx); - } - - // Belt printer: a Z-only move in slicing frame needs to emit both Y and Z in machine coords. - Vec3d machine = to_machine_coords(Vec3d(m_pos.x() - m_x_offset, m_pos.y() - m_y_offset, z)); - - GCodeG1Formatter w; - w.emit_xyz(machine); - w.emit_f(speed * 60.0); - w.emit_comment(GCodeWriter::full_gcode_comment, comment); - return w.string(); -} - -std::string BeltGCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment, bool force_no_extrusion) -{ - m_pos(0) = point(0); - m_pos(1) = point(1); - if (std::abs(dE) <= std::numeric_limits::epsilon()) - force_no_extrusion = true; - - if (!force_no_extrusion) - filament()->extrude(dE); - - Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset }; - - // Belt printer: transform and emit XYZ (Y and Z are coupled) - Vec3d machine = to_machine_coords(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z())); - - GCodeG1Formatter w; - w.emit_xyz(machine); - if (!force_no_extrusion) - w.emit_e(filament()->E()); - w.emit_comment(GCodeWriter::full_gcode_comment, comment); - return w.string(); -} - -std::string BeltGCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment, bool force_no_extrusion) -{ - m_pos = point; - m_lifted = 0; - if (!force_no_extrusion) - filament()->extrude(dE); - - Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) }; - point_on_plate = to_machine_coords(point_on_plate); - - GCodeG1Formatter w; - w.emit_xyz(point_on_plate); - if (!force_no_extrusion) - w.emit_e(filament()->E()); - w.emit_comment(GCodeWriter::full_gcode_comment, comment); - return w.string(); -} - -std::string BeltGCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment, bool force_z) -{ - // Belt-specific override of travel_to_xyz. - // Key differences from base: - // 1. All coordinates go through to_machine_coords() - // 2. Always emit full XYZ (can't split XY and Z due to coupling) - // 3. Lift type forced to NormalLift (handled by lazy_lift/eager_lift overrides) - - Vec3d dest_point = point; - const bool first_layer_for_point = belt_point_on_first_layer( - m_first_layer_plane, m_first_layer_thickness_mm, m_is_first_layer, point); - auto travel_speed = - first_layer_for_point ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) - : this->config.travel_speed.get_at(m_cached_extruder_idx); - - // Handle pending z_hop - if (std::abs(m_to_lift) > EPSILON) { - assert(std::abs(m_lifted) < EPSILON); - if ((!this->is_current_position_clear() || m_pos != dest_point) && - m_to_lift + m_pos(2) > point(2)) { - m_lifted = m_to_lift + m_pos(2) - point(2); - dest_point(2) = m_to_lift + m_pos(2); - } - m_to_lift = 0.; - - std::string slop_move; - Vec3d source = { m_pos(0) - m_x_offset, m_pos(1) - m_y_offset, m_pos(2) }; - Vec3d target = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) }; - Vec3d delta = target - source; - Vec2d delta_no_z = { delta(0), delta(1) }; - - if (delta(2) > 0 && delta_no_z.norm() != 0.0f) { - // Belt: SpiralLift and SlopeLift are disabled (lazy_lift forces NormalLift), - // but handle NormalLift and fallthrough. - if (m_to_lift_type == LiftType::SlopeLift && - this->is_current_position_clear() && - atan2(delta(2), delta_no_z.norm()) < this->filament()->travel_slope()) { - Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->filament()->travel_slope()); - Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source; - slope_top_point = to_machine_coords(slope_top_point); - GCodeG1Formatter w0; - w0.emit_xyz(slope_top_point); - w0.emit_f(travel_speed * 60.0); - w0.emit_comment(GCodeWriter::full_gcode_comment, comment); - slop_move = w0.string(); - } - else if (m_to_lift_type == LiftType::NormalLift && this->is_current_position_clear()) { - // Only lift-in-place when the current position is known. On a normal - // printer _travel_to_z emits a Z-only move, but in belt mode Z is coupled - // to Y/X, so _travel_to_z re-emits the current m_pos through the belt - // shear. At print start (and after custom gcode) m_pos.xy is still the - // uninitialised origin (0,0), which shears into a bogus machine point - // (e.g. X=bed_max, Y=layer_z) far up the gantry. Skipping the separate - // lift here is safe: there is nothing to lift over yet, and the - // xy_z_move below travels straight to the destination with full XYZ, - // establishing the correct position. This mirrors the SlopeLift branch - // above, which already guards on is_current_position_clear(). - slop_move = _travel_to_z(target.z(), "normal lift Z"); - } - } - - std::string xy_z_move; - { - Vec3d emit_target = to_machine_coords(target); - GCodeG1Formatter w0; - // Belt mode: always emit full XYZ since Y and Z are coupled - w0.emit_xyz(emit_target); - w0.emit_f(travel_speed * 60.0); - w0.emit_comment(GCodeWriter::full_gcode_comment, comment); - xy_z_move = w0.string(); - } - m_pos = dest_point; - this->set_current_position_clear(true); - return slop_move + xy_z_move; - } - else if (!force_z && !this->will_move_z(point(2))) { - double nominal_z = m_pos(2) - m_lifted; - m_lifted -= (point(2) - nominal_z); - if (std::abs(m_lifted) < EPSILON) - m_lifted = 0.; - this->set_current_position_clear(true); - return this->travel_to_xy(to_2d(point)); - } - else { - m_lifted = 0; - } - - Vec3d point_on_plate = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) }; - point_on_plate = to_machine_coords(point_on_plate); - - // Belt mode: always emit full XYZ - GCodeG1Formatter w; - w.emit_xyz(point_on_plate); - // Use the first-layer-aware travel_speed computed at the top of this function, - // not the raw config travel_speed, so initial-layer travels are correctly slowed. - w.emit_f(travel_speed * 60.0); - w.emit_comment(GCodeWriter::full_gcode_comment, comment); - - m_pos = dest_point; - this->set_current_position_clear(true); - return w.string(); -} - -} // namespace Slic3r diff --git a/src/libslic3r/BeltGCodeWriter.hpp b/src/libslic3r/BeltGCodeWriter.hpp deleted file mode 100644 index ff7428d062..0000000000 --- a/src/libslic3r/BeltGCodeWriter.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include "GCodeWriter.hpp" -#include "GCode/BeltBackTransform.hpp" -#include "GCode/MachineFrameTransform.hpp" - -namespace Slic3r { - -class FirstLayerPlane; - -// Belt-printer-specific GCode writer. -// -// Inherits from GCodeWriter and overrides movement methods to apply -// coordinate transformation (back-transform, axis remap, machine-frame -// transform) and emit coupled XYZ moves (Y and Z are coupled due to belt tilt). -class BeltGCodeWriter : public GCodeWriter -{ -public: - BeltGCodeWriter() : GCodeWriter() {} - - // Belt configuration (axis remap is inherited from GCodeWriter) - void set_belt_back_transform(const PrintConfig &config); - void set_machine_frame_transform(const PrintConfig &config); - Vec3d to_machine_coords(const Vec3d &pos) const; - - // World-coordinates mode: incoming coordinates are treated as points - // relative to the physical belt surface (X across, Y along the belt, - // Z height above it) instead of slicing-frame coordinates — the - // slicer->world back-transform is skipped. Used by the PA line / PA - // pattern calibration generators, whose logical bed coordinates describe - // first-layer drawings on the build surface. - void set_world_coordinates(bool enable) { m_world_coordinates = enable; } - - // First-layer plane: when set to a non-null active evaluator, travel - // speed selection consults the plane per-move and uses - // initial_layer_travel_speed for points within first_layer_height_mm - // of the plane (regardless of slicing layer index). - void set_first_layer_plane(const FirstLayerPlane *plane, - double first_layer_height_mm) { - m_first_layer_plane = plane; - m_first_layer_thickness_mm = first_layer_height_mm; - } - - // Overridden movement methods - std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string()) override; - std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string(), bool force_z = false) override; - std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false) override; - std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false) override; - std::string lazy_lift(LiftType lift_type = LiftType::NormalLift, bool spiral_vase = false) override; - std::string eager_lift(const LiftType type) override; - -protected: - std::string _travel_to_z(double z, const std::string &comment) override; - -private: - BeltBackTransform m_belt_back_transform; - MachineFrameTransform m_machine_frame_transform; - bool m_world_coordinates = false; - // Borrowed pointer; lifetime owned by GCode. null = inactive. - const FirstLayerPlane *m_first_layer_plane = nullptr; - double m_first_layer_thickness_mm = 0.; -}; - -} // namespace Slic3r diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index be34f6ba9e..1787c1adef 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -84,8 +84,6 @@ set(lisbslic3r_sources BeltBrim.hpp BeltGCode.cpp BeltGCode.hpp - BeltGCodeWriter.cpp - BeltGCodeWriter.hpp BeltPurge.cpp BeltSliceStrategy.cpp BeltSliceStrategy.hpp @@ -243,6 +241,10 @@ set(lisbslic3r_sources GCode/BeltBackTransform.hpp GCode/MachineFrameTransform.cpp GCode/MachineFrameTransform.hpp + GCode/BeltKinematics.cpp + GCode/BeltKinematics.hpp + GCode/MachineKinematics.cpp + GCode/MachineKinematics.hpp GCode/ConflictChecker.cpp GCode/ConflictChecker.hpp GCode/CoolingBuffer.cpp diff --git a/src/libslic3r/FirstLayerPlane.cpp b/src/libslic3r/FirstLayerPlane.cpp index 6a4feba886..f668ea774e 100644 --- a/src/libslic3r/FirstLayerPlane.cpp +++ b/src/libslic3r/FirstLayerPlane.cpp @@ -56,7 +56,7 @@ MachineZAffine compute_machine_z_affine(const PrintConfig &config) out.constant = trans; if (config.gcode_back_transform.value && config.belt_printer.value) { - // BeltGCodeWriter applies F^-1 before R when back-transform is on. + // 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 diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f95f59dceb..32976fad76 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2986,8 +2986,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // 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()); - if (auto *belt_writer = dynamic_cast(m_writer.get())) { - belt_writer->set_first_layer_plane( + // Belt writers only: the plane also switches travel-speed selection to be + // per-point (see GCodeWriter::uses_pointwise_travel_speed()), which must not + // change for non-belt printers. + if (print.config().belt_printer.value) { + m_writer->set_first_layer_plane( m_first_layer_plane.get(), print.config().initial_layer_print_height.value); } @@ -3888,12 +3891,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // ORCA-Belt: the PA line test draws directly on the build surface in // logical bed coordinates — on a belt printer that surface is the // belt plane, not the slicing plane. - BeltGCodeWriter* belt_writer = dynamic_cast(m_writer.get()); - if (belt_writer != nullptr) - belt_writer->set_world_coordinates(true); + const bool belt_world_coords = print.config().belt_printer.value; + if (belt_world_coords) + install_belt_kinematics(*m_writer, print.config(), /*world_coordinates=*/true); gcode += pa_test.generate_test(params.start, params.step, std::llround(std::ceil((params.end - params.start) / params.step)) + 1); - if (belt_writer != nullptr) - belt_writer->set_world_coordinates(false); + if (belt_world_coords) + install_belt_kinematics(*m_writer, print.config(), /*world_coordinates=*/false); file.write(gcode); } else { @@ -8400,7 +8403,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // mesh transform is now rotation ∘ pre-remap, both orthogonal, so |det(T)| // is always 1 and this is currently a no-op; it is retained as a guard in // case a non-orthogonal mesh transform is ever reintroduced. (Machine-frame - // shear/scale acts on the g-code in BeltGCodeWriter, not here.) + // shear/scale acts on the g-code in BeltKinematics, not here.) if (m_config.belt_printer.value) { double det = std::abs(BeltTransformPipeline::build_forward_transform(m_config).linear().determinant()); if (det > EPSILON) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 17a7ce4608..56496f1b4e 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -4,7 +4,7 @@ #include "libslic3r.h" #include "ExPolygon.hpp" #include "GCodeWriter.hpp" -#include "BeltGCodeWriter.hpp" +#include "GCode/BeltKinematics.hpp" #include "FirstLayerPlane.hpp" #include "Layer.hpp" #include "Point.hpp" diff --git a/src/libslic3r/GCode/BeltBackTransform.hpp b/src/libslic3r/GCode/BeltBackTransform.hpp index b98770c688..878ae12b5a 100644 --- a/src/libslic3r/GCode/BeltBackTransform.hpp +++ b/src/libslic3r/GCode/BeltBackTransform.hpp @@ -13,7 +13,7 @@ namespace Slic3r { // machine's real coordinate space. // // Initialized once from PrintConfig, then applied per-point in -// GCodeWriter::to_machine_coords() before axis remapping. +// 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 diff --git a/src/libslic3r/GCode/BeltKinematics.cpp b/src/libslic3r/GCode/BeltKinematics.cpp new file mode 100644 index 0000000000..155e18d32d --- /dev/null +++ b/src/libslic3r/GCode/BeltKinematics.cpp @@ -0,0 +1,40 @@ +#include "BeltKinematics.hpp" +#include "../BeltTransform.hpp" +#include "../PrintConfig.hpp" +#include "../GCodeWriter.hpp" + +namespace Slic3r { + +BeltKinematics::BeltKinematics(const PrintConfig &config, bool world_coordinates) + : m_world_coordinates(world_coordinates) +{ + m_back_active = m_back_transform.init_from_config(config); + m_machine_frame.init_from_config(config); + if (m_back_active) + // BeltBackTransform stores the inverse of this; keep the forward so + // to_logical() can reverse the whole chain. + m_back_forward = BeltTransformPipeline::build_forward_transform(config); +} + +Vec3d BeltKinematics::to_machine(const Vec3d &p) const +{ + const Vec3d after_back = m_world_coordinates ? p : m_back_transform.apply(p); + const Vec3d after_remap = this->apply_axis_remap(after_back); + return m_machine_frame.apply(after_remap); +} + +Vec3d BeltKinematics::to_logical(const Vec3d &machine) const +{ + const Vec3d before_frame = m_machine_frame.apply_inverse(machine); + const Vec3d before_remap = this->apply_axis_remap_inverse(before_frame); + if (m_world_coordinates || ! m_back_active) + return before_remap; + return m_back_forward * before_remap; +} + +void install_belt_kinematics(GCodeWriter &writer, const PrintConfig &config, bool world_coordinates) +{ + writer.set_kinematics(std::make_unique(config, world_coordinates)); +} + +} // namespace Slic3r diff --git a/src/libslic3r/GCode/BeltKinematics.hpp b/src/libslic3r/GCode/BeltKinematics.hpp new file mode 100644 index 0000000000..a57f18dcdd --- /dev/null +++ b/src/libslic3r/GCode/BeltKinematics.hpp @@ -0,0 +1,66 @@ +#ifndef slic3r_BeltKinematics_hpp_ +#define slic3r_BeltKinematics_hpp_ + +#include "MachineKinematics.hpp" +#include "BeltBackTransform.hpp" +#include "MachineFrameTransform.hpp" + +namespace Slic3r { + +class PrintConfig; +class GCodeWriter; + +// Belt-printer machine frame. +// +// Forward order, as applied per emitted point: +// machine = MachineFrameTransform( axis_remap( BeltBackTransform( logical ) ) ) +// +// i.e. the slicer->world back-transform runs FIRST and the machine-frame +// shear/scale LAST, so the latter acts as a global linear transform on the +// already-placed coordinates. +// +// world_coordinates mode (the PA line / PA pattern calibration generators) +// treats the incoming point as already relative to the belt surface -- X across, +// Y along the belt, Z above it -- and therefore skips the back-transform while +// keeping the remap and the machine frame. It is a different coordinate map, not +// a writer mode, which is why it is fixed at construction. +class BeltKinematics : public CartesianKinematics +{ +public: + explicit BeltKinematics(const PrintConfig &config, bool world_coordinates = false); + + Vec3d to_machine(const Vec3d &p) const override; + Vec3d to_logical(const Vec3d &machine) const override; + // Machine -> build-volume frame. Only the machine-frame shear/scale is undone, + // matching what GCodeProcessor's bounds validation wants. This is deliberately + // NOT to_logical(). + Vec3d to_build_volume(const Vec3d &machine) const override + { return m_machine_frame.apply_inverse(machine); } + + // A belt writer has always emitted full XYZ on every move, whether or not any + // individual stage reports itself active. Making this conditional would change + // emitted G-code for an identity-transform belt configuration. + bool must_emit_all_axes() const override { return true; } + bool suppress_lift_at_unknown_position() const override { return true; } + + bool world_coordinates() const { return m_world_coordinates; } + +private: + BeltBackTransform m_back_transform; + MachineFrameTransform m_machine_frame; + // Forward of what m_back_transform inverts, kept so to_logical() can undo it. + Transform3d m_back_forward { Transform3d::Identity() }; + bool m_back_active { false }; + bool m_world_coordinates { false }; +}; + +// Install a belt machine frame on any GCodeWriter. Any axis remap and build +// volume already configured on the writer are carried over, so this may be +// called before or after those setters. Re-calling it with a different +// world_coordinates value swaps the map (used around the PA line generator). +void install_belt_kinematics(GCodeWriter &writer, const PrintConfig &config, + bool world_coordinates = false); + +} // namespace Slic3r + +#endif // slic3r_BeltKinematics_hpp_ diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index ca41547ff3..2112cac8fd 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2774,7 +2774,7 @@ bool GCodeProcessor::check_multi_extruder_gcode_valid(const int }; // Belt-printer post-gcode shear/scale/post_remap is applied as the final - // step of BeltGCodeWriter::to_machine_coords, so MoveVertex.position is + // step of BeltKinematics::to_machine, so MoveVertex.position is // in the printer's machine frame. Undo it here so XY area and Z height // checks operate in the build-volume frame that printable_area / // printable_height are defined in. For non-belt printers @@ -7111,7 +7111,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type, // During the start G-code "prepare" stage the toolhead Z is not yet a real // print height on a normal printer, so it is pinned to the first-layer height // to keep the preview tidy. Belt printers are the exception: there the Z is - // written explicitly by BeltGCodeWriter and the designed-view back-transform + // written explicitly by the belt kinematics and the designed-view back-transform // couples machine Z into the rendered model Y (the belt tilt mixes the height // and belt-feed axes). Overriding Z therefore back-transforms the last // prepare-stage move (the unretract before the first extrusion) to model diff --git a/src/libslic3r/GCode/MachineFrameTransform.hpp b/src/libslic3r/GCode/MachineFrameTransform.hpp index 83519bdbe4..cdae28ca1a 100644 --- a/src/libslic3r/GCode/MachineFrameTransform.hpp +++ b/src/libslic3r/GCode/MachineFrameTransform.hpp @@ -9,7 +9,7 @@ namespace Slic3r { // Post-stage machine-frame transform for belt printers. // -// Applied in BeltGCodeWriter::to_machine_coords AFTER the back-transform and +// Applied in BeltKinematics::to_machine AFTER the back-transform and // the gcode_remap_* axis remap. Maps Cartesian (axis-permuted) G-code // coordinates into the printer's physical machine frame. // diff --git a/src/libslic3r/GCode/MachineKinematics.cpp b/src/libslic3r/GCode/MachineKinematics.cpp new file mode 100644 index 0000000000..05f5375aef --- /dev/null +++ b/src/libslic3r/GCode/MachineKinematics.cpp @@ -0,0 +1,47 @@ +#include "MachineKinematics.hpp" + +namespace Slic3r { + +// Moved verbatim from GCodeWriter::apply_axis_remap(). +Vec3d CartesianKinematics::apply_axis_remap(const Vec3d &pos) const +{ + if (!has_axis_remap()) + return pos; + auto remap = [this, &pos](int r) -> double { + int axis = r % 3; + if (r < 3) return pos[axis]; + if (r < 6) return -pos[axis]; + return m_build_vol_max[axis] - pos[axis]; + }; + return { remap(m_remap_x), remap(m_remap_y), remap(m_remap_z) }; +} + +// Inverse of the above. Output axis i is fed by source axis (r_i % 3); walking +// the three outputs therefore fills every source component exactly once, so long +// as the remap is a permutation (which set_axis_remap callers guarantee). +Vec3d CartesianKinematics::apply_axis_remap_inverse(const Vec3d &machine) const +{ + if (!has_axis_remap()) + return machine; + Vec3d out = Vec3d::Zero(); + const int r[3] = { m_remap_x, m_remap_y, m_remap_z }; + for (int i = 0; i < 3; ++i) { + const int axis = r[i] % 3; + if (r[i] < 3) out[axis] = machine[i]; + else if (r[i] < 6) out[axis] = -machine[i]; + else out[axis] = m_build_vol_max[axis] - machine[i]; + } + return out; +} + +Vec3d CartesianKinematics::to_machine(const Vec3d &p) const +{ + return this->apply_axis_remap(p); +} + +Vec3d CartesianKinematics::to_logical(const Vec3d &machine) const +{ + return this->apply_axis_remap_inverse(machine); +} + +} // namespace Slic3r diff --git a/src/libslic3r/GCode/MachineKinematics.hpp b/src/libslic3r/GCode/MachineKinematics.hpp new file mode 100644 index 0000000000..c3e381d851 --- /dev/null +++ b/src/libslic3r/GCode/MachineKinematics.hpp @@ -0,0 +1,95 @@ +#ifndef slic3r_MachineKinematics_hpp_ +#define slic3r_MachineKinematics_hpp_ + +#include "../Point.hpp" + +namespace Slic3r { + +// The frame contract for emitted movement. +// +// GCodeWriter produces points in the *logical placed* frame: plate offsets have +// already been subtracted, but no machine-specific mapping has been applied. +// A MachineKinematics turns that into the coordinates actually written to +// G-code, and answers the two structural questions the writer needs in order to +// decide which axis words it may omit. +// +// This is a seam for writer-generated movement only. Start/end/custom G-code, +// classic wipe-tower output and GCodeWriter::extrude_arc_to_xy() do NOT pass +// through it; see doc in 09-orca-machinekinematics-split-plan.md section 6. +class MachineKinematics +{ +public: + virtual ~MachineKinematics() = default; + + // Logical placed point -> emitted machine point. + virtual Vec3d to_machine(const Vec3d &p) const = 0; + + // Inverse of to_machine(), back to the logical placed frame. Intended for + // consumers that must reconstruct model coordinates from emitted G-code + // (the G-code viewer's upright preview). + virtual Vec3d to_logical(const Vec3d &machine) const = 0; + + // Machine point -> build-volume frame, for bounds validation only. This is + // deliberately NOT to_logical(): the build-volume check wants the physical + // frame the printable area is expressed in, not the model frame. Keeping + // them separate stops the two contracts from being confused. + virtual Vec3d to_build_volume(const Vec3d &machine) const = 0; + + // True when a move must emit X, Y and Z because omitting a word would be + // wrong under this mapping. Deliberately not called "couples_axes": a pure + // axis permutation forces full emission without physically coupling axes. + virtual bool must_emit_all_axes() const = 0; + + // True when a separate in-place lift must be suppressed while the current + // position is unknown, because _travel_to_z() re-emits the logical X/Y + // through this mapping and an uninitialised position would map to a bogus + // machine point. + virtual bool suppress_lift_at_unknown_position() const = 0; + + // Configuration. GCodeWriter forwards its setters here so that the state + // lives with the strategy and a strategy installed before the setters run + // still receives it. + virtual void set_axis_remap(int rx, int ry, int rz) = 0; + virtual void set_build_volume_max(const Vec3d &max) = 0; +}; + +// Axis remap only -- the historical GCodeWriter behaviour, moved verbatim. +// +// The remap encodes, per output axis, which source axis feeds it and how: +// r < 3 : source axis r, unchanged +// r < 6 : source axis r-3, negated +// else : source axis r-6, reversed within the build volume +class CartesianKinematics : public MachineKinematics +{ +public: + Vec3d to_machine(const Vec3d &p) const override; + Vec3d to_logical(const Vec3d &machine) const override; + Vec3d to_build_volume(const Vec3d &machine) const override { return machine; } + + bool must_emit_all_axes() const override { return this->has_axis_remap(); } + // The base writer has never suppressed the lift, not even under a remap that + // makes _travel_to_z re-emit X/Y. That is arguably a latent bug, but fixing + // it here would change emitted G-code, so today's behaviour is preserved and + // the divergence from BeltKinematics is deliberate. + bool suppress_lift_at_unknown_position() const override { return false; } + + void set_axis_remap(int rx, int ry, int rz) override + { m_remap_x = rx; m_remap_y = ry; m_remap_z = rz; } + void set_build_volume_max(const Vec3d &max) override { m_build_vol_max = max; } + + bool has_axis_remap() const + { return m_remap_x != 0 || m_remap_y != 1 || m_remap_z != 2; } + +protected: + Vec3d apply_axis_remap(const Vec3d &pos) const; + Vec3d apply_axis_remap_inverse(const Vec3d &pos) const; + + int m_remap_x { 0 }; + int m_remap_y { 1 }; + int m_remap_z { 2 }; + Vec3d m_build_vol_max { Vec3d::Zero() }; +}; + +} // namespace Slic3r + +#endif // slic3r_MachineKinematics_hpp_ diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 67dd6cf138..be8f6607de 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -1,4 +1,5 @@ #include "GCodeWriter.hpp" +#include "FirstLayerPlane.hpp" #include "CustomGCode.hpp" #include "Geometry.hpp" #include "I18N.hpp" @@ -24,34 +25,46 @@ namespace Slic3r { bool GCodeWriter::full_gcode_comment = true; +bool GCodeWriter::point_on_first_layer(const Vec3d &point_logical) const +{ + if (m_first_layer_plane && m_first_layer_plane->is_active()) + return m_first_layer_plane->is_first_layer(point_logical, m_first_layer_thickness_mm); + return m_is_first_layer; +} + void GCodeWriter::set_axis_remap(int rx, int ry, int rz) { m_remap_x = rx; m_remap_y = ry; m_remap_z = rz; + m_kinematics->set_axis_remap(rx, ry, rz); } void GCodeWriter::set_build_volume_max(const Vec3d &max) { m_build_vol_max = max; + m_kinematics->set_build_volume_max(max); } +void GCodeWriter::set_kinematics(std::unique_ptr kinematics) +{ + assert(kinematics); + m_kinematics = std::move(kinematics); + // Replay whatever was configured on the previous strategy so callers may + // install the kinematics before or after set_axis_remap/set_build_volume_max. + m_kinematics->set_axis_remap(m_remap_x, m_remap_y, m_remap_z); + m_kinematics->set_build_volume_max(m_build_vol_max); +} + +// Kept as the writer-facing name for "this move must emit every axis word". bool GCodeWriter::has_axis_remap() const { - return m_remap_x != 0 || m_remap_y != 1 || m_remap_z != 2; + return m_kinematics->must_emit_all_axes(); } Vec3d GCodeWriter::apply_axis_remap(const Vec3d &pos) const { - if (!has_axis_remap()) - return pos; - auto remap = [this, &pos](int r) -> double { - int axis = r % 3; - if (r < 3) return pos[axis]; - if (r < 6) return -pos[axis]; - return m_build_vol_max[axis] - pos[axis]; - }; - return { remap(m_remap_x), remap(m_remap_y), remap(m_remap_z) }; + return m_kinematics->to_machine(pos); } bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor) @@ -795,7 +808,7 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com } else { w.emit_xy(point_on_plate); } - auto speed = m_is_first_layer + auto speed = this->point_on_first_layer(Vec3d(point.x(), point.y(), m_pos.z())) ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) : this->config.travel_speed.get_at(m_cached_extruder_idx); w.emit_f(speed * 60.0); //BBS @@ -808,6 +821,8 @@ it will not perform subsequent lifts, even if Z was raised manually (i.e. with travel_to_z()) and thus _lifted was reduced. */ std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase) { + if (m_force_normal_lift) + lift_type = LiftType::NormalLift; // check whether the above/below conditions are met double target_lift = 0; { @@ -836,7 +851,7 @@ std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase) // BBS: immediately execute an undelayed lift move with a spiral lift pattern // designed specifically for subsequent gcode injection (e.g. timelapse) std::string GCodeWriter::eager_lift(const LiftType type) { - const LiftType effective_type = type; + const LiftType effective_type = m_force_normal_lift ? LiftType::NormalLift : type; std::string lift_move; double target_lift = 0; { @@ -888,7 +903,12 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co // BBS Vec3d dest_point = point; auto travel_speed = - m_is_first_layer ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) : this->config.travel_speed.get_at(m_cached_extruder_idx); + this->point_on_first_layer(point) ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) : this->config.travel_speed.get_at(m_cached_extruder_idx); + // See uses_pointwise_travel_speed(): the historical path deliberately emits the + // raw configured speed in the final branch below, ignoring travel_speed. + const double final_travel_speed = this->uses_pointwise_travel_speed() + ? travel_speed + : this->config.travel_speed.get_at(m_cached_extruder_idx); //BBS: a z_hop need to be handle when travel if (std::abs(m_to_lift) > EPSILON) { assert(std::abs(m_lifted) < EPSILON); @@ -946,7 +966,16 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co w0.emit_comment(GCodeWriter::full_gcode_comment, comment); slop_move = w0.string(); } - else if (m_to_lift_type == LiftType::NormalLift) { + else if (m_to_lift_type == LiftType::NormalLift && + (! m_kinematics->suppress_lift_at_unknown_position() || + this->is_current_position_clear())) { + // Only lift in place when the current position is known, for a mapping + // that makes _travel_to_z re-emit logical X/Y: at print start (and after + // custom gcode) m_pos.xy is still the uninitialised origin, which would + // map to a bogus machine point. The xy_z_move below then travels straight + // to the destination with full XYZ and establishes the correct position. + // Mappings that do not need this (the historical Cartesian behaviour) + // report false and keep lifting unconditionally. slop_move = _travel_to_z(target.z(), "normal lift Z"); } } @@ -1002,20 +1031,20 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co if (has_axis_remap()) { // Remap may couple XY with Z; emit full XYZ in machine coordinates. w.emit_xyz(apply_axis_remap(point_on_plate)); - w.emit_f(this->config.travel_speed.get_at(m_cached_extruder_idx) * 60.0); + w.emit_f(final_travel_speed * 60.0); w.emit_comment(GCodeWriter::full_gcode_comment, comment); out_string = w.string(); } else if (!this->is_current_position_clear()) { //force to move xy first then z after filament change w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y())); - w.emit_f(this->config.travel_speed.get_at(m_cached_extruder_idx) * 60.0); + w.emit_f(final_travel_speed * 60.0); w.emit_comment(GCodeWriter::full_gcode_comment, comment); out_string = w.string() + _travel_to_z(point_on_plate.z(), comment); } else { GCodeG1Formatter w; w.emit_xyz(point_on_plate); - w.emit_f(this->config.travel_speed.get_at(m_cached_extruder_idx) * 60.0); + w.emit_f(final_travel_speed * 60.0); w.emit_comment(GCodeWriter::full_gcode_comment, comment); out_string = w.string(); } @@ -1050,8 +1079,9 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) double speed = this->config.travel_speed_z.get_at(m_cached_extruder_idx); if (speed == 0.) { - speed = m_is_first_layer ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) - : this->config.travel_speed.get_at(m_cached_extruder_idx); + speed = this->point_on_first_layer(Vec3d(m_pos.x(), m_pos.y(), z)) + ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) + : this->config.travel_speed.get_at(m_cached_extruder_idx); } GCodeG1Formatter w; diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index da2ce35cd6..f5e037d685 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -9,8 +9,12 @@ #include "Polygon.hpp" #include "PrintConfig.hpp" #include "GCode/CoolingBuffer.hpp" +#include "GCode/MachineKinematics.hpp" +#include namespace Slic3r { +class FirstLayerPlane; + class GCodeWriter { public: virtual ~GCodeWriter() = default; @@ -28,7 +32,8 @@ public: m_lifted(0), m_to_lift(0), m_to_lift_type(LiftType::NormalLift), - m_current_speed(3600), m_is_first_layer(true) + m_current_speed(3600), m_is_first_layer(true), + m_kinematics(std::make_unique()) {} Extruder* filament(size_t extruder_id) { assert(extruder_id < m_curr_filament_extruder.size()); return m_curr_filament_extruder[extruder_id]; } const Extruder* filament(size_t extruder_id) const { assert(extruder_id < m_curr_filament_extruder.size()); return m_curr_filament_extruder[extruder_id]; } @@ -142,10 +147,28 @@ public: void set_build_volume_max(const Vec3d &max); bool has_axis_remap() const; + // Install the machine frame mapping. Any axis remap / build volume already + // configured is carried over, so install order does not matter. + void set_kinematics(std::unique_ptr kinematics); + const MachineKinematics& kinematics() const { return *m_kinematics; } + + // First-layer plane evaluator. When set to an active plane, travel speed + // selection consults the plane per destination point instead of the + // layer-coarse m_is_first_layer flag. Borrowed pointer; lifetime is owned + // by GCode, which constructs the plane after the writer exists -- so this is + // deliberately a setter and not a constructor argument. + void set_first_layer_plane(const FirstLayerPlane *plane, double first_layer_height_mm) + { m_first_layer_plane = plane; m_first_layer_thickness_mm = first_layer_height_mm; } + + // Force every lift to a plain vertical lift. Spiral and slope lifts compute + // their slope in the logical frame and do not account for a machine mapping + // that couples axes. + void set_force_normal_lift(bool force) { m_force_normal_lift = force; } + // Returns whether this flavor supports separate print and travel acceleration. static bool supports_separate_travel_acceleration(GCodeFlavor flavor); protected: - // Position/lift/offset state — accessible to subclasses (e.g. BeltGCodeWriter) + // Position/lift/offset state. Vec3d m_pos = Vec3d::Zero(); double m_x_offset{ 0 }; double m_y_offset{ 0 }; @@ -158,17 +181,44 @@ protected: virtual std::string _travel_to_z(double z, const std::string &comment); - // Axis remap state — accessible to subclasses. + // Whether a destination gets first-layer treatment. With an active plane + // evaluator, distance from the plane decides; otherwise the layer-coarse + // m_is_first_layer flag does. + bool point_on_first_layer(const Vec3d &point_logical) const; + + // True when travel speed is selected per destination point rather than per + // layer. Set for writers that install a first-layer plane. The historical + // path emits the raw configured travel speed in the final branch of + // travel_to_xyz(), ignoring the first-layer selection computed at the top of + // that function; a plane-driven writer uses the first-layer-aware value + // throughout. Both are preserved exactly -- unifying them would change + // emitted feedrates and belongs in its own commit. + bool uses_pointwise_travel_speed() const { return m_first_layer_plane != nullptr; } + + // Borrowed; null = inactive. + const FirstLayerPlane *m_first_layer_plane = nullptr; + double m_first_layer_thickness_mm = 0.; + bool m_force_normal_lift = false; + + // The machine frame mapping. Owns the axis-remap state that used to live + // here as m_remap_* / m_build_vol_max; the setters above forward to it. + // Never null: a CartesianKinematics at the identity remap reproduces the + // historical behaviour exactly. + std::unique_ptr m_kinematics; + + // Last configured remap / build volume, replayed onto a newly installed + // kinematics so set_kinematics() and the setters are order-independent. int m_remap_x = 0; // RemapAxis: 0=+X, 1=+Y, 2=+Z, 3=-X, etc. int m_remap_y = 1; int m_remap_z = 2; Vec3d m_build_vol_max = Vec3d::Zero(); - // Apply axis remap to a point. Returns pos unchanged if remap is identity. + // Apply the machine frame mapping to a point. Returns pos unchanged when the + // mapping is the identity. Vec3d apply_axis_remap(const Vec3d &pos) const; // Motion uses the global/base process variant until a filament becomes active. - // Protected so BeltGCodeWriter indexes the per-extruder speed options (travel_speed, + // Protected so subclasses index the per-extruder speed options (travel_speed, // travel_speed_z, initial_layer_travel_speed) exactly as the base writer does. size_t m_cached_extruder_idx; diff --git a/src/libslic3r/calib.cpp b/src/libslic3r/calib.cpp index 620babba4d..cfb8112826 100644 --- a/src/libslic3r/calib.cpp +++ b/src/libslic3r/calib.cpp @@ -1,5 +1,5 @@ #include "calib.hpp" -#include "BeltGCodeWriter.hpp" +#include "GCode/BeltKinematics.hpp" #include "BoundingBox.hpp" #include "Config.hpp" #include "Model.hpp" @@ -855,10 +855,8 @@ void CalibPressureAdvancePattern::_refresh_writer(bool is_bbl_machine, const Mod // needs the machine kinematics (axis remap + frame shear/scale) with the // coordinates interpreted as world points (see set_world_coordinates). if (print_config.belt_printer.value) { - auto belt_writer = std::make_shared(); - belt_writer->set_belt_back_transform(print_config); - belt_writer->set_machine_frame_transform(print_config); - belt_writer->set_world_coordinates(true); + auto belt_writer = std::make_shared(); + install_belt_kinematics(*belt_writer, print_config, /*world_coordinates=*/true); const int rx = int(print_config.gcode_remap_x.value); const int ry = int(print_config.gcode_remap_y.value); const int rz = int(print_config.gcode_remap_z.value); @@ -869,7 +867,9 @@ void CalibPressureAdvancePattern::_refresh_writer(bool is_bbl_machine, const Mod print_config.printable_height.value)); } m_writer = std::move(belt_writer); - } else if (dynamic_cast(m_writer.get()) != nullptr) { + } else if (m_writer && dynamic_cast(&m_writer->kinematics()) != nullptr) { + // Previously configured for a belt printer; drop back to a plain writer, + // exactly as the old dynamic_cast check did. m_writer = std::make_shared(); } diff --git a/src/libslic3r/calib.hpp b/src/libslic3r/calib.hpp index 109360f6ec..2478349bf0 100644 --- a/src/libslic3r/calib.hpp +++ b/src/libslic3r/calib.hpp @@ -377,7 +377,7 @@ private: const Calib_Params &m_params; - // Polymorphic so belt printers get a BeltGCodeWriter in world-coordinates + // Polymorphic so belt printers get belt kinematics in world-coordinates // mode (_refresh_writer); shared_ptr keeps the class copyable — the writer // is rebuilt by refresh_setup() before every use anyway. std::shared_ptr m_writer{std::make_shared()}; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 1b5c2df0b5..ca4867a68b 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1147,7 +1147,7 @@ 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 (BeltGCodeWriter:: +// 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: diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6a8b398871..1929b83de5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -15760,7 +15760,7 @@ void Plater::_calib_apply_belt_mode() void Plater::calib_pa(const Calib_Params& params) { // ORCA-Belt: PA Line / PA Pattern have the belt plumbing in place - // (BeltGCodeWriter::set_world_coordinates draws them on the belt surface) + // (belt kinematics in world-coordinates mode draws them on the belt surface) // but are not validated yet — keep them gated to the PA Tower for now. { double angle_rad = 0.; diff --git a/src/slic3r/GUI/calib_dlg.cpp b/src/slic3r/GUI/calib_dlg.cpp index c0979ec56c..83e36dbd69 100644 --- a/src/slic3r/GUI/calib_dlg.cpp +++ b/src/slic3r/GUI/calib_dlg.cpp @@ -98,7 +98,7 @@ std::vector make_shaper_type_labels() } // ORCA-Belt: PA Line / PA Pattern have belt plumbing in place (drawn on the -// belt surface via BeltGCodeWriter world-coordinates mode) but are not +// belt surface via BeltKinematics world-coordinates mode) but are not // validated yet — belt printers are restricted to the PA Tower for now. bool is_belt_printer_selected() { diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index 22ca387bbb..522d945948 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -19,7 +19,7 @@ #include "libslic3r/GCode/GCodeProcessor.hpp" #include #include -#include "libslic3r/BeltGCodeWriter.hpp" +#include "libslic3r/GCode/BeltKinematics.hpp" #include "libslic3r/BeltTransform.hpp" #include "libslic3r/GCodeReader.hpp" #include "libslic3r/PrintConfig.hpp" @@ -39,9 +39,8 @@ TEST_CASE("Belt machine coordinates retain a non-45-degree slicing angle", "[GCo 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); + GCodeWriter writer; + install_belt_kinematics(writer, config); writer.set_axis_remap(int(config.gcode_remap_x.value), int(config.gcode_remap_y.value), int(config.gcode_remap_z.value)); @@ -52,7 +51,7 @@ TEST_CASE("Belt machine coordinates retain a non-45-degree slicing angle", "[GCo // 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); + const Vec3d machine = writer.kinematics().to_machine(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). @@ -905,7 +904,7 @@ TEST_CASE("Custom G-code motion limits are restored before generated moves", "[G // is_current_position_clear(), mirroring the SlopeLift branch. SCENARIO("Belt: the first travel does not lift through the uninitialised origin", "[GCodeWriter][belt]") { - GIVEN("A fresh BeltGCodeWriter configured for an X-tilt 45 degree belt") { + GIVEN("A fresh belt-kinematics GCodeWriter configured for an X-tilt 45 degree belt") { // Machine-frame + slicer->world back-transform config (X tilt, 45 deg). PrintConfig belt_config; belt_config.belt_printer.value = true; @@ -917,9 +916,8 @@ SCENARIO("Belt: the first travel does not lift through the uninitialised origin" belt_config.belt_frame_tilt_decouple.value = false; belt_config.belt_frame_tilt_angle.value = 45.0; - BeltGCodeWriter writer; - writer.set_machine_frame_transform(belt_config); - writer.set_belt_back_transform(belt_config); + GCodeWriter writer; + install_belt_kinematics(writer, belt_config); std::vector extruder_ids { 0 }; writer.set_extruders(extruder_ids);