diff --git a/resources/profiles/Custom/machine/fdm_belt_common.json b/resources/profiles/Custom/machine/fdm_belt_common.json index 0c3a976c0e..545ad15846 100644 --- a/resources/profiles/Custom/machine/fdm_belt_common.json +++ b/resources/profiles/Custom/machine/fdm_belt_common.json @@ -82,9 +82,9 @@ "z_hop_types": [ "Normal Lift" ], - "post_gcode_remap_x": "rev_x", - "post_gcode_remap_y": "pos_z", - "post_gcode_remap_z": "pos_y", + "gcode_remap_x": "rev_x", + "gcode_remap_y": "pos_z", + "gcode_remap_z": "pos_y", "printer_extruder_id": [ "1" ], @@ -94,7 +94,6 @@ "belt_slice_rotation_global": "1", "gcode_shear_z": "pos_tan", "gcode_scale_y": "inv_cos", - "build_plate_tilt_x": "45", "purge_in_prime_tower": "0", "scan_first_layer": "0", "auxiliary_fan": "0" diff --git a/src/libslic3r/BeltGCode.cpp b/src/libslic3r/BeltGCode.cpp index a89b20bcec..8c8c3d4392 100644 --- a/src/libslic3r/BeltGCode.cpp +++ b/src/libslic3r/BeltGCode.cpp @@ -14,7 +14,6 @@ void BeltGCode::init_belt_writer(Print &print, bool is_bbl_printers) auto belt_writer = std::make_unique(); belt_writer->set_is_bbl_machine(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()); @@ -34,30 +33,12 @@ void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print) if (!print.config().belt_printer.value) return; - file.write_format("; belt_printer_angle = %.1f\n", print.config().belt_printer_angle.value); - // Shear configs const auto &full_cfg = print.full_print_config(); - file.write_format("; belt_shear_x = %s\n", full_cfg.opt_serialize("belt_shear_x").c_str()); - file.write_format("; belt_shear_x_angle = %.1f\n", print.config().belt_shear_x_angle.value); - file.write_format("; belt_shear_x_from = %s\n", full_cfg.opt_serialize("belt_shear_x_from").c_str()); - file.write_format("; belt_shear_y = %s\n", full_cfg.opt_serialize("belt_shear_y").c_str()); - file.write_format("; belt_shear_y_angle = %.1f\n", print.config().belt_shear_y_angle.value); - file.write_format("; belt_shear_y_from = %s\n", full_cfg.opt_serialize("belt_shear_y_from").c_str()); - file.write_format("; belt_shear_z = %s\n", full_cfg.opt_serialize("belt_shear_z").c_str()); - file.write_format("; belt_shear_z_angle = %.1f\n", print.config().belt_shear_z_angle.value); - file.write_format("; belt_shear_z_from = %s\n", full_cfg.opt_serialize("belt_shear_z_from").c_str()); - // Scale configs - file.write_format("; belt_scale_x = %s\n", full_cfg.opt_serialize("belt_scale_x").c_str()); - file.write_format("; belt_scale_x_angle = %.1f\n", print.config().belt_scale_x_angle.value); - file.write_format("; belt_scale_y = %s\n", full_cfg.opt_serialize("belt_scale_y").c_str()); - 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); - // Slicing rotation configs + // Slicing rotation: the belt tilt (axis + angle) and the single source of truth + // for the physical tilt the G-code viewer uses to enable belt view. file.write_format("; belt_slice_rotation = %s\n", full_cfg.opt_serialize("belt_slice_rotation").c_str()); file.write_format("; belt_slice_rotation_angle = %.1f\n", print.config().belt_slice_rotation_angle.value); file.write_format("; belt_slice_rotation_global = %d\n", print.config().belt_slice_rotation_global.value ? 1 : 0); - 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()); @@ -81,9 +62,6 @@ void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print) 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) @@ -94,21 +72,16 @@ void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift) // This replaces the bbox-based axis snap with an exact formula. // // 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) + // belt_preslice_global — full pipeline (rotation * remap) is global + // preslice_remap_global — only the pre-slice remap is global + // belt_slice_rotation_global — slicing rotation treated as global (matches + // the per-instance Z-offset added in PrintObjectSlice.cpp) // The XY origin adjustment uses the FULL forward transform either way, // because the back_transform applied during G-code emission is always the - // 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. + // inverse of the full pipeline. bool use_global = m_config.belt_preslice_global.value || (m_config.preslice_remap_global.value && BeltTransformPipeline::has_preslice_remap(m_config)) - || (m_config.belt_shear_z_global.value - && m_config.belt_shear_z.value != BeltShearMode::None) || (m_config.belt_slice_rotation_global.value && m_config.belt_slice_rotation.value != BeltRotationAxis::None && std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON); @@ -117,10 +90,10 @@ void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift) if (belt_writer) { // 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. + // global_z_offset (via the belt_z_shift term in PrintObjectSlice.cpp). + // 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.); } diff --git a/src/libslic3r/BeltGCodeWriter.cpp b/src/libslic3r/BeltGCodeWriter.cpp index e0e4fb6f46..751a7f19d8 100644 --- a/src/libslic3r/BeltGCodeWriter.cpp +++ b/src/libslic3r/BeltGCodeWriter.cpp @@ -25,11 +25,6 @@ inline bool belt_point_on_first_layer( // ---- Belt configuration --------------------------------------------------- -void BeltGCodeWriter::set_belt_angle(double angle_deg) -{ - m_belt_angle_rad = Geometry::deg2rad(angle_deg); -} - void BeltGCodeWriter::set_belt_back_transform(const PrintConfig &config) { m_belt_back_transform.init_from_config(config); @@ -66,7 +61,7 @@ Vec3d BeltGCodeWriter::to_machine_coords(const Vec3d &pos) const if (m_origin_snap[i]) result[i] -= (m_origin_bbox_min[i] - m_origin_offset[i]); Vec3d after_snap = result; - // Step 4: Machine-frame transform (gcode_shear / gcode_scale / post_gcode_remap) + // Step 4: Machine-frame transform (gcode_shear / gcode_scale) // 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 diff --git a/src/libslic3r/BeltGCodeWriter.hpp b/src/libslic3r/BeltGCodeWriter.hpp index 60fd93f3e0..e9d4af0f3a 100644 --- a/src/libslic3r/BeltGCodeWriter.hpp +++ b/src/libslic3r/BeltGCodeWriter.hpp @@ -19,8 +19,6 @@ public: BeltGCodeWriter() : GCodeWriter() {} // Belt configuration (axis remap is inherited from GCodeWriter) - 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); @@ -52,7 +50,6 @@ protected: std::string _travel_to_z(double z, const std::string &comment) override; private: - double m_belt_angle_rad = 0.; BeltBackTransform m_belt_back_transform; MachineFrameTransform m_machine_frame_transform; bool m_origin_snap[3] = {false, false, false}; diff --git a/src/libslic3r/BeltSliceStrategy.cpp b/src/libslic3r/BeltSliceStrategy.cpp index 81c4c06f1e..ddaabdef0b 100644 --- a/src/libslic3r/BeltSliceStrategy.cpp +++ b/src/libslic3r/BeltSliceStrategy.cpp @@ -17,10 +17,7 @@ std::unique_ptr BeltSliceStrategy::create(const PrintConfig & 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_rotation = BeltTransformPipeline::build_rotation_matrix(config, &m_has_rotation); - m_order = config.belt_mesh_transform_order.value; } void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, @@ -28,15 +25,10 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, bool has_remap, double *out_belt_min_z) const { - // 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). - // Rotation (if active) is applied AFTER shear/scale, matching build_forward_transform. - if (m_has_shear || m_has_scale || m_has_rotation) { - Matrix3d shear_scale = (m_order == BeltTransformOrder::ScaleThenShear) - ? Matrix3d(m_shear * m_scale) - : Matrix3d(m_scale * m_shear); + // Rotation is the only mesh-side belt transform (matching build_forward_transform). + if (m_has_rotation) { Transform3d belt_xform = Transform3d::Identity(); - belt_xform.linear() = Matrix3d(m_rotation * shear_scale); + belt_xform.linear() = m_rotation; trafo = belt_xform * trafo; } @@ -47,7 +39,7 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, // within the object) causes min_z to be computed against mesh-local vertex // coordinates rather than object-space coordinates, so volumes translated // along the slicer's Z axis are silently excluded from the bound check. - if (has_remap || m_has_shear || m_has_scale || m_has_rotation) { + if (has_remap || m_has_rotation) { // [BELT-DEBUG] Capture the incoming trafo for diagnostic logging. // This is the slicer-frame transform AFTER belt_xform but BEFORE z_shift. const Transform3d trafo_pre_shift = trafo; @@ -65,9 +57,7 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, ss << "(" << v.x() << "," << v.y() << "," << v.z() << ")"; return ss.str(); }; - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] apply_to_trafo enter" - << " has_shear=" << m_has_shear - << " has_scale=" << m_has_scale + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] apply_to_trafo enter" << " has_rotation=" << m_has_rotation << " has_remap=" << has_remap << " trafo.linear=" << log_mat(trafo_pre_shift.linear()) @@ -96,7 +86,7 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, vol_min_z = std::min(vol_min_z, pt.z()); min_z = std::min(min_z, pt.z()); } - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] vol[" << vol_idx + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] vol[" << vol_idx << "] id=" << mv->id().id << " name='" << mv->name << "'" << " mesh_bbox_min=" << log_vec3(mesh_min) << " mesh_bbox_max=" << log_vec3(mesh_max) << " get_matrix.translation=" << log_vec3(mv->get_matrix().translation()) @@ -105,7 +95,7 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, ++vol_idx; } double belt_z_shift_val = (min_z < 0. && min_z != std::numeric_limits::max()) ? -min_z : 0.; - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] combined min_z=" << min_z + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] combined min_z=" << min_z << " z_shift_val=" << belt_z_shift_val; if (belt_z_shift_val > 0.) { Transform3d z_shift = Transform3d::Identity(); @@ -114,11 +104,11 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, } if (out_belt_min_z) { double new_val = (min_z != std::numeric_limits::max()) ? min_z : 0.; - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] write m_belt_min_z tid=" << std::this_thread::get_id() + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] 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; } - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] apply_to_trafo exit" + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] apply_to_trafo exit" << " final_trafo.linear=" << log_mat(trafo.linear()) << " final_trafo.translation=" << log_vec3(trafo.translation()); } diff --git a/src/libslic3r/BeltSliceStrategy.hpp b/src/libslic3r/BeltSliceStrategy.hpp index 6009155c7b..ff56f6fece 100644 --- a/src/libslic3r/BeltSliceStrategy.hpp +++ b/src/libslic3r/BeltSliceStrategy.hpp @@ -13,17 +13,17 @@ namespace Slic3r { // Belt printer pre-slice transform strategy. // -// Encapsulates the pre-remap, shear, scale, and Z-shift transforms -// that are applied to model geometry before slicing on belt printers. -// Used by PrintObjectSlice.cpp to isolate belt-specific logic from -// the slicing pipeline. +// Encapsulates the pre-remap, rotation, and Z-shift transforms that are +// applied to model geometry before slicing on belt printers. (Shear & scale +// are applied to the g-code, not the mesh.) Used by PrintObjectSlice.cpp to +// isolate belt-specific logic from the slicing pipeline. class BeltSliceStrategy { public: // Create a strategy if belt_printer is enabled; returns nullptr otherwise. static std::unique_ptr create(const PrintConfig &config); - // Apply belt-specific transforms (shear + scale + z-shift) to the slicing trafo. + // Apply belt-specific transforms (rotation + z-shift) to the slicing trafo. // Pre-slice remap is handled separately (standalone feature). // has_remap: whether pre-slice remap was already applied (affects z-shift detection). void apply_to_trafo(Transform3d &trafo, @@ -34,13 +34,8 @@ public: private: explicit BeltSliceStrategy(const PrintConfig &config); - bool m_has_shear = false; - bool m_has_scale = false; - bool m_has_rotation = false; - Matrix3d m_shear = Matrix3d::Identity(); - Matrix3d m_scale = Matrix3d::Identity(); - Matrix3d m_rotation = Matrix3d::Identity(); - BeltTransformOrder m_order = BeltTransformOrder::ScaleThenShear; + bool m_has_rotation = false; + Matrix3d m_rotation = Matrix3d::Identity(); }; } // namespace Slic3r diff --git a/src/libslic3r/BeltTransform.cpp b/src/libslic3r/BeltTransform.cpp index 0d1ae41295..db23e13417 100644 --- a/src/libslic3r/BeltTransform.cpp +++ b/src/libslic3r/BeltTransform.cpp @@ -51,50 +51,6 @@ Transform3d BeltTransformPipeline::build_preslice_remap(const PrintConfig &confi return pre_remap; } -Matrix3d BeltTransformPipeline::build_shear_matrix(const PrintConfig &config, bool *has_shear_out) -{ - struct AxisShear { BeltShearMode mode; double angle; int from; }; - AxisShear axes[3] = { - { config.belt_shear_x.value, config.belt_shear_x_angle.value, int(config.belt_shear_x_from.value) }, - { config.belt_shear_y.value, config.belt_shear_y_angle.value, int(config.belt_shear_y_from.value) }, - { config.belt_shear_z.value, config.belt_shear_z_angle.value, int(config.belt_shear_z_from.value) }, - }; - - Matrix3d shear = Matrix3d::Identity(); - bool active = false; - for (int row = 0; row < 3; ++row) { - if (axes[row].mode != BeltShearMode::None) { - double factor = compute_shear_factor(axes[row].mode, axes[row].angle); - if (std::abs(factor) > EPSILON) { - shear(row, axes[row].from) += factor; - active = true; - } - } - } - if (has_shear_out) *has_shear_out = active; - return shear; -} - -Matrix3d BeltTransformPipeline::build_scale_matrix(const PrintConfig &config, bool *has_scale_out) -{ - double sx = compute_scale_factor(config.belt_scale_x.value, config.belt_scale_x_angle.value); - double sy = compute_scale_factor(config.belt_scale_y.value, config.belt_scale_y_angle.value); - double sz = compute_scale_factor(config.belt_scale_z.value, config.belt_scale_z_angle.value); - - bool 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; - } - if (has_scale_out) *has_scale_out = active; - return scale; -} - Matrix3d BeltTransformPipeline::build_rotation_matrix(const PrintConfig &config, bool *has_rot_out) { BeltRotationAxis axis = config.belt_slice_rotation.value; @@ -116,26 +72,13 @@ Matrix3d BeltTransformPipeline::build_rotation_matrix(const PrintConfig &config, Transform3d BeltTransformPipeline::build_forward_transform(const PrintConfig &config) { - Transform3d pre_remap = build_preslice_remap(config); - bool shear_active = false; - Matrix3d shear = build_shear_matrix(config, &shear_active); - bool scale_active = false; - Matrix3d scale = build_scale_matrix(config, &scale_active); - bool rot_active = false; - Matrix3d rot = build_rotation_matrix(config, &rot_active); - - // 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). - // Rotation is applied AFTER shear/scale: rot * shear_scale * pre_remap. - Matrix3d shear_scale = (config.belt_mesh_transform_order.value == BeltTransformOrder::ScaleThenShear) - ? Matrix3d(shear * scale) - : Matrix3d(scale * shear); + // Mesh-side belt transform: rotation applied after the pre-slice axis remap. + // (Shear & scale are a g-code-side stage, not part of the mesh transform.) + Transform3d pre_remap = build_preslice_remap(config); + Matrix3d rot = build_rotation_matrix(config); Transform3d combined = Transform3d::Identity(); - combined.linear() = Matrix3d(rot * shear_scale); + combined.linear() = rot; combined = combined * pre_remap; return combined; } @@ -190,166 +133,75 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl( BeltTransformPipeline::BeltHeightResult result; result.object_height = original_height; - // Extract Z-axis shear/scale + per-axis scale + transform order + rotation 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; - BeltRotationAxis rot_axis; - double rot_angle; + // Extract the mesh rotation from config (the sole mesh-side belt transform). + BeltRotationAxis rot_axis; + double rot_angle; 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); - order = config.belt_mesh_transform_order.value; - rot_axis = config.belt_slice_rotation.value; - rot_angle = config.belt_slice_rotation_angle.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; - } + rot_axis = config.belt_slice_rotation.value; + rot_angle = config.belt_slice_rotation_angle.value; } else { // DynamicPrintConfig path - auto get_shear = [&](const char *key) { - auto *opt = config.template option>(key); - return opt ? opt->value : BeltShearMode::None; - }; - auto get_scale = [&](const char *key) { - auto *opt = config.template option>(key); - return opt ? opt->value : BeltScaleMode::None; - }; auto get_float = [&](const char *key) { auto *opt = config.template option(key); - return opt ? opt->value : 45.0; - }; - auto get_axis = [&](const char *key) { - 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; + return opt ? opt->value : 0.0; }; auto get_rot_axis = [&](const char *key) { auto *opt = config.template option>(key); return opt ? opt->value : BeltRotationAxis::None; }; - 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"); - rot_axis = get_rot_axis("belt_slice_rotation"); - rot_angle = get_float("belt_slice_rotation_angle"); - 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"); - } + rot_axis = get_rot_axis("belt_slice_rotation"); + rot_angle = get_float("belt_slice_rotation_angle"); } - bool has_z_shear = z_shear_mode != BeltShearMode::None; - bool has_z_scale = z_scale_mode != BeltScaleMode::None; bool has_rotation = rot_axis != BeltRotationAxis::None && std::abs(rot_angle) > EPSILON; - - if (!has_z_shear && !has_z_scale && !has_rotation) + if (!has_rotation) return result; - 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_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; - double min_rz = std::numeric_limits::max(); - 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)}) { - // 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; - // 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 if (has_rotation) { - // Rotation-only path (no Z-shear): sweep 8 bbox corners through R. - double angle_rad = Geometry::deg2rad(rot_angle); - Vec3d unit_axis; - switch (rot_axis) { - case BeltRotationAxis::X: unit_axis = Vec3d::UnitX(); break; - case BeltRotationAxis::Y: unit_axis = Vec3d::UnitY(); break; - case BeltRotationAxis::Z: unit_axis = Vec3d::UnitZ(); break; - default: unit_axis = Vec3d::UnitX(); break; - } - Matrix3d R = Eigen::AngleAxisd(angle_rad, unit_axis).toRotationMatrix(); - double min_rz = std::numeric_limits::max(); - double max_rz = std::numeric_limits::lowest(); - for (int i = 0; i < 8; ++i) { - Vec3d c((i & 1) ? bb.max.x() : bb.min.x(), - (i & 2) ? bb.max.y() : bb.min.y(), - (i & 4) ? bb.max.z() : bb.min.z()); - double z = (R * c).z(); - min_rz = std::min(min_rz, z); - max_rz = std::max(max_rz, z); - } - // Optional Z-scale still applies multiplicatively if both are set. - result.object_height = (max_rz - min_rz) * (has_z_scale ? scale_z : 1.0); - - // Belt floor in slicer-frame is the image of z_machine = 0 under R. - // R(+α, X): point (·, y, 0) → (·, cos α · y, sin α · y) ⇒ z = tan(α) · y_s - // R(+α, Y): point (x, ·, 0) → (cos α · x, ·, -sin α · x) ⇒ z = -tan(α) · x_s - // R(+α, Z): point (·, ·, 0) → (·, ·, 0); no tilt → no floor - double sin_a = std::sin(angle_rad), cos_a = std::cos(angle_rad); - switch (rot_axis) { - case BeltRotationAxis::X: - result.floor_params.shear_factor = (std::abs(cos_a) > EPSILON) ? sin_a / cos_a : 0.; - result.floor_params.from_axis = 1; // Y - break; - case BeltRotationAxis::Y: - result.floor_params.shear_factor = (std::abs(cos_a) > EPSILON) ? -sin_a / cos_a : 0.; - result.floor_params.from_axis = 0; // X - break; - case BeltRotationAxis::Z: - default: - result.floor_params.shear_factor = 0.0; - result.floor_params.from_axis = 1; - break; - } - result.floor_params.z_shift = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.); - } else { - result.object_height = original_height * scale_z; + // Rotation path: sweep the 8 bbox corners through R to get the rotated height, + // then derive the belt floor (the image of machine-Z = 0 under R). + double angle_rad = Geometry::deg2rad(rot_angle); + Vec3d unit_axis; + switch (rot_axis) { + case BeltRotationAxis::X: unit_axis = Vec3d::UnitX(); break; + case BeltRotationAxis::Y: unit_axis = Vec3d::UnitY(); break; + case BeltRotationAxis::Z: unit_axis = Vec3d::UnitZ(); break; + default: unit_axis = Vec3d::UnitX(); break; } + Matrix3d R = Eigen::AngleAxisd(angle_rad, unit_axis).toRotationMatrix(); + double min_rz = std::numeric_limits::max(); + double max_rz = std::numeric_limits::lowest(); + for (int i = 0; i < 8; ++i) { + Vec3d c((i & 1) ? bb.max.x() : bb.min.x(), + (i & 2) ? bb.max.y() : bb.min.y(), + (i & 4) ? bb.max.z() : bb.min.z()); + double z = (R * c).z(); + min_rz = std::min(min_rz, z); + max_rz = std::max(max_rz, z); + } + result.object_height = max_rz - min_rz; + + // Belt floor in slicer-frame is the image of z_machine = 0 under R. + // R(+α, X): point (·, y, 0) → (·, cos α · y, sin α · y) ⇒ z = tan(α) · y_s + // R(+α, Y): point (x, ·, 0) → (cos α · x, ·, -sin α · x) ⇒ z = -tan(α) · x_s + // R(+α, Z): point (·, ·, 0) → (·, ·, 0); no tilt → no floor + double sin_a = std::sin(angle_rad), cos_a = std::cos(angle_rad); + switch (rot_axis) { + case BeltRotationAxis::X: + result.floor_params.shear_factor = (std::abs(cos_a) > EPSILON) ? sin_a / cos_a : 0.; + result.floor_params.from_axis = 1; // Y + break; + case BeltRotationAxis::Y: + result.floor_params.shear_factor = (std::abs(cos_a) > EPSILON) ? -sin_a / cos_a : 0.; + result.floor_params.from_axis = 0; // X + break; + case BeltRotationAxis::Z: + default: + result.floor_params.shear_factor = 0.0; + result.floor_params.from_axis = 1; + break; + } + result.floor_params.z_shift = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.); return result; } diff --git a/src/libslic3r/BeltTransform.hpp b/src/libslic3r/BeltTransform.hpp index 1ce47a2e31..50f56bd75f 100644 --- a/src/libslic3r/BeltTransform.hpp +++ b/src/libslic3r/BeltTransform.hpp @@ -15,11 +15,16 @@ class ModelObject; // Shared belt-printer transform math. // // The pre-slice pipeline applied in PrintObjectSlice.cpp is: -// trafo_out = z_shift * scale * shear * pre_remap * trafo_in +// trafo_out = z_shift * rotation * pre_remap * trafo_in // -// This class provides the building blocks so every call site uses the -// same implementation. z_shift is object-dependent (computed from mesh -// vertex bounds) and is NOT included in build_forward_transform(). +// Rotation is the sole mesh-side belt transform; shear & scale are applied +// to the g-code instead (see MachineFrameTransform). This class provides the +// building blocks so every call site uses the same implementation. z_shift is +// object-dependent (computed from mesh vertex bounds) and is NOT included in +// build_forward_transform(). +// +// The compute_shear_factor / compute_scale_factor math helpers below are still +// used by the g-code-side machine-frame shear/scale. class BeltTransformPipeline { public: @@ -75,50 +80,53 @@ public: get_int("preslice_remap_z") != int(RemapAxis::PosZ); } - static bool has_shear(const PrintConfig &config) - { - return config.belt_shear_x.value != BeltShearMode::None || - config.belt_shear_y.value != BeltShearMode::None || - config.belt_shear_z.value != BeltShearMode::None; - } - - static bool has_scale(const PrintConfig &config) - { - double sx = compute_scale_factor(config.belt_scale_x.value, config.belt_scale_x_angle.value); - double sy = compute_scale_factor(config.belt_scale_y.value, config.belt_scale_y_angle.value); - double sz = compute_scale_factor(config.belt_scale_z.value, config.belt_scale_z_angle.value); - return std::abs(sx - 1.) > EPSILON || - std::abs(sy - 1.) > EPSILON || - std::abs(sz - 1.) > EPSILON; - } - static bool has_rotation(const PrintConfig &config) { return config.belt_slice_rotation.value != BeltRotationAxis::None && std::abs(config.belt_slice_rotation_angle.value) > EPSILON; } + // Physical belt tilt derived from the slicing rotation — the single source of + // truth for bed rendering, support gravity tilt and the bed-exclusion + // projection. Returns the tilt magnitude in degrees split onto the X and Y + // build-plate tilt axes according to the rotation axis: + // rotation about X → tilt_x = angle (gantry tilts in the YZ plane) + // rotation about Y → tilt_y = angle (gantry tilts in the XZ plane) + // rotation about Z / None → no tilt (in-plane spin doesn't tilt the belt) + // The magnitude uses abs(angle) so a negative rotation still reports a positive + // physical tilt. + struct PhysicalTilt { double tilt_x_deg = 0.; double tilt_y_deg = 0.; }; + + static PhysicalTilt physical_tilt(BeltRotationAxis axis, double angle_deg) + { + PhysicalTilt t; + double mag = std::abs(angle_deg); + switch (axis) { + case BeltRotationAxis::X: t.tilt_x_deg = mag; break; + case BeltRotationAxis::Y: t.tilt_y_deg = mag; break; + default: break; // Z / None: no physical tilt + } + return t; + } + + static PhysicalTilt physical_tilt(const PrintConfig &config) + { + return physical_tilt(config.belt_slice_rotation.value, + config.belt_slice_rotation_angle.value); + } + // ---- Matrix builders -------------------------------------------------- // Build the pre-slice axis remap transform (includes Rev-mode translation). static Transform3d build_preslice_remap(const PrintConfig &config); - // Build the 3x3 shear matrix. Returns Identity if no shear is active. - // Also sets has_shear_out if non-null. - static Matrix3d build_shear_matrix(const PrintConfig &config, bool *has_shear_out = nullptr); - - // Build the 3x3 diagonal scale matrix. Returns Identity if no scale. - // Also sets has_scale_out if non-null. - static Matrix3d build_scale_matrix(const PrintConfig &config, bool *has_scale_out = nullptr); - // Build the 3x3 rotation matrix from belt_slice_rotation* config. // Returns Identity if rotation axis is None or angle is ~0. // Also sets has_rot_out if non-null. static Matrix3d build_rotation_matrix(const PrintConfig &config, bool *has_rot_out = nullptr); - // Combined forward transform. Shear/scale order is selected by - // belt_mesh_transform_order so the result matches what BeltSliceStrategy - // applied to the mesh (BeltBackTransform inverts this). + // Combined forward transform (rotation * pre_remap) — the mesh-side belt + // transform that BeltSliceStrategy applies and BeltBackTransform inverts. // Does NOT include the per-object Z-shift. static Transform3d build_forward_transform(const PrintConfig &config); diff --git a/src/libslic3r/FirstLayerPlane.cpp b/src/libslic3r/FirstLayerPlane.cpp index 10866c57f5..6a4feba886 100644 --- a/src/libslic3r/FirstLayerPlane.cpp +++ b/src/libslic3r/FirstLayerPlane.cpp @@ -85,9 +85,8 @@ FirstLayerPlane::FirstLayerPlane(const PrintConfig &config) FirstLayerPlaneMode mode = config.first_layer_plane.value; if (mode == FirstLayerPlaneMode::Auto) { bool belt_affine_active = config.belt_printer.value && - (config.belt_shear_z.value != BeltShearMode::None || - (config.belt_slice_rotation.value != BeltRotationAxis::None && - std::abs(config.belt_slice_rotation_angle.value) > EPSILON)); + config.belt_slice_rotation.value != BeltRotationAxis::None && + std::abs(config.belt_slice_rotation_angle.value) > EPSILON; mode = belt_affine_active ? FirstLayerPlaneMode::BeltAffine : FirstLayerPlaneMode::XY; } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 047d00250c..2ad11a710d 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6520,14 +6520,14 @@ 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 + // Belt printer: compensate for any 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. + // back-transform to a machine-frame region of volume V / |det(T)|. The + // 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.) 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/BeltBackTransform.cpp b/src/libslic3r/GCode/BeltBackTransform.cpp index 654afe6262..6a25bbd408 100644 --- a/src/libslic3r/GCode/BeltBackTransform.cpp +++ b/src/libslic3r/GCode/BeltBackTransform.cpp @@ -12,18 +12,15 @@ bool BeltBackTransform::init_from_config(const PrintConfig &config) return false; // Require at least one active transform to proceed. - bool has_global_shear = config.belt_shear_x_global.value || - config.belt_shear_y_global.value || - config.belt_shear_z_global.value; bool has_global_rotation = config.belt_slice_rotation_global.value && config.belt_slice_rotation.value != BeltRotationAxis::None; bool has_preslice_global = config.belt_preslice_global.value || config.preslice_remap_global.value; - if (!has_global_shear && !has_global_rotation && !has_preslice_global + if (!has_global_rotation && !has_preslice_global && !BeltTransformPipeline::has_preslice_remap(config)) return false; - // Build the forward pipeline (scale * shear * pre_remap) and store its inverse. + // Build the forward pipeline (rotation * pre_remap) and store its inverse. Transform3d forward = BeltTransformPipeline::build_forward_transform(config); if (forward.isApprox(Transform3d::Identity())) return false; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index d9fc8bc992..7df84ade84 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3072,90 +3072,21 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers return; } - // Belt printer angle detection from G-code header comment. - if (boost::starts_with(comment, " belt_printer_angle = ")) { + // Belt printer: derive the physical tilt magnitude from the slicing-rotation + // angle header comment (used to enable the preview's belt view). + if (boost::starts_with(comment, " belt_slice_rotation_angle = ")) { try { - m_result.belt_printer_angle = std::stof(std::string(comment.substr(22))); + m_result.belt_tilt_angle = std::abs(std::stof(std::string(comment.substr(29)))); } catch (...) {} return; } - // Belt printer: parse shear configs from header comments. + // Belt printer: parse pre-slice axis remap from header comments. { - auto parse_shear_mode = [](const std::string &s) -> BeltShearMode { - if (s == "pos_cot") return BeltShearMode::PosCot; - if (s == "neg_cot") return BeltShearMode::NegCot; - if (s == "pos_tan") return BeltShearMode::PosTan; - if (s == "neg_tan") return BeltShearMode::NegTan; - return BeltShearMode::None; - }; - auto parse_axis = [](const std::string &s) -> BeltAxis { - if (s == "y") return BeltAxis::Y; - if (s == "z") return BeltAxis::Z; - return BeltAxis::X; - }; - auto parse_scale_mode = [](const std::string &s) -> BeltScaleMode { - if (s == "inv_sin") return BeltScaleMode::InvSin; - if (s == "inv_cos") return BeltScaleMode::InvCos; - if (s == "sin") return BeltScaleMode::Sin; - if (s == "cos") return BeltScaleMode::Cos; - return BeltScaleMode::None; - }; auto trim = [](const std::string &s) -> std::string { size_t start = s.find_first_not_of(" \t\r\n"); size_t end = s.find_last_not_of(" \t\r\n"); return (start == std::string::npos) ? "" : s.substr(start, end - start + 1); }; - // Shear X - if (boost::starts_with(comment, " belt_shear_x = ")) { - m_result.belt_shear_x = parse_shear_mode(trim(std::string(comment.substr(16)))); return; - } - if (boost::starts_with(comment, " belt_shear_x_angle = ")) { - try { m_result.belt_shear_x_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return; - } - if (boost::starts_with(comment, " belt_shear_x_from = ")) { - m_result.belt_shear_x_from = parse_axis(trim(std::string(comment.substr(21)))); return; - } - // Shear Y - if (boost::starts_with(comment, " belt_shear_y = ")) { - m_result.belt_shear_y = parse_shear_mode(trim(std::string(comment.substr(16)))); return; - } - if (boost::starts_with(comment, " belt_shear_y_angle = ")) { - try { m_result.belt_shear_y_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return; - } - if (boost::starts_with(comment, " belt_shear_y_from = ")) { - m_result.belt_shear_y_from = parse_axis(trim(std::string(comment.substr(21)))); return; - } - // Shear Z - if (boost::starts_with(comment, " belt_shear_z = ")) { - m_result.belt_shear_z = parse_shear_mode(trim(std::string(comment.substr(16)))); return; - } - if (boost::starts_with(comment, " belt_shear_z_angle = ")) { - try { m_result.belt_shear_z_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return; - } - if (boost::starts_with(comment, " belt_shear_z_from = ")) { - m_result.belt_shear_z_from = parse_axis(trim(std::string(comment.substr(21)))); return; - } - // Scale X - if (boost::starts_with(comment, " belt_scale_x = ")) { - m_result.belt_scale_x = parse_scale_mode(trim(std::string(comment.substr(16)))); return; - } - if (boost::starts_with(comment, " belt_scale_x_angle = ")) { - try { m_result.belt_scale_x_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return; - } - // Scale Y - if (boost::starts_with(comment, " belt_scale_y = ")) { - m_result.belt_scale_y = parse_scale_mode(trim(std::string(comment.substr(16)))); return; - } - if (boost::starts_with(comment, " belt_scale_y_angle = ")) { - try { m_result.belt_scale_y_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return; - } - // Scale Z - if (boost::starts_with(comment, " belt_scale_z = ")) { - m_result.belt_scale_z = parse_scale_mode(trim(std::string(comment.substr(16)))); return; - } - if (boost::starts_with(comment, " belt_scale_z_angle = ")) { - try { m_result.belt_scale_z_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return; - } // Pre-slice axis remap auto parse_remap_axis = [](const std::string &s) -> RemapAxis { if (s == "pos_x") return RemapAxis::PosX; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 4e5bb5b746..3047c005ed 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -245,25 +245,9 @@ class Print; bool support_traditional_timelapse{true}; float printable_height; float z_offset; - // Belt printer: angle for coordinate transformation in preview. - float belt_printer_angle{ 0.f }; - // Belt printer: per-axis shear config. - BeltShearMode belt_shear_x{ BeltShearMode::None }; - float belt_shear_x_angle{ 45.f }; - BeltAxis belt_shear_x_from{ BeltAxis::Y }; - BeltShearMode belt_shear_y{ BeltShearMode::None }; - float belt_shear_y_angle{ 45.f }; - BeltAxis belt_shear_y_from{ BeltAxis::Y }; - BeltShearMode belt_shear_z{ BeltShearMode::None }; - float belt_shear_z_angle{ 45.f }; - BeltAxis belt_shear_z_from{ BeltAxis::Y }; - // Belt printer: per-axis scale config. - BeltScaleMode belt_scale_x{ BeltScaleMode::None }; - float belt_scale_x_angle{ 45.f }; - BeltScaleMode belt_scale_y{ BeltScaleMode::None }; - float belt_scale_y_angle{ 45.f }; - BeltScaleMode belt_scale_z{ BeltScaleMode::None }; - float belt_scale_z_angle{ 45.f }; + // Belt printer: physical tilt magnitude (deg) parsed from the slicing-rotation + // header comment; used to enable the preview's belt view. + float belt_tilt_angle{ 0.f }; RemapAxis preslice_remap_x{ RemapAxis::PosX }; RemapAxis preslice_remap_y{ RemapAxis::PosY }; RemapAxis preslice_remap_z{ RemapAxis::PosZ }; @@ -332,22 +316,7 @@ class Print; optimal_assignment = other.optimal_assignment; filament_change_count_map = other.filament_change_count_map; initial_layer_time = other.initial_layer_time; - belt_printer_angle = other.belt_printer_angle; - belt_shear_x = other.belt_shear_x; - belt_shear_x_angle = other.belt_shear_x_angle; - belt_shear_x_from = other.belt_shear_x_from; - belt_shear_y = other.belt_shear_y; - belt_shear_y_angle = other.belt_shear_y_angle; - belt_shear_y_from = other.belt_shear_y_from; - belt_shear_z = other.belt_shear_z; - belt_shear_z_angle = other.belt_shear_z_angle; - belt_shear_z_from = other.belt_shear_z_from; - belt_scale_x = other.belt_scale_x; - belt_scale_x_angle = other.belt_scale_x_angle; - belt_scale_y = other.belt_scale_y; - belt_scale_y_angle = other.belt_scale_y_angle; - belt_scale_z = other.belt_scale_z; - belt_scale_z_angle = other.belt_scale_z_angle; + belt_tilt_angle = other.belt_tilt_angle; preslice_remap_x = other.preslice_remap_x; preslice_remap_y = other.preslice_remap_y; preslice_remap_z = other.preslice_remap_z; diff --git a/src/libslic3r/GCode/MachineFrameTransform.cpp b/src/libslic3r/GCode/MachineFrameTransform.cpp index 10a658c7f7..03f74605fe 100644 --- a/src/libslic3r/GCode/MachineFrameTransform.cpp +++ b/src/libslic3r/GCode/MachineFrameTransform.cpp @@ -6,50 +6,6 @@ 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) { @@ -94,13 +50,6 @@ Matrix3d build_gcode_scale_matrix(const PrintConfig &config, bool &active) 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) @@ -111,13 +60,12 @@ bool MachineFrameTransform::init_from_config(const PrintConfig &config) 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)) + if (!shear_active && !scale_active) return false; // Compose per belt_gcode_transform_order: @@ -127,7 +75,6 @@ bool MachineFrameTransform::init_from_config(const PrintConfig &config) 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; diff --git a/src/libslic3r/GCode/MachineFrameTransform.hpp b/src/libslic3r/GCode/MachineFrameTransform.hpp index 4a2f189802..9fa25b2323 100644 --- a/src/libslic3r/GCode/MachineFrameTransform.hpp +++ b/src/libslic3r/GCode/MachineFrameTransform.hpp @@ -9,15 +9,14 @@ 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: +// Applied in BeltGCodeWriter::to_machine_coords AFTER the back-transform, +// the gcode_remap_* axis remap and per-axis origin snap. Maps Cartesian +// (axis-permuted) G-code coordinates into the printer's physical machine +// frame using: // 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. +// Composition follows belt_gcode_transform_order (shear * scale or scale * shear). class MachineFrameTransform { public: @@ -25,7 +24,7 @@ public: // 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. + // both shear and scale are identity. bool init_from_config(const PrintConfig &config); // Apply the transform to a point. Returns pos unchanged if not active. diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index fab3e963a7..05bdb2f262 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1318,12 +1318,8 @@ static std::vector s_Preset_machine_limits_options { static std::vector s_Preset_printer_options { "printer_technology", - "printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "build_plate_tilt_x", "build_plate_tilt_y", "belt_printer", "belt_printer_angle", "belt_printer_infinite_y", "belt_shear_x", "belt_shear_x_angle", "belt_shear_x_from", "belt_shear_x_global", - "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", + "printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "build_plate_tilt_x", "build_plate_tilt_y", "belt_printer", "belt_printer_infinite_y", "belt_slice_rotation", "belt_slice_rotation_angle", "belt_slice_rotation_global", - "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", @@ -1333,7 +1329,6 @@ static std::vector s_Preset_printer_options { "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 c45334f95e..32f43052af 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -112,7 +112,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "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", @@ -301,26 +300,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "build_plate_tilt_y" // Belt printer transform options change the mesh geometry before slicing. || opt_key == "belt_printer" - || opt_key == "belt_printer_angle" - || opt_key == "belt_shear_x" - || opt_key == "belt_shear_x_angle" - || opt_key == "belt_shear_x_from" - || opt_key == "belt_shear_x_global" - || opt_key == "belt_shear_y" - || opt_key == "belt_shear_y_angle" - || opt_key == "belt_shear_y_from" - || opt_key == "belt_shear_y_global" - || opt_key == "belt_shear_z" - || opt_key == "belt_shear_z_angle" - || opt_key == "belt_shear_z_from" - || opt_key == "belt_shear_z_global" - || opt_key == "belt_scale_x" - || opt_key == "belt_scale_x_angle" - || opt_key == "belt_scale_y" - || 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_slice_rotation" + || opt_key == "belt_slice_rotation_angle" + || opt_key == "belt_slice_rotation_global" || opt_key == "belt_preslice_global" || opt_key == "preslice_remap_global" || opt_key == "preslice_remap_x" @@ -2284,10 +2266,13 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) int object_count = m_objects.size(); std::set need_slicing_objects; std::set re_slicing_objects; - // Belt global Z shear: each object needs unique layer Z values based on - // its bed position, so sharing layers between "identical" objects is wrong. - bool belt_no_share = m_config.belt_printer.value && m_config.belt_shear_z_global.value - && m_config.belt_shear_z.value != BeltShearMode::None; + // Belt global modes couple each object's bed position into its layer Z values, + // so sharing layers between "identical" objects is wrong. + bool belt_no_share = m_config.belt_printer.value && + ((m_config.belt_slice_rotation_global.value + && m_config.belt_slice_rotation.value != BeltRotationAxis::None) + || m_config.preslice_remap_global.value + || m_config.belt_preslice_global.value); if (!use_cache) { for (int index = 0; index < object_count; index++) { diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index b883b474be..3dddcc40dd 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1537,8 +1537,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Belt global mode: force each instance into its own PrintObject // so each gets independent layer Z values. bool belt_force_separate = m_config.belt_printer.value && ( - (m_config.belt_shear_z_global.value && m_config.belt_shear_z.value != BeltShearMode::None) - || (m_config.belt_slice_rotation_global.value + (m_config.belt_slice_rotation_global.value && m_config.belt_slice_rotation.value != BeltRotationAxis::None && std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON) || m_config.belt_preslice_global.value @@ -1633,8 +1632,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // min_shift across all objects, so one move affects everyone). if (belt_instances_shifted && m_config.belt_printer.value - && ((m_config.belt_shear_z_global.value && m_config.belt_shear_z.value != BeltShearMode::None) - || (m_config.belt_slice_rotation_global.value + && ((m_config.belt_slice_rotation_global.value && m_config.belt_slice_rotation.value != BeltRotationAxis::None && std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON) || m_config.belt_preslice_global.value diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8ee2350487..5c33692db0 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1,5 +1,6 @@ #include "PrintConfig.hpp" #include "PrintConfigConstants.hpp" +#include "BeltTransform.hpp" #include "ClipperUtils.hpp" #include "Config.hpp" #include "Geometry.hpp" @@ -6388,18 +6389,6 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); - def = this->add("belt_printer_angle", coFloat); - def->label = L("Belt angle"); - def->category = L("Printable space"); - def->tooltip = L("The tilt angle of the belt surface in degrees. " - "Most belt printers use a 45-degree angle. " - "This controls the rotation applied to the slicing plane and G-code coordinates."); - def->sidetext = u8"\u00B0"; - def->min = 0; - def->max = 90; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(45.)); - def = this->add("belt_printer_infinite_y", coBool); def->label = L("Infinite Y axis"); def->category = L("Printable space"); @@ -6409,134 +6398,42 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); - // Per-axis shear controls for belt printer - auto add_belt_shear_mode = [this](const char *key, const char *label, BeltShearMode default_mode, - ConfigOptionMode mode = comAdvanced) { - auto def = this->add(key, coEnum); - def->label = L(label); - def->category = L("Printable space"); - def->tooltip = L("Shear function applied to this axis in belt printer mode."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values = {"none", "pos_cot", "neg_cot", "pos_tan", "neg_tan"}; - def->enum_labels = {L("None"), L("+cot(α)"), L("-cot(α)"), L("+tan(α)"), L("-tan(α)")}; - def->mode = mode; - def->set_default_value(new ConfigOptionEnum(default_mode)); - }; - auto add_belt_shear_angle = [this](const char *key, const char *label, - ConfigOptionMode mode = comAdvanced) { - auto def = this->add(key, coFloat); - def->label = L(label); - def->category = L("Printable space"); - def->tooltip = L("Angle (degrees) for the shear function on this axis."); - def->sidetext = L("°"); - def->min = 0.1; - def->max = 89.9; - def->mode = mode; - def->set_default_value(new ConfigOptionFloat(45)); - }; - auto add_belt_axis_enum = [this](const char *key, const char *label, const char *tooltip, - BeltAxis default_axis, ConfigOptionMode mode = comAdvanced) { - 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 = {"x", "y", "z"}; - def->enum_labels = {L("X"), L("Y"), L("Z")}; - def->mode = mode; - def->set_default_value(new ConfigOptionEnum(default_axis)); - }; - - auto add_belt_shear_global = [this](const char *key, const char *label, bool default_val = false) { - auto def = this->add(key, coBool); - def->label = L(label); - def->category = L("Printable space"); - def->tooltip = L("Apply shear in global coordinates (position-aware) rather than object-local coordinates."); - def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(default_val)); - }; - - add_belt_shear_mode ("belt_shear_x", "Function", BeltShearMode::None, comExpert); - add_belt_shear_angle("belt_shear_x_angle", "Angle", comExpert); - add_belt_axis_enum ("belt_shear_x_from", "From", "Source axis for X shear.", BeltAxis::Z, comExpert); - add_belt_shear_global("belt_shear_x_global", "Global"); - - add_belt_shear_mode ("belt_shear_y", "Function", BeltShearMode::None, comExpert); - add_belt_shear_angle("belt_shear_y_angle", "Angle", comExpert); - add_belt_axis_enum ("belt_shear_y_from", "From", "Source axis for Y shear.", BeltAxis::Z, comExpert); - add_belt_shear_global("belt_shear_y_global", "Global"); - - add_belt_shear_mode ("belt_shear_z", "Function", BeltShearMode::None, comExpert); - add_belt_shear_angle("belt_shear_z_angle", "Angle", comExpert); - add_belt_axis_enum ("belt_shear_z_from", "From", "Source axis for Z shear.", BeltAxis::Y, comExpert); - add_belt_shear_global("belt_shear_z_global", "Global", true); - - // Per-axis scale controls for belt printer - auto add_belt_scale_mode = [this](const char *key, const char *label, BeltScaleMode default_mode, - ConfigOptionMode mode = comAdvanced) { - auto def = this->add(key, coEnum); - def->label = L(label); - def->category = L("Printable space"); - def->tooltip = L("Scale factor applied to this axis in belt printer mode."); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values = {"none", "inv_sin", "inv_cos", "sin", "cos"}; - def->enum_labels = {L("None"), L("1/sin(α)"), L("1/cos(α)"), L("sin(α)"), L("cos(α)")}; - def->mode = mode; - def->set_default_value(new ConfigOptionEnum(default_mode)); - }; - auto add_belt_scale_angle = [this](const char *key, const char *label, - ConfigOptionMode mode = comAdvanced) { - auto def = this->add(key, coFloat); - def->label = L(label); - def->category = L("Printable space"); - def->tooltip = L("Angle (degrees) for the scale function on this axis."); - def->sidetext = L("°"); - def->min = 0.1; - def->max = 89.9; - def->mode = mode; - def->set_default_value(new ConfigOptionFloat(45)); - }; - - add_belt_scale_mode ("belt_scale_x", "Function", BeltScaleMode::None, comExpert); - add_belt_scale_angle("belt_scale_x_angle", "Angle", comExpert); - - add_belt_scale_mode ("belt_scale_y", "Function", BeltScaleMode::None, comExpert); - add_belt_scale_angle("belt_scale_y_angle", "Angle", comExpert); - - add_belt_scale_mode ("belt_scale_z", "Function", BeltScaleMode::None, comExpert); - add_belt_scale_angle("belt_scale_z_angle", "Angle", comExpert); - - // Global slicing rotation (alternative to per-axis shear+scale). + // Mesh rotation applied before slicing — the sole mesh-side belt transform AND + // the single source of truth for the physical belt tilt (bed rendering, support + // gravity tilt and bed-exclusion projection all derive their angle from this). def = this->add("belt_slice_rotation", coEnum); - def->label = L("Slicing rotation axis"); + def->label = L("Belt tilt axis"); def->category = L("Printable space"); - def->tooltip = L("Rotate the mesh by this axis before slicing. Use this for an " - "isometric (no shear distortion) belt slicing transform. " - "Mutually exclusive with the per-axis shear/scale controls " - "in the UI; the pipeline composes them if both are set in JSON."); + def->tooltip = L("Axis the mesh is rotated about before slicing. This is the belt " + "printer's tilt: an isometric (no distortion) rotation that also " + "drives bed rendering and support gravity tilt, and that the g-code " + "back-transform inverts before the machine-frame shear/scale and remap. " + "X is the typical gantry tilt (belt travels along Y)."); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values = {"none", "x", "y", "z"}; def->enum_labels = {L("None"), L("X"), L("Y"), L("Z")}; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(BeltRotationAxis::None)); + def->set_default_value(new ConfigOptionEnum(BeltRotationAxis::X)); def = this->add("belt_slice_rotation_angle", coFloat); - def->label = L("Slicing rotation angle"); + def->label = L("Belt tilt angle"); def->category = L("Printable space"); - def->tooltip = L("Magnitude of the slicing rotation, in degrees. Positive values " - "rotate counter-clockwise looking down the positive axis."); + def->tooltip = L("Tilt angle of the belt surface, in degrees. Most belt printers use " + "45°. Positive values rotate counter-clockwise looking down the " + "positive tilt axis; the magnitude is also the physical belt tilt " + "used for bed rendering and support gravity."); def->sidetext = L("°"); def->min = -180.; def->max = 180.; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.)); + def->set_default_value(new ConfigOptionFloat(45.)); def = this->add("belt_slice_rotation_global", coBool); def->label = L("Global"); def->category = L("Printable space"); def->tooltip = L("Treat the slicing rotation as part of the global forward transform " "that BeltBackTransform inverts before the machine-frame remap. " - "Required for rotation-mode belt printers; mirrors belt_shear_z_global. " + "Required for rotation-mode belt printers. " "Defaults to on because virtually all rotation-mode printers need it."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); @@ -6553,12 +6450,6 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionEnum(BeltTransformOrder::ShearThenScale)); }; - 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, ConfigOptionMode mode = comSimple) { @@ -6599,7 +6490,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("When enabled, the pre-slice axis remap accounts for each object's bed position. " "Without this, the remap is applied locally around each object's center, so " "objects at different positions don't get a position-dependent contribution. " - "Mirrors the per-axis 'Global' option on belt mesh shears, but for the remap."); + "Mirrors the 'Global' option on the belt slicing rotation, but for the remap."); def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); @@ -6609,6 +6500,69 @@ void PrintConfigDef::init_fff_params() // 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. + // These shear/scale transforms act on the G-code coordinates (not the mesh) — they are + // the belt printer's only shear/scale stage. + auto add_belt_shear_mode = [this](const char *key, const char *label, BeltShearMode default_mode, + ConfigOptionMode mode = comAdvanced) { + auto def = this->add(key, coEnum); + def->label = L(label); + def->category = L("Printable space"); + def->tooltip = L("Shear function applied to this axis in belt printer mode."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = {"none", "pos_cot", "neg_cot", "pos_tan", "neg_tan"}; + def->enum_labels = {L("None"), L("+cot(α)"), L("-cot(α)"), L("+tan(α)"), L("-tan(α)")}; + def->mode = mode; + def->set_default_value(new ConfigOptionEnum(default_mode)); + }; + auto add_belt_shear_angle = [this](const char *key, const char *label, + ConfigOptionMode mode = comAdvanced) { + auto def = this->add(key, coFloat); + def->label = L(label); + def->category = L("Printable space"); + def->tooltip = L("Angle (degrees) for the shear function on this axis."); + def->sidetext = L("°"); + def->min = 0.1; + def->max = 89.9; + def->mode = mode; + def->set_default_value(new ConfigOptionFloat(45)); + }; + auto add_belt_axis_enum = [this](const char *key, const char *label, const char *tooltip, + BeltAxis default_axis, ConfigOptionMode mode = comAdvanced) { + 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 = {"x", "y", "z"}; + def->enum_labels = {L("X"), L("Y"), L("Z")}; + def->mode = mode; + def->set_default_value(new ConfigOptionEnum(default_axis)); + }; + auto add_belt_scale_mode = [this](const char *key, const char *label, BeltScaleMode default_mode, + ConfigOptionMode mode = comAdvanced) { + auto def = this->add(key, coEnum); + def->label = L(label); + def->category = L("Printable space"); + def->tooltip = L("Scale factor applied to this axis in belt printer mode."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = {"none", "inv_sin", "inv_cos", "sin", "cos"}; + def->enum_labels = {L("None"), L("1/sin(α)"), L("1/cos(α)"), L("sin(α)"), L("cos(α)")}; + def->mode = mode; + def->set_default_value(new ConfigOptionEnum(default_mode)); + }; + auto add_belt_scale_angle = [this](const char *key, const char *label, + ConfigOptionMode mode = comAdvanced) { + auto def = this->add(key, coFloat); + def->label = L(label); + def->category = L("Printable space"); + def->tooltip = L("Angle (degrees) for the scale function on this axis."); + def->sidetext = L("°"); + def->min = 0.1; + def->max = 89.9; + def->mode = mode; + def->set_default_value(new ConfigOptionFloat(45)); + }; + add_belt_shear_mode ("gcode_shear_x", "Function", BeltShearMode::None, comExpert); add_belt_shear_angle("gcode_shear_x_angle", "Angle", comExpert); add_belt_axis_enum ("gcode_shear_x_from", "From", "Source axis for X shear in the machine-frame stage.", BeltAxis::Z, comExpert); @@ -6635,19 +6589,6 @@ void PrintConfigDef::init_fff_params() "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"); @@ -11801,16 +11742,22 @@ Polygons get_bed_excluded_area(const PrintConfig& cfg) { const Pointfs exclude_area_points = cfg.bed_exclude_area.values; - // Belt printer: project exclusion zone points from belt surface to machine-frame XY. - // On the belt surface, Z=0, so machine_Y = belt_Y * cos(angle). + // Belt printer: project exclusion zone points from the belt surface to machine-frame XY. + // On the belt surface Z=0, so the in-plane axis foreshortens by cos(tilt). The tilt + // axis decides which bed axis foreshortens: tilt about X (belt along Y) scales Y, + // tilt about Y (belt along X) scales X. Derived from belt_slice_rotation. const bool is_belt = cfg.belt_printer.value; - const double belt_cos = is_belt ? std::cos(Geometry::deg2rad(cfg.belt_printer_angle.value)) : 1.0; + const auto tilt = BeltTransformPipeline::physical_tilt( + cfg.belt_slice_rotation.value, cfg.belt_slice_rotation_angle.value); + const double cos_x = is_belt ? std::cos(Geometry::deg2rad(tilt.tilt_x_deg)) : 1.0; // foreshortens Y + const double cos_y = is_belt ? std::cos(Geometry::deg2rad(tilt.tilt_y_deg)) : 1.0; // foreshortens X Polygon exclude_poly; for (int i = 0; i < exclude_area_points.size(); i++) { auto pt = exclude_area_points[i]; - double y = is_belt ? pt.y() * belt_cos : pt.y(); - exclude_poly.points.emplace_back(scale_(pt.x()), scale_(y)); + double x = is_belt ? pt.x() * cos_y : pt.x(); + double y = is_belt ? pt.y() * cos_x : pt.y(); + exclude_poly.points.emplace_back(scale_(x), scale_(y)); } exclude_poly.make_counter_clockwise(); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index bcfaab7bbc..f13c04f454 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1573,29 +1573,13 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, build_plate_tilt_y)) // Belt printer settings (printer-level). ((ConfigOptionBool, belt_printer)) - ((ConfigOptionFloat, belt_printer_angle)) ((ConfigOptionBool, belt_printer_infinite_y)) - ((ConfigOptionEnum, belt_shear_x)) - ((ConfigOptionFloat, belt_shear_x_angle)) - ((ConfigOptionEnum, belt_shear_x_from)) - ((ConfigOptionBool, belt_shear_x_global)) - ((ConfigOptionEnum, belt_shear_y)) - ((ConfigOptionFloat, belt_shear_y_angle)) - ((ConfigOptionEnum, belt_shear_y_from)) - ((ConfigOptionBool, belt_shear_y_global)) - ((ConfigOptionEnum, belt_shear_z)) - ((ConfigOptionFloat, belt_shear_z_angle)) - ((ConfigOptionEnum, belt_shear_z_from)) - ((ConfigOptionBool, belt_shear_z_global)) - ((ConfigOptionEnum, belt_scale_x)) - ((ConfigOptionFloat, belt_scale_x_angle)) - ((ConfigOptionEnum, belt_scale_y)) - ((ConfigOptionFloat, belt_scale_y_angle)) - ((ConfigOptionEnum, belt_scale_z)) - ((ConfigOptionFloat, belt_scale_z_angle)) - // Global mesh rotation as an alternative to per-axis shear/scale (isometric - // slicing transform). Composes with shear in the pipeline math; UI gates - // them as mutually exclusive. + // Mesh rotation applied before slicing — the sole mesh-side belt transform and + // the single source of truth for the physical belt tilt (its angle + axis drive + // bed rendering, support gravity tilt, and the bed-exclusion projection). + // Isometric (no distortion); the g-code back-transform inverts it before the + // machine-frame remap. (Shear & scale are applied to the g-code, not the + // mesh — see gcode_shear_* / gcode_scale_* below.) ((ConfigOptionEnum, belt_slice_rotation)) ((ConfigOptionFloat, belt_slice_rotation_angle)) ((ConfigOptionBool, belt_slice_rotation_global)) @@ -1621,11 +1605,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((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/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index deaf6fdfce..5fc7df232e 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -341,9 +341,7 @@ static std::vector> slices_to_regions( // pushes those layers into the parallel_for path below, which handles multi-volume // clipping per layer without relying on the bbox Z range. const bool bbox_z_in_layer_frame = !(print_config.belt_printer.value && - (BeltTransformPipeline::has_shear(print_config) - || BeltTransformPipeline::has_scale(print_config) - || BeltTransformPipeline::has_rotation(print_config) + (BeltTransformPipeline::has_rotation(print_config) || BeltTransformPipeline::has_preslice_remap(print_config))); // Belt-transform addendum: with bbox-Z untrusted, the simple path's // "first model_part wins" logic drops subsequent volumes' slices unless @@ -880,12 +878,12 @@ void groupingVolumesForBrim(PrintObject* object, LayerPtrs& layers, int firstLay // Resulting expolygons of layer regions are marked as Internal. void PrintObject::slice() { - BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] slice request tid=" << std::this_thread::get_id() << " obj=" << this; + BOOST_LOG_TRIVIAL(trace) << "[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)"; + BOOST_LOG_TRIVIAL(trace) << "[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; + BOOST_LOG_TRIVIAL(trace) << "[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; @@ -968,16 +966,16 @@ void PrintObject::slice() // regardless of global mode, only the output Z coordinates change. { const auto &pcfg = this->print()->config(); - BOOST_LOG_TRIVIAL(warning) << "Belt global check: belt_printer=" << pcfg.belt_printer.value - << " belt_shear_z=" << int(pcfg.belt_shear_z.value) - << " belt_shear_z_global=" << pcfg.belt_shear_z_global.value + BOOST_LOG_TRIVIAL(trace) << "Belt global check: belt_printer=" << pcfg.belt_printer.value + << " belt_slice_rotation=" << int(pcfg.belt_slice_rotation.value) + << " belt_slice_rotation_global=" << pcfg.belt_slice_rotation_global.value << " belt_preslice_global=" << pcfg.belt_preslice_global.value << " object=" << this->model_object()->name; if (pcfg.belt_printer.value) { Point inst_shift = this->instances().empty() ? Point(0, 0) : this->instances().front().shift - this->center_offset(); - BOOST_LOG_TRIVIAL(warning) << "Belt global: object " << this->model_object()->name + BOOST_LOG_TRIVIAL(trace) << "Belt global: object " << this->model_object()->name << " instances=" << this->instances().size() << " shift=(" << unscale(inst_shift.x()) << ", " << unscale(inst_shift.y()) << ")"; @@ -987,20 +985,14 @@ void PrintObject::slice() // so the slicer can slice with slicer_z >= 0. BeltBackTransform inverts // build_forward_transform() which DOES NOT include this per-object // Z-shift (it's not known until vertex scan time). Result: G-code - // coords emerge offset by the un-undone Z-shift — for shear that's a - // pure machine_z lift; for rotation it leaks into both machine_y and - // machine_z because the inverse rotation couples slicer_z back into - // both axes. Compensating layer.print_z by shear_min_z here makes the - // back-transform produce correct machine-frame coordinates whether or - // not any global mode is active. - // - // (The original global-mode-only application of shear_min_z was sized - // for cube-vs-inverted-cone-tip differentiation; the same compensation - // is what fixes assemblies and rotation-mode parts where z_shift > 0.) + // coords emerge offset by the un-undone Z-shift — the inverse rotation + // couples slicer_z back into both machine_y and machine_z. Compensating + // layer.print_z by belt_z_shift here makes the back-transform produce + // correct machine-frame coordinates whether or not a global mode is active. double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg) ? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.; - double shear_min_z = m_belt_min_z - belt_surface_z; - double global_z_offset = shear_min_z; + double belt_z_shift = m_belt_min_z - belt_surface_z; + double global_z_offset = belt_z_shift; // Centering correction: trafo_centered pretranslates by // -m_center_offset.{x,y}. Under the belt forward transform, the @@ -1019,7 +1011,7 @@ void PrintObject::slice() 0.); double centering_z_corr = (T_fwd.linear() * c_off).z(); global_z_offset += centering_z_corr; - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] centering correction" + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] centering correction" << " obj=" << this->model_object()->name << " m_center_offset_mm=(" << c_off.x() << "," << c_off.y() << ")" << " centering_z_corr=" << centering_z_corr @@ -1031,7 +1023,7 @@ void PrintObject::slice() // print_z adjustment. { BoundingBoxf3 raw_bb = this->model_object()->raw_bounding_box(); - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] slice() per-object summary" + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] slice() per-object summary" << " obj=" << this->model_object()->name << " n_volumes=" << this->model_object()->volumes.size() << " raw_bbox.min=(" << raw_bb.min.x() << "," << raw_bb.min.y() << "," << raw_bb.min.z() << ")" @@ -1041,13 +1033,13 @@ void PrintObject::slice() << " inst_shift=(" << unscale(inst_shift.x()) << "," << unscale(inst_shift.y()) << ")" << " m_belt_min_z=" << m_belt_min_z << " belt_surface_z=" << belt_surface_z - << " shear_min_z=" << shear_min_z; + << " belt_z_shift=" << belt_z_shift; // Per-volume bbox + get_matrix translation so order/composition is visible. int vi = 0; for (const ModelVolume *mv : this->model_object()->volumes) { if (!mv->is_model_part()) { ++vi; continue; } BoundingBoxf3 vol_bb = mv->mesh().transformed_bounding_box(mv->get_matrix()); - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] vol[" << vi + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] vol[" << vi << "] id=" << mv->id().id << " name='" << mv->name << "'" << " get_matrix.translation=(" << mv->get_matrix().translation().x() << "," << mv->get_matrix().translation().y() << "," << mv->get_matrix().translation().z() << ")" << " object_bbox.min=(" << vol_bb.min.x() << "," << vol_bb.min.y() << "," << vol_bb.min.z() << ")" @@ -1063,46 +1055,15 @@ 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(); - BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_global_xy_correction tid=" << std::this_thread::get_id() + BOOST_LOG_TRIVIAL(trace) << "[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=(" + BOOST_LOG_TRIVIAL(trace) << "Belt preslice_global: correction=(" << c.x() << ", " << c.y() << ", " << c.z() << ")" - << " shear_min_z=" << shear_min_z << " (m_belt_min_z=" << m_belt_min_z << ")"; + << " belt_z_shift=" << belt_z_shift << " (m_belt_min_z=" << m_belt_min_z << ")"; } else { - struct GAxis { BeltShearMode mode; double angle; int from; bool global; }; - GAxis gaxes[3] = { - { pcfg.belt_shear_x.value, pcfg.belt_shear_x_angle.value, int(pcfg.belt_shear_x_from.value), pcfg.belt_shear_x_global.value }, - { pcfg.belt_shear_y.value, pcfg.belt_shear_y_angle.value, int(pcfg.belt_shear_y_from.value), pcfg.belt_shear_y_global.value }, - { pcfg.belt_shear_z.value, pcfg.belt_shear_z_angle.value, int(pcfg.belt_shear_z_from.value), pcfg.belt_shear_z_global.value }, - }; - - // Only the Z-row shear contributes a Z offset from global mode. - // (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) { - // 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; - global_z_offset += c.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 << ")"; - } - // Slicing rotation in global mode: bed-position-dependent Z offset. // For R(α, X): c.z = sin(α)*d.y so objects at different bed-Y // values print at different machine Z values along the inclined belt. @@ -1129,16 +1090,16 @@ void PrintObject::slice() } } - BOOST_LOG_TRIVIAL(warning) << "Belt global: z_offset=" << global_z_offset + BOOST_LOG_TRIVIAL(trace) << "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() + BOOST_LOG_TRIVIAL(trace) << "[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; // [BELT-DEBUG] Final breakdown of all contributions to layer.print_z // and where the first / last layer end up post-adjustment. - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] global_z_offset breakdown" + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] global_z_offset breakdown" << " obj=" << this->model_object()->name - << " shear_min_z=" << shear_min_z + << " belt_z_shift=" << belt_z_shift << " total_global_z_offset=" << global_z_offset << " xy_correction=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y() << ")" << " belt_floor_z_shift_before=" << (m_slicing_params.belt_floor_z_shift) @@ -1152,13 +1113,13 @@ void PrintObject::slice() m_slicing_params.belt_floor_z_shift += global_z_offset; } if (!m_layers.empty()) { - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] post-adjustment" + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] post-adjustment" << " first_layer.print_z=" << m_layers.front()->print_z << " last_layer.print_z=" << m_layers.back()->print_z << " belt_floor_z_shift_after=" << m_slicing_params.belt_floor_z_shift; } if (!m_layers.empty()) { - BOOST_LOG_TRIVIAL(warning) << "Belt global: first_layer_z=" << m_layers.front()->print_z + BOOST_LOG_TRIVIAL(trace) << "Belt global: first_layer_z=" << m_layers.front()->print_z << " last_layer_z=" << m_layers.back()->print_z << " num_layers=" << m_layers.size() << " center_offset=(" << unscale(m_center_offset.x()) @@ -1175,7 +1136,7 @@ void PrintObject::slice() } // BBS - BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] slice EXIT tid=" << std::this_thread::get_id() << " obj=" << this + BOOST_LOG_TRIVIAL(trace) << "[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() << ")"; diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 5663882f1c..fa9acffb50 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -700,11 +700,11 @@ void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& proj shader->start_using(); shader->set_uniform("emission_factor", 0.0f); Transform3d model_matrix = Geometry::assemble_transform(m_model_offset); - // Belt printer: rotate the bed model about X so the belt tilt is visible. - // Negative angle: belt surface tilts downward away from the nozzle. + // Belt printer: rotate the bed model about the tilt axis so the belt tilt + // is visible. Negative angle: belt surface tilts downward away from the nozzle. if (m_is_belt_printer && m_belt_angle > 0.f) { double angle_rad = Geometry::deg2rad(static_cast(m_belt_angle)); - model_matrix = Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX()) * model_matrix; + model_matrix = Eigen::AngleAxisd(-angle_rad, belt_tilt_unit_axis()) * model_matrix; } shader->set_uniform("volume_world_matrix", model_matrix); shader->set_uniform("view_model_matrix", view_matrix * model_matrix); @@ -747,12 +747,10 @@ void Bed3D::render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, co void Bed3D::render_gravity_arrow(const Transform3d& view_matrix, const Transform3d& projection_matrix) { const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config; + // build_plate_tilt_{x,y} are kept in sync with the belt tilt (see TabPrinter), so + // reading them here covers both belt and non-belt tilted printers. double tilt_x_deg = cfg.opt_float("build_plate_tilt_x"); double tilt_y_deg = cfg.opt_float("build_plate_tilt_y"); - // Belt printer: auto-derive gravity direction from belt angle if belt mode is active. - if (m_is_belt_printer && m_belt_angle > 0.f) { - tilt_x_deg = m_belt_angle; - } if (tilt_x_deg == 0. && tilt_y_deg == 0.) { m_gravity_arrow.reset(); return; @@ -819,11 +817,12 @@ void Bed3D::render_slicing_arrow(const Transform3d& view_matrix, const Transform m_slicing_arrow.init_from(stilized_arrow(16, tip_radius, tip_length, stem_radius, stem_length)); } - // The slicing direction: layers stack along the gantry normal. - // With mesh rotation R(-alpha, X), the slicing Z-axis in the original frame - // points in direction R(+alpha, X) * (0, 0, 1) = (0, -sin(alpha), cos(alpha)). + // The slicing direction: layers stack along the gantry normal, i.e. the image of + // +Z under the mesh rotation about the tilt axis. Use the same AngleAxis as the + // slicing pipeline so the arrow matches whichever tilt axis is configured. double angle_rad = Geometry::deg2rad(static_cast(m_belt_angle)); - Vec3d slice_dir = Vec3d(0., -std::sin(angle_rad), std::cos(angle_rad)).normalized(); + Vec3d slice_dir = (Eigen::AngleAxisd(angle_rad, belt_tilt_unit_axis()).toRotationMatrix() + * Vec3d::UnitZ()).normalized(); // Compute rotation to align +Z (arrow default) with slice_dir. Vec3d from = Vec3d::UnitZ(); @@ -895,12 +894,12 @@ void Bed3D::render_slicing_plane(const Transform3d& view_matrix, const Transform shader->start_using(); // Show a tilted plane representing the slicing direction. - // The slicing plane is rotated by belt_angle about X from horizontal. + // The slicing plane is rotated by belt_angle about the tilt axis from horizontal. // Raise it slightly so it's visible above the bed surface. double angle_rad = Geometry::deg2rad(static_cast(m_belt_angle)); Transform3d model_matrix = Transform3d::Identity(); model_matrix.translate(Vec3d(0., 0., 30.)); - model_matrix.rotate(Eigen::AngleAxisd(angle_rad, Vec3d::UnitX())); + model_matrix.rotate(Eigen::AngleAxisd(angle_rad, belt_tilt_unit_axis())); shader->set_uniform("view_model_matrix", view_matrix * model_matrix); shader->set_uniform("projection_matrix", projection_matrix); diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index d01273961d..788d8f52a3 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -125,6 +125,8 @@ private: // Belt printer state for rendering. bool m_is_belt_printer = false; float m_belt_angle = 0.f; + // Tilt axis: 0 = X (belt travels along Y, the common case), 1 = Y. + int m_belt_tilt_axis = 0; public: Bed3D() = default; @@ -146,10 +148,14 @@ public: const BuildVolume& build_volume() const { return m_build_volume; } BuildVolume& build_volume() { return m_build_volume; } - // Belt printer bed settings. - void set_belt_printer(bool enabled, float angle_deg) { m_is_belt_printer = enabled; m_belt_angle = angle_deg; } + // Belt printer bed settings. tilt_axis: 0 = X (belt along Y), 1 = Y. + void set_belt_printer(bool enabled, float angle_deg, int tilt_axis = 0) { + m_is_belt_printer = enabled; m_belt_angle = angle_deg; m_belt_tilt_axis = tilt_axis; + } bool is_belt_printer() const { return m_is_belt_printer; } float belt_angle() const { return m_belt_angle; } + // Unit vector of the tilt axis in bed space. + Vec3d belt_tilt_unit_axis() const { return m_belt_tilt_axis == 1 ? Vec3d::UnitY() : Vec3d::UnitX(); } // Was the model provided, or was it generated procedurally? Type get_type() const { return m_type; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2bd1f5fc8d..c4a38fc12e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -11312,80 +11312,33 @@ void Plater::priv::set_bed_shape(const Pointfs &shape, const auto *belt_opt = config->option("belt_printer"); bool is_belt = belt_opt && belt_opt->value; if (is_belt) { - double belt_angle = config->opt_float("belt_printer_angle"); + // The slicing rotation is the single source of truth for the belt tilt: + // its magnitude is the physical tilt angle and its axis is the tilt axis. + auto rot_axis = config->option>("belt_slice_rotation")->value; + double rot_angle = config->opt_float("belt_slice_rotation_angle"); + double belt_angle = std::abs(rot_angle); // physical tilt magnitude + int tilt_axis = (rot_axis == BeltRotationAxis::Y) ? 1 : 0; bool infinite_y = config->opt_bool("belt_printer_infinite_y"); bed.build_volume().set_belt_printer(true, belt_angle, infinite_y); - bed.set_belt_printer(true, static_cast(belt_angle)); + bed.set_belt_printer(true, static_cast(belt_angle), tilt_axis); if (preview) preview->get_canvas3d()->get_gcode_viewer().set_belt_printer(true, static_cast(belt_angle)); - // Compute the inverse of the full belt shear+scale transform for the G-code viewer. - auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double { - double angle_rad = Geometry::deg2rad(angle_deg); - double sin_a = std::sin(angle_rad); - double cos_a = std::cos(angle_rad); - switch (mode) { - case BeltShearMode::PosCot: return (sin_a > EPSILON) ? cos_a / sin_a : 0.; - case BeltShearMode::NegCot: return (sin_a > EPSILON) ? -cos_a / sin_a : 0.; - case BeltShearMode::PosTan: return (cos_a > EPSILON) ? sin_a / cos_a : 0.; - case BeltShearMode::NegTan: return (cos_a > EPSILON) ? -sin_a / cos_a : 0.; - default: return 0.; - } - }; - auto compute_scale_factor = [](BeltScaleMode mode, double angle_deg) -> double { - if (mode == BeltScaleMode::None) return 1.; - double angle_rad = Geometry::deg2rad(angle_deg); - double sin_a = std::sin(angle_rad); - double cos_a = std::cos(angle_rad); - switch (mode) { - case BeltScaleMode::InvSin: return (sin_a > EPSILON) ? 1. / sin_a : 1.; - case BeltScaleMode::InvCos: return (cos_a > EPSILON) ? 1. / cos_a : 1.; - case BeltScaleMode::Sin: return sin_a; - case BeltScaleMode::Cos: return cos_a; - default: return 1.; - } - }; - - // Read shear configs. - auto get_shear_mode = [this](const char *key) -> BeltShearMode { - auto opt = config->option>(key); - return opt ? opt->value : BeltShearMode::None; - }; - auto get_axis = [this](const char *key) -> BeltAxis { - auto opt = config->option>(key); - return opt ? opt->value : BeltAxis::X; - }; - auto get_scale_mode = [this](const char *key) -> BeltScaleMode { - auto opt = config->option>(key); - return opt ? opt->value : BeltScaleMode::None; - }; - - struct AxisShear { BeltShearMode mode; double angle; int from; }; - AxisShear axes[3] = { - { get_shear_mode("belt_shear_x"), config->opt_float("belt_shear_x_angle"), int(get_axis("belt_shear_x_from")) }, - { get_shear_mode("belt_shear_y"), config->opt_float("belt_shear_y_angle"), int(get_axis("belt_shear_y_from")) }, - { get_shear_mode("belt_shear_z"), config->opt_float("belt_shear_z_angle"), int(get_axis("belt_shear_z_from")) }, - }; - - Transform3d belt_shear = Transform3d::Identity(); - for (int row = 0; row < 3; ++row) { - if (axes[row].mode != BeltShearMode::None) { - double factor = compute_shear_factor(axes[row].mode, axes[row].angle); - if (std::abs(factor) > EPSILON) - belt_shear.matrix()(row, axes[row].from) += factor; + // Compute the inverse of the mesh-side belt transform for the G-code + // viewer. The sole mesh transform is the slicing rotation; build its + // matrix from config and hand the viewer its inverse (rotation is + // orthogonal, so the inverse is the transpose). + Transform3d forward = Transform3d::Identity(); + if (rot_axis != BeltRotationAxis::None && std::abs(rot_angle) > EPSILON) { + Vec3d unit_axis = Vec3d::UnitX(); + switch (rot_axis) { + case BeltRotationAxis::X: unit_axis = Vec3d::UnitX(); break; + case BeltRotationAxis::Y: unit_axis = Vec3d::UnitY(); break; + case BeltRotationAxis::Z: unit_axis = Vec3d::UnitZ(); break; + default: break; } + forward.linear() = Eigen::AngleAxisd(Geometry::deg2rad(rot_angle), unit_axis).toRotationMatrix(); } - - double sx = compute_scale_factor(get_scale_mode("belt_scale_x"), config->opt_float("belt_scale_x_angle")); - double sy = compute_scale_factor(get_scale_mode("belt_scale_y"), config->opt_float("belt_scale_y_angle")); - double sz = compute_scale_factor(get_scale_mode("belt_scale_z"), config->opt_float("belt_scale_z_angle")); - Transform3d belt_scale = Transform3d::Identity(); - belt_scale.matrix()(0, 0) = sx; - belt_scale.matrix()(1, 1) = sy; - belt_scale.matrix()(2, 2) = sz; - - // Forward transform: scale * shear. Inverse for the viewer. - Transform3d forward = belt_scale * belt_shear; Transform3d inverse = forward.inverse(); if (preview) preview->get_canvas3d()->get_gcode_viewer().set_belt_inverse_transform(inverse); @@ -13730,10 +13683,10 @@ void Plater::load_gcode(const wxString& filename) current_print.set_gcode_file_ready(); - // Belt printer: detect belt_printer_angle from loaded G-code header and enable + // Belt printer: detect the belt tilt from the loaded G-code header and enable // belt view mode on the GCodeViewer so the "Show designed view" toggle appears. - if (current_result->belt_printer_angle > 0.f) { - float angle = current_result->belt_printer_angle; + if (current_result->belt_tilt_angle > 0.f) { + float angle = current_result->belt_tilt_angle; p->preview->get_canvas3d()->get_gcode_viewer().set_belt_printer(true, angle); } else { p->preview->get_canvas3d()->get_gcode_viewer().set_belt_printer(false, 0.f); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index fa90d97f54..1126d176c5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4,6 +4,7 @@ #include "PresetHints.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/BeltTransform.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" @@ -4464,68 +4465,22 @@ void TabPrinter::build_fff() // checkbox is hidden when belt_printer is off (see TabPrinter::toggle_options). auto belt_og = page->new_optgroup(L("Belt printer"), L"param_advanced"); belt_og->append_single_option_line("belt_printer"); - belt_og->append_single_option_line("belt_printer_angle"); belt_og->append_single_option_line("belt_printer_infinite_y"); - // Mesh rotate (default belt-side transform): isometric, no distortion. - // Shown above the per-axis shear/scale rows because most users should - // pick rotation; shear/scale is the expert escape hatch. + // Belt tilt: the sole mesh-side transform and the single source of truth for + // the physical tilt (drives bed rendering and support gravity tilt too). + // Isometric rotation, no distortion; the back-transform inverts it before the + // machine-frame remap. { - Line line = { L("Mesh rotate"), - L("Global mesh rotation applied before slicing. Isometric " - "(no distortion); the back-transform inverts it before the " - "machine-frame remap. Default belt transform. Mutually " - "exclusive with per-axis shear/scale in the UI.") }; + Line line = { L("Belt tilt"), + L("Belt tilt axis and angle, applied as a mesh rotation before " + "slicing. Also drives bed rendering and support gravity tilt. " + "Isometric (no distortion); the back-transform inverts it before " + "the machine-frame remap.") }; line.append_option(belt_og->get_option("belt_slice_rotation")); line.append_option(belt_og->get_option("belt_slice_rotation_angle")); line.append_option(belt_og->get_option("belt_slice_rotation_global")); belt_og->append_line(line); } - // Per-axis shear/scale: expert escape hatch for non-rigid belt transforms - // (BlackBelt-style 1/sin scaling, asymmetric belt geometries, etc.). - // Group mode + angle + source on one row per axis. - { - Line line = { L("Mesh shear X"), L("Shear applied to the X axis before slicing") }; - line.append_option(belt_og->get_option("belt_shear_x")); - line.append_option(belt_og->get_option("belt_shear_x_angle")); - line.append_option(belt_og->get_option("belt_shear_x_from")); - line.append_option(belt_og->get_option("belt_shear_x_global")); - belt_og->append_line(line); - } - { - Line line = { L("Mesh shear Y"), L("Shear applied to the Y axis before slicing") }; - line.append_option(belt_og->get_option("belt_shear_y")); - line.append_option(belt_og->get_option("belt_shear_y_angle")); - line.append_option(belt_og->get_option("belt_shear_y_from")); - line.append_option(belt_og->get_option("belt_shear_y_global")); - belt_og->append_line(line); - } - { - Line line = { L("Mesh shear Z"), L("Shear applied to the Z axis before slicing") }; - line.append_option(belt_og->get_option("belt_shear_z")); - line.append_option(belt_og->get_option("belt_shear_z_angle")); - line.append_option(belt_og->get_option("belt_shear_z_from")); - line.append_option(belt_og->get_option("belt_shear_z_global")); - belt_og->append_line(line); - } - { - Line line = { L("Mesh scale X"), L("Scale applied to the X axis before slicing") }; - line.append_option(belt_og->get_option("belt_scale_x")); - line.append_option(belt_og->get_option("belt_scale_x_angle")); - belt_og->append_line(line); - } - { - Line line = { L("Mesh scale Y"), L("Scale applied to the Y axis before slicing") }; - line.append_option(belt_og->get_option("belt_scale_y")); - line.append_option(belt_og->get_option("belt_scale_y_angle")); - belt_og->append_line(line); - } - { - Line line = { L("Mesh scale Z"), L("Scale applied to the Z axis before slicing") }; - line.append_option(belt_og->get_option("belt_scale_z")); - line.append_option(belt_og->get_option("belt_scale_z_angle")); - belt_og->append_line(line); - } - belt_og->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 " @@ -4537,13 +4492,6 @@ void TabPrinter::build_fff() line.append_option(belt_og->get_option("preslice_remap_global")); belt_og->append_line(line); } - { - Line line = { L("G-code axis remap (post-slice)"), L("Remap slicing-frame axes to machine axes in G-code output. Applied AFTER slicing, during G-code generation.") }; - line.append_option(belt_og->get_option("gcode_remap_x")); - line.append_option(belt_og->get_option("gcode_remap_y")); - line.append_option(belt_og->get_option("gcode_remap_z")); - belt_og->append_line(line); - } belt_og->append_single_option_line("belt_preslice_global"); belt_og->append_single_option_line("gcode_back_transform"); { @@ -4588,12 +4536,10 @@ void TabPrinter::build_fff() { auto mf = page->new_optgroup(L("Machine frame transforms"), L"param_advanced"); { - 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")); + Line line = { L("G-code axis remap (post-slice)"), L("Remap slicing-frame axes to machine axes in G-code output. Applied AFTER slicing, during G-code generation.") }; + line.append_option(mf->get_option("gcode_remap_x")); + line.append_option(mf->get_option("gcode_remap_y")); + line.append_option(mf->get_option("gcode_remap_z")); mf->append_line(line); } { @@ -5612,20 +5558,9 @@ void TabPrinter::toggle_options() // Belt printer: show belt-specific settings only when belt_printer is enabled. bool is_belt = m_config->opt_bool("belt_printer"); bool expert_or_above = (m_mode >= comExpert); - toggle_line("belt_printer_angle", is_belt); toggle_line("belt_printer_infinite_y", is_belt); - // Mesh rotate: advanced (visible by default in belt mode). + // Belt tilt: the sole mesh-side belt transform (visible by default in belt mode). toggle_line("belt_slice_rotation", is_belt); - // Mesh shear/scale: expert-only (the rigid rotation above is the - // primary belt transform; shear/scale is reserved for power users - // matching BlackBelt-style 1/sin scale or asymmetric belt geometries). - toggle_line("belt_shear_x", is_belt && expert_or_above); - toggle_line("belt_shear_y", is_belt && expert_or_above); - toggle_line("belt_shear_z", is_belt && expert_or_above); - toggle_line("belt_scale_x", is_belt && expert_or_above); - toggle_line("belt_scale_y", is_belt && expert_or_above); - toggle_line("belt_scale_z", is_belt && expert_or_above); - toggle_line("belt_mesh_transform_order", is_belt && expert_or_above); for (auto el : {"belt_origin_snap_x", "belt_origin_snap_y", "belt_origin_snap_z"}) toggle_line(el, is_belt); @@ -5641,59 +5576,11 @@ void TabPrinter::toggle_options() // preslice_remap_global: superseded by belt_preslice_global toggle_option("preslice_remap_global", is_belt && !belt_global); - // Mutual exclusion (UI only): slicing rotation and per-axis shear/scale are - // alternatives in the printer panel. The pipeline math composes them; this - // just disables the inactive set so users pick one path or the other. + // Rotation is the only mesh-side belt transform. Gray out its angle/global + // sub-options when no rotation axis is selected. auto rot_axis = m_config->option>("belt_slice_rotation")->value; - bool rotation_active = is_belt - && rot_axis != BeltRotationAxis::None - && std::abs(m_config->opt_float("belt_slice_rotation_angle")) > 1e-9; - bool shear_or_scale_active = is_belt && ( - m_config->option>("belt_shear_x")->value != BeltShearMode::None || - m_config->option>("belt_shear_y")->value != BeltShearMode::None || - m_config->option>("belt_shear_z")->value != BeltShearMode::None || - m_config->option>("belt_scale_x")->value != BeltScaleMode::None || - m_config->option>("belt_scale_y")->value != BeltScaleMode::None || - m_config->option>("belt_scale_z")->value != BeltScaleMode::None); - bool allow_shear_scale = !rotation_active; - bool allow_rotation = !shear_or_scale_active; - - // Disable shear/scale mode dropdowns when rotation is active. - 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"}) - toggle_option(el, is_belt && allow_shear_scale); - - // Disable rotation controls when any shear/scale is active. - toggle_option("belt_slice_rotation", is_belt && allow_rotation); - toggle_option("belt_slice_rotation_angle", is_belt && allow_rotation && rot_axis != BeltRotationAxis::None); - toggle_option("belt_slice_rotation_global", is_belt && allow_rotation && rot_axis != BeltRotationAxis::None); - - // Gray out angle/from sub-options when their parent shear/scale mode is None. - // Per-axis globals are superseded when belt_preslice_global is on. - auto sx = m_config->option>("belt_shear_x")->value; - toggle_option("belt_shear_x_angle", is_belt && sx != BeltShearMode::None && allow_shear_scale); - toggle_option("belt_shear_x_from", is_belt && sx != BeltShearMode::None && allow_shear_scale); - toggle_option("belt_shear_x_global", is_belt && sx != BeltShearMode::None && !belt_global && allow_shear_scale); - - auto sy = m_config->option>("belt_shear_y")->value; - toggle_option("belt_shear_y_angle", is_belt && sy != BeltShearMode::None && allow_shear_scale); - toggle_option("belt_shear_y_from", is_belt && sy != BeltShearMode::None && allow_shear_scale); - toggle_option("belt_shear_y_global", is_belt && sy != BeltShearMode::None && !belt_global && allow_shear_scale); - - auto sz = m_config->option>("belt_shear_z")->value; - toggle_option("belt_shear_z_angle", is_belt && sz != BeltShearMode::None && allow_shear_scale); - toggle_option("belt_shear_z_from", is_belt && sz != BeltShearMode::None && allow_shear_scale); - toggle_option("belt_shear_z_global", is_belt && sz != BeltShearMode::None && !belt_global && allow_shear_scale); - - auto scx = m_config->option>("belt_scale_x")->value; - toggle_option("belt_scale_x_angle", is_belt && scx != BeltScaleMode::None && allow_shear_scale); - - auto scy = m_config->option>("belt_scale_y")->value; - toggle_option("belt_scale_y_angle", is_belt && scy != BeltScaleMode::None && allow_shear_scale); - - auto scz = m_config->option>("belt_scale_z")->value; - toggle_option("belt_scale_z_angle", is_belt && scz != BeltScaleMode::None && allow_shear_scale); + toggle_option("belt_slice_rotation_angle", is_belt && rot_axis != BeltRotationAxis::None); + toggle_option("belt_slice_rotation_global", is_belt && rot_axis != BeltRotationAxis::None); // Machine-frame transforms: shown only in belt mode. // Mirror the Advanced/Expert split used for mesh shear/scale. @@ -5703,8 +5590,7 @@ void TabPrinter::toggle_options() toggle_line("gcode_scale_x", is_belt && expert_or_above); toggle_line("gcode_scale_y", is_belt); toggle_line("gcode_scale_z", is_belt && expert_or_above); - for (auto el : {"belt_gcode_transform_order", "post_gcode_remap_x"}) - toggle_line(el, is_belt); + toggle_line("belt_gcode_transform_order", is_belt); auto gsx = m_config->option>("gcode_shear_x")->value; toggle_option("gcode_shear_x_angle", is_belt && gsx != BeltShearMode::None); @@ -5939,21 +5825,29 @@ void TabPrinter::update_fff() m_use_silent_mode = m_config->opt_bool("silent_mode"); } - // Belt printer: auto-sync build_plate_tilt_x to belt_printer_angle when belt mode is active. - // When belt mode is off, reset build_plate_tilt_x to 0 if it was set by belt mode. + // Belt printer: auto-sync build_plate_tilt_{x,y} (which drives support gravity tilt) + // from the belt slicing rotation, the single source of truth for the physical tilt. + // Tilt about X drives tilt_x, tilt about Y drives tilt_y. When belt mode is off, + // reset whichever tilt axis matches a leftover belt value so we don't clobber a + // manually-set tilt on a non-belt tilted printer. if (m_config->opt_bool("belt_printer")) { - double belt_angle = m_config->opt_float("belt_printer_angle"); - if (m_config->opt_float("build_plate_tilt_x") != belt_angle) { - m_config->set_key_value("build_plate_tilt_x", new ConfigOptionFloat(belt_angle)); - } + auto rot_axis = m_config->option>("belt_slice_rotation")->value; + const auto tilt = BeltTransformPipeline::physical_tilt( + rot_axis, m_config->opt_float("belt_slice_rotation_angle")); + if (m_config->opt_float("build_plate_tilt_x") != tilt.tilt_x_deg) + m_config->set_key_value("build_plate_tilt_x", new ConfigOptionFloat(tilt.tilt_x_deg)); + if (m_config->opt_float("build_plate_tilt_y") != tilt.tilt_y_deg) + m_config->set_key_value("build_plate_tilt_y", new ConfigOptionFloat(tilt.tilt_y_deg)); } else { - // Only reset if build_plate_tilt_x matches a typical belt angle (was set by auto-sync). - // Avoid clobbering a manually-set tilt value for non-belt tilted printers. - double current_tilt = m_config->opt_float("build_plate_tilt_x"); - double belt_angle = m_config->opt_float("belt_printer_angle"); - if (current_tilt != 0. && std::abs(current_tilt - belt_angle) < 0.01) { + const auto tilt = BeltTransformPipeline::physical_tilt( + m_config->option>("belt_slice_rotation")->value, + m_config->opt_float("belt_slice_rotation_angle")); + double tx = m_config->opt_float("build_plate_tilt_x"); + double ty = m_config->opt_float("build_plate_tilt_y"); + if (tx != 0. && std::abs(tx - tilt.tilt_x_deg) < 0.01) m_config->set_key_value("build_plate_tilt_x", new ConfigOptionFloat(0.)); - } + if (ty != 0. && std::abs(ty - tilt.tilt_y_deg) < 0.01) + m_config->set_key_value("build_plate_tilt_y", new ConfigOptionFloat(0.)); } toggle_options();