Decouple Slicing From Machine Frame Logic (#21)

* minor logic swap

* first attempt, has a race condition

* fixed the offset issue

* found a solution, I think things work now (at least once I quash this race condition)

* still chasing down race conditions

* add manual shear / scale order strategy swap

* tweak manual shear, fix ui uninitialization crash

* fix z height / g-code desync issue

* fix shear then scale cutoff planes

* getting closer

* fix support termination planes

* fix incorrect offsets in shear-then-scale mode

* test - fix overextrusion due to model/layer scale
This commit is contained in:
Joseph Robertson
2026-05-18 19:01:43 -05:00
committed by GitHub
parent 8fa6a4602b
commit 6a2d690f45
19 changed files with 621 additions and 46 deletions

View File

@@ -1 +0,0 @@
{"sessionId":"597ab29b-cd70-40f4-95d9-fee98b3968ad","pid":1159657,"acquiredAt":1775936463783}

View File

@@ -17,6 +17,7 @@ void BeltGCode::init_belt_writer(Print &print, bool is_bbl_printers)
belt_writer->set_belt_angle(print.config().belt_printer_angle.value); 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. // 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_belt_back_transform(print.config());
belt_writer->set_machine_frame_transform(print.config());
m_writer = std::move(belt_writer); m_writer = std::move(belt_writer);
// Per-axis origin snap config. // Per-axis origin snap config.
@@ -52,12 +53,33 @@ void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print)
file.write_format("; belt_scale_y_angle = %.1f\n", print.config().belt_scale_y_angle.value); file.write_format("; belt_scale_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 = %s\n", full_cfg.opt_serialize("belt_scale_z").c_str());
file.write_format("; belt_scale_z_angle = %.1f\n", print.config().belt_scale_z_angle.value); file.write_format("; belt_scale_z_angle = %.1f\n", print.config().belt_scale_z_angle.value);
file.write_format("; belt_mesh_transform_order = %s\n", full_cfg.opt_serialize("belt_mesh_transform_order").c_str());
// Pre-slice remap configs // 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_x = %s\n", full_cfg.opt_serialize("preslice_remap_x").c_str());
file.write_format("; preslice_remap_y = %s\n", full_cfg.opt_serialize("preslice_remap_y").c_str()); file.write_format("; preslice_remap_y = %s\n", full_cfg.opt_serialize("preslice_remap_y").c_str());
file.write_format("; preslice_remap_z = %s\n", full_cfg.opt_serialize("preslice_remap_z").c_str()); file.write_format("; preslice_remap_z = %s\n", full_cfg.opt_serialize("preslice_remap_z").c_str());
file.write_format("; preslice_remap_global = %d\n", print.config().preslice_remap_global.value ? 1 : 0); file.write_format("; preslice_remap_global = %d\n", print.config().preslice_remap_global.value ? 1 : 0);
file.write_format("; belt_preslice_global = %d\n", print.config().belt_preslice_global.value ? 1 : 0); file.write_format("; belt_preslice_global = %d\n", print.config().belt_preslice_global.value ? 1 : 0);
// Machine-frame transform configs
file.write_format("; gcode_shear_x = %s\n", full_cfg.opt_serialize("gcode_shear_x").c_str());
file.write_format("; gcode_shear_x_angle = %.1f\n", print.config().gcode_shear_x_angle.value);
file.write_format("; gcode_shear_x_from = %s\n", full_cfg.opt_serialize("gcode_shear_x_from").c_str());
file.write_format("; gcode_shear_y = %s\n", full_cfg.opt_serialize("gcode_shear_y").c_str());
file.write_format("; gcode_shear_y_angle = %.1f\n", print.config().gcode_shear_y_angle.value);
file.write_format("; gcode_shear_y_from = %s\n", full_cfg.opt_serialize("gcode_shear_y_from").c_str());
file.write_format("; gcode_shear_z = %s\n", full_cfg.opt_serialize("gcode_shear_z").c_str());
file.write_format("; gcode_shear_z_angle = %.1f\n", print.config().gcode_shear_z_angle.value);
file.write_format("; gcode_shear_z_from = %s\n", full_cfg.opt_serialize("gcode_shear_z_from").c_str());
file.write_format("; gcode_scale_x = %s\n", full_cfg.opt_serialize("gcode_scale_x").c_str());
file.write_format("; gcode_scale_x_angle = %.1f\n", print.config().gcode_scale_x_angle.value);
file.write_format("; gcode_scale_y = %s\n", full_cfg.opt_serialize("gcode_scale_y").c_str());
file.write_format("; gcode_scale_y_angle = %.1f\n", print.config().gcode_scale_y_angle.value);
file.write_format("; gcode_scale_z = %s\n", full_cfg.opt_serialize("gcode_scale_z").c_str());
file.write_format("; gcode_scale_z_angle = %.1f\n", print.config().gcode_scale_z_angle.value);
file.write_format("; belt_gcode_transform_order = %s\n", full_cfg.opt_serialize("belt_gcode_transform_order").c_str());
file.write_format("; post_gcode_remap_x = %s\n", full_cfg.opt_serialize("post_gcode_remap_x").c_str());
file.write_format("; post_gcode_remap_y = %s\n", full_cfg.opt_serialize("post_gcode_remap_y").c_str());
file.write_format("; post_gcode_remap_z = %s\n", full_cfg.opt_serialize("post_gcode_remap_z").c_str());
} }
void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift) void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift)
@@ -67,20 +89,31 @@ void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift)
// back_transform(T * origin) = origin (correct machine position). // back_transform(T * origin) = origin (correct machine position).
// This replaces the bbox-based axis snap with an exact formula. // This replaces the bbox-based axis snap with an exact formula.
// //
// Two flags trigger this path: // Flags that trigger this path:
// belt_preslice_global — full pipeline (scale * shear * remap) is global // belt_preslice_global — full pipeline (scale * shear * remap) is global
// preslice_remap_global — only the pre-slice remap is global // preslice_remap_global — only the pre-slice remap is global
// belt_shear_z_global — Z-row shear treated as global (matches per-axis
// Z-offset added in PrintObjectSlice.cpp)
// The XY origin adjustment uses the FULL forward transform either way, // The XY origin adjustment uses the FULL forward transform either way,
// because the back_transform applied during G-code emission is always the // because the back_transform applied during G-code emission is always the
// inverse of the full pipeline. When only the remap is configured, both // inverse of the full pipeline. Without pre-multiplication under
// flags produce identical math (T == R). // ShearThenScale order with sy != 1, machine_y of bed position cy ends up
// at cy/sy instead of cy, which also leaves the object bottom off the belt
// plane.
bool use_global = m_config.belt_preslice_global.value bool use_global = m_config.belt_preslice_global.value
|| (m_config.preslice_remap_global.value || (m_config.preslice_remap_global.value
&& BeltTransformPipeline::has_preslice_remap(m_config)); && BeltTransformPipeline::has_preslice_remap(m_config))
|| (m_config.belt_shear_z_global.value
&& m_config.belt_shear_z.value != BeltShearMode::None);
if (use_global && m_config.belt_printer.value) { if (use_global && m_config.belt_printer.value) {
auto *belt_writer = dynamic_cast<BeltGCodeWriter*>(m_writer.get()); auto *belt_writer = dynamic_cast<BeltGCodeWriter*>(m_writer.get());
if (belt_writer) { if (belt_writer) {
// Clear snap — not needed with computed corrections // The per-object lift (z_shift_val = max(0, -m_belt_min_z)) added by
// BeltSliceStrategy::apply_to_trafo is already compensated inside
// global_z_offset (via the shear_min_z term in PrintObjectSlice.cpp's
// preslice_global branch). Snap was previously used here for the same
// purpose, but with both active the lift gets subtracted twice. Clear
// any leftover snap state from a prior instance.
for (int a = 0; a < 3; ++a) for (int a = 0; a < 3; ++a)
belt_writer->set_origin_snap(a, false, 0., 0.); belt_writer->set_origin_snap(a, false, 0., 0.);
} }
@@ -125,7 +158,12 @@ void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift)
unscale<double>(inst_shift.y()), unscale<double>(inst_shift.y()),
obj->belt_global_z_offset()); obj->belt_global_z_offset());
// Compute this instance's machine-space bbox min // Compute this instance's bbox min in the Cartesian frame (post back_transform
// + axis_remap, before machine_frame_transform). Using to_cartesian instead of
// to_machine_coords ensures the 8 axis-aligned bbox corners coincide with the
// geometry's extreme points — a property that breaks under shear, which would
// mis-normalize non-cubic shapes (inverted cone, benchy) by their bbox-volume
// corners rather than their actual lowest geometry point.
BoundingBoxf3 bb = obj->model_object()->raw_bounding_box(); BoundingBoxf3 bb = obj->model_object()->raw_bounding_box();
Vec3d mn = bb.min.cast<double>(), mx = bb.max.cast<double>(); Vec3d mn = bb.min.cast<double>(), mx = bb.max.cast<double>();
Vec3d inst_min(std::numeric_limits<double>::max(), Vec3d inst_min(std::numeric_limits<double>::max(),
@@ -135,7 +173,7 @@ void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift)
Vec3d c((i & 1) ? mx.x() : mn.x(), Vec3d c((i & 1) ? mx.x() : mn.x(),
(i & 2) ? mx.y() : mn.y(), (i & 2) ? mx.y() : mn.y(),
(i & 4) ? mx.z() : mn.z()); (i & 4) ? mx.z() : mn.z());
Vec3d mc = belt_writer->to_machine_coords(full * c + shift); Vec3d mc = belt_writer->to_cartesian(full * c + shift);
for (int a = 0; a < 3; ++a) for (int a = 0; a < 3; ++a)
inst_min[a] = std::min(inst_min[a], mc[a]); inst_min[a] = std::min(inst_min[a], mc[a]);
} }

View File

@@ -34,6 +34,11 @@ void BeltGCodeWriter::set_belt_back_transform(const PrintConfig &config)
m_belt_back_transform.init_from_config(config); m_belt_back_transform.init_from_config(config);
} }
void BeltGCodeWriter::set_machine_frame_transform(const PrintConfig &config)
{
m_machine_frame_transform.init_from_config(config);
}
void BeltGCodeWriter::set_origin_snap(int axis, bool enable, double offset, double bbox_min) void BeltGCodeWriter::set_origin_snap(int axis, bool enable, double offset, double bbox_min)
{ {
if (axis >= 0 && axis < 3) { if (axis >= 0 && axis < 3) {
@@ -43,17 +48,26 @@ void BeltGCodeWriter::set_origin_snap(int axis, bool enable, double offset, doub
} }
} }
Vec3d BeltGCodeWriter::to_cartesian(const Vec3d &pos) const
{
// back_transform → axis_remap, no origin_snap, no machine_frame_transform.
return apply_axis_remap(m_belt_back_transform.apply(pos));
}
Vec3d BeltGCodeWriter::to_machine_coords(const Vec3d &pos) const Vec3d BeltGCodeWriter::to_machine_coords(const Vec3d &pos) const
{ {
// Step 1: Undo the shear/scale applied during slicing. // Step 1+2: To Cartesian (back_transform + axis_remap).
Vec3d p = m_belt_back_transform.apply(pos); Vec3d result = to_cartesian(pos);
// Step 2: Apply axis remap (uses inherited base class method). // Step 3: Per-axis origin snap (computed in the Cartesian frame).
Vec3d result = apply_axis_remap(p);
// Step 3: Per-axis origin snap.
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)
if (m_origin_snap[i]) if (m_origin_snap[i])
result[i] -= (m_origin_bbox_min[i] - m_origin_offset[i]); result[i] -= (m_origin_bbox_min[i] - m_origin_offset[i]);
return result; // Step 4: Machine-frame transform (gcode_shear / gcode_scale / post_gcode_remap)
// applied LAST so it acts as a global linear transform on the placed coords.
// Order matters: putting it before origin_snap would feed sheared bbox corners
// into the snap's per-object min calculation, mis-normalizing non-cubic geometries
// (the corners of the original bbox aren't extreme points of the sheared shape).
return m_machine_frame_transform.apply(result);
} }
// ---- Overridden movement methods ------------------------------------------ // ---- Overridden movement methods ------------------------------------------

View File

@@ -2,6 +2,7 @@
#include "GCodeWriter.hpp" #include "GCodeWriter.hpp"
#include "GCode/BeltBackTransform.hpp" #include "GCode/BeltBackTransform.hpp"
#include "GCode/MachineFrameTransform.hpp"
namespace Slic3r { namespace Slic3r {
@@ -21,8 +22,13 @@ public:
void set_belt_angle(double angle_deg); void set_belt_angle(double angle_deg);
bool is_belt_printer() const { return m_belt_angle_rad != 0.; } bool is_belt_printer() const { return m_belt_angle_rad != 0.; }
void set_belt_back_transform(const PrintConfig &config); 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); void set_origin_snap(int axis, bool enable, double offset, double bbox_min);
Vec3d to_machine_coords(const Vec3d &pos) const; Vec3d to_machine_coords(const Vec3d &pos) const;
// back_transform + axis_remap only (no origin_snap, no machine_frame_transform).
// Used by on_set_origin for bbox computation in the Cartesian frame, where
// axis-aligned bbox corners coincide with the geometry's extreme points.
Vec3d to_cartesian(const Vec3d &pos) const;
// First-layer plane: when set to a non-null active evaluator, travel // First-layer plane: when set to a non-null active evaluator, travel
// speed selection consults the plane per-move and uses // speed selection consults the plane per-move and uses
@@ -47,7 +53,8 @@ protected:
private: private:
double m_belt_angle_rad = 0.; double m_belt_angle_rad = 0.;
BeltBackTransform m_belt_back_transform; BeltBackTransform m_belt_back_transform;
MachineFrameTransform m_machine_frame_transform;
bool m_origin_snap[3] = {false, false, false}; bool m_origin_snap[3] = {false, false, false};
double m_origin_offset[3] = {0., 0., 0.}; double m_origin_offset[3] = {0., 0., 0.};
double m_origin_bbox_min[3] = {0., 0., 0.}; double m_origin_bbox_min[3] = {0., 0., 0.};

View File

@@ -1,6 +1,7 @@
#include "BeltSliceStrategy.hpp" #include "BeltSliceStrategy.hpp"
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <thread>
namespace Slic3r { namespace Slic3r {
@@ -15,6 +16,7 @@ BeltSliceStrategy::BeltSliceStrategy(const PrintConfig &config)
{ {
m_shear = BeltTransformPipeline::build_shear_matrix(config, &m_has_shear); m_shear = BeltTransformPipeline::build_shear_matrix(config, &m_has_shear);
m_scale = BeltTransformPipeline::build_scale_matrix(config, &m_has_scale); m_scale = BeltTransformPipeline::build_scale_matrix(config, &m_has_scale);
m_order = config.belt_mesh_transform_order.value;
} }
void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo, void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo,
@@ -22,10 +24,13 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo,
bool has_remap, bool has_remap,
double *out_belt_min_z) const double *out_belt_min_z) const
{ {
// Shear + scale (belt-only; pre-slice remap is handled separately). // ScaleThenShear: applied to a point, scale runs first then shear (m_shear * m_scale).
// ShearThenScale: applied to a point, shear runs first then scale (m_scale * m_shear).
if (m_has_shear || m_has_scale) { if (m_has_shear || m_has_scale) {
Transform3d belt_xform = Transform3d::Identity(); Transform3d belt_xform = Transform3d::Identity();
belt_xform.linear() = m_scale * m_shear; belt_xform.linear() = (m_order == BeltTransformOrder::ScaleThenShear)
? Matrix3d(m_shear * m_scale)
: Matrix3d(m_scale * m_shear);
trafo = belt_xform * trafo; trafo = belt_xform * trafo;
} }
@@ -47,8 +52,12 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo,
z_shift.matrix()(2, 3) = belt_z_shift_val; z_shift.matrix()(2, 3) = belt_z_shift_val;
trafo = z_shift * trafo; trafo = z_shift * trafo;
} }
if (out_belt_min_z) if (out_belt_min_z) {
*out_belt_min_z = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.; double new_val = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.;
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_min_z tid=" << std::this_thread::get_id()
<< " target=" << out_belt_min_z << " old=" << *out_belt_min_z << " new=" << new_val;
*out_belt_min_z = new_val;
}
} }
} }

View File

@@ -34,10 +34,11 @@ public:
private: private:
explicit BeltSliceStrategy(const PrintConfig &config); explicit BeltSliceStrategy(const PrintConfig &config);
bool m_has_shear = false; bool m_has_shear = false;
bool m_has_scale = false; bool m_has_scale = false;
Matrix3d m_shear = Matrix3d::Identity(); Matrix3d m_shear = Matrix3d::Identity();
Matrix3d m_scale = Matrix3d::Identity(); Matrix3d m_scale = Matrix3d::Identity();
BeltTransformOrder m_order = BeltTransformOrder::ScaleThenShear;
}; };
} // namespace Slic3r } // namespace Slic3r

View File

@@ -103,9 +103,15 @@ Transform3d BeltTransformPipeline::build_forward_transform(const PrintConfig &co
bool scale_active = false; bool scale_active = false;
Matrix3d scale = build_scale_matrix(config, &scale_active); Matrix3d scale = build_scale_matrix(config, &scale_active);
// Pipeline: scale * shear * pre_remap // Match the mesh-side ordering selected by belt_mesh_transform_order so
// BeltBackTransform inverts the same composition that BeltSliceStrategy
// applied to the mesh.
// ScaleThenShear: applied to p, scale runs first then shear (shear * scale).
// ShearThenScale: applied to p, shear runs first then scale (scale * shear).
Transform3d combined = Transform3d::Identity(); Transform3d combined = Transform3d::Identity();
combined.linear() = scale * shear; combined.linear() = (config.belt_mesh_transform_order.value == BeltTransformOrder::ScaleThenShear)
? Matrix3d(shear * scale)
: Matrix3d(scale * shear);
combined = combined * pre_remap; combined = combined * pre_remap;
return combined; return combined;
} }
@@ -160,19 +166,30 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
BeltTransformPipeline::BeltHeightResult result; BeltTransformPipeline::BeltHeightResult result;
result.object_height = original_height; result.object_height = original_height;
// Extract Z-axis shear/scale config. // Extract Z-axis shear/scale + per-axis scale + transform order from config.
BeltShearMode z_shear_mode; BeltShearMode z_shear_mode;
double z_shear_angle; double z_shear_angle;
BeltScaleMode z_scale_mode; BeltScaleMode z_scale_mode;
double z_scale_angle; double z_scale_angle;
int z_shear_from; int z_shear_from;
BeltScaleMode from_scale_mode; // scale on the shear's source axis
double from_scale_angle;
BeltTransformOrder order;
if constexpr (std::is_same_v<Config, PrintConfig>) { if constexpr (std::is_same_v<Config, PrintConfig>) {
z_shear_mode = config.belt_shear_z.value; z_shear_mode = config.belt_shear_z.value;
z_shear_angle = config.belt_shear_z_angle.value; z_shear_angle = config.belt_shear_z_angle.value;
z_scale_mode = config.belt_scale_z.value; z_scale_mode = config.belt_scale_z.value;
z_scale_angle = config.belt_scale_z_angle.value; z_scale_angle = config.belt_scale_z_angle.value;
z_shear_from = int(config.belt_shear_z_from.value); z_shear_from = int(config.belt_shear_z_from.value);
order = config.belt_mesh_transform_order.value;
if (z_shear_from == 0) {
from_scale_mode = config.belt_scale_x.value;
from_scale_angle = config.belt_scale_x_angle.value;
} else {
from_scale_mode = config.belt_scale_y.value;
from_scale_angle = config.belt_scale_y_angle.value;
}
} else { } else {
// DynamicPrintConfig path // DynamicPrintConfig path
auto get_shear = [&](const char *key) { auto get_shear = [&](const char *key) {
@@ -191,11 +208,23 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
auto *opt = config.template option<ConfigOptionEnum<BeltAxis>>(key); auto *opt = config.template option<ConfigOptionEnum<BeltAxis>>(key);
return opt ? int(opt->value) : 1; return opt ? int(opt->value) : 1;
}; };
auto get_order = [&](const char *key) {
auto *opt = config.template option<ConfigOptionEnum<BeltTransformOrder>>(key);
return opt ? opt->value : BeltTransformOrder::ScaleThenShear;
};
z_shear_mode = get_shear("belt_shear_z"); z_shear_mode = get_shear("belt_shear_z");
z_shear_angle = get_float("belt_shear_z_angle"); z_shear_angle = get_float("belt_shear_z_angle");
z_scale_mode = get_scale("belt_scale_z"); z_scale_mode = get_scale("belt_scale_z");
z_scale_angle = get_float("belt_scale_z_angle"); z_scale_angle = get_float("belt_scale_z_angle");
z_shear_from = get_axis("belt_shear_z_from"); z_shear_from = get_axis("belt_shear_z_from");
order = get_order("belt_mesh_transform_order");
if (z_shear_from == 0) {
from_scale_mode = get_scale("belt_scale_x");
from_scale_angle = get_float("belt_scale_x_angle");
} else {
from_scale_mode = get_scale("belt_scale_y");
from_scale_angle = get_float("belt_scale_y_angle");
}
} }
bool has_z_shear = z_shear_mode != BeltShearMode::None; bool has_z_shear = z_shear_mode != BeltShearMode::None;
@@ -206,7 +235,8 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
double shear_factor = has_z_shear double shear_factor = has_z_shear
? BeltTransformPipeline::compute_shear_factor(z_shear_mode, z_shear_angle) : 0.; ? BeltTransformPipeline::compute_shear_factor(z_shear_mode, z_shear_angle) : 0.;
double scale_z = BeltTransformPipeline::compute_scale_factor(z_scale_mode, z_scale_angle); double scale_z = BeltTransformPipeline::compute_scale_factor(z_scale_mode, z_scale_angle);
double scale_from = BeltTransformPipeline::compute_scale_factor(from_scale_mode, from_scale_angle);
if (has_z_shear && std::abs(shear_factor) > EPSILON) { if (has_z_shear && std::abs(shear_factor) > EPSILON) {
int from = z_shear_from; int from = z_shear_from;
@@ -214,14 +244,29 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
double max_rz = std::numeric_limits<double>::lowest(); double max_rz = std::numeric_limits<double>::lowest();
for (double vz : {bb.min.z(), bb.max.z()}) for (double vz : {bb.min.z(), bb.max.z()})
for (double vs : {bb.min(from), bb.max(from)}) { for (double vs : {bb.min(from), bb.max(from)}) {
double new_z = scale_z * (vz + shear_factor * vs); // Mesh-frame new_z computed per ordering.
// scale-then-shear: Z_s = sz*Z_m + s_from*tan(α)*from_m
// shear-then-scale: Z_s = sz*(Z_m + tan(α)*from_m)
double new_z = (order == BeltTransformOrder::ScaleThenShear)
? scale_z * vz + scale_from * shear_factor * vs
: scale_z * (vz + shear_factor * vs);
min_rz = std::min(min_rz, new_z); min_rz = std::min(min_rz, new_z);
max_rz = std::max(max_rz, new_z); max_rz = std::max(max_rz, new_z);
} }
result.object_height = max_rz - min_rz; result.object_height = max_rz - min_rz;
result.floor_params.shear_factor = shear_factor; // Effective slicer-frame slope of the belt surface (Z_m=0 line):
result.floor_params.from_axis = from; // scale-then-shear: Z_s = tan(α) * Y_s → slope = tan(α)
result.floor_params.z_shift = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.); // 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 { } else {
result.object_height = original_height * scale_z; result.object_height = original_height * scale_z;
} }

View File

@@ -105,7 +105,9 @@ public:
// Also sets has_scale_out if non-null. // Also sets has_scale_out if non-null.
static Matrix3d build_scale_matrix(const PrintConfig &config, bool *has_scale_out = nullptr); static Matrix3d build_scale_matrix(const PrintConfig &config, bool *has_scale_out = nullptr);
// Combined forward transform: scale * shear * pre_remap. // Combined forward transform. Shear/scale order is selected by
// belt_mesh_transform_order so the result matches what BeltSliceStrategy
// applied to the mesh (BeltBackTransform inverts this).
// Does NOT include the per-object Z-shift. // Does NOT include the per-object Z-shift.
static Transform3d build_forward_transform(const PrintConfig &config); static Transform3d build_forward_transform(const PrintConfig &config);

View File

@@ -221,6 +221,8 @@ set(lisbslic3r_sources
GCode/AvoidCrossingPerimeters.hpp GCode/AvoidCrossingPerimeters.hpp
GCode/BeltBackTransform.cpp GCode/BeltBackTransform.cpp
GCode/BeltBackTransform.hpp GCode/BeltBackTransform.hpp
GCode/MachineFrameTransform.cpp
GCode/MachineFrameTransform.hpp
GCode/ConflictChecker.cpp GCode/ConflictChecker.cpp
GCode/ConflictChecker.hpp GCode/ConflictChecker.hpp
GCode/CoolingBuffer.cpp GCode/CoolingBuffer.cpp

View File

@@ -6462,6 +6462,20 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
auto _mm3_per_mm = path.mm3_per_mm * this->config().print_flow_ratio; auto _mm3_per_mm = path.mm3_per_mm * this->config().print_flow_ratio;
_mm3_per_mm *= filament_flow_ratio; _mm3_per_mm *= filament_flow_ratio;
// Belt printer: compensate for the volume change introduced by the mesh
// forward transform. path.mm3_per_mm is derived from slicer-frame layer
// height × line width, but a slicer-frame slab of volume V maps under the
// back-transform to a machine-frame region of volume V / |det(T)|. When
// det(T) > 1 — e.g. belt_scale_y = 1/cos(α) ≈ 1.155 at α = 30° — the
// machine slab holds less plastic than the slicer thinks it is filling
// and we over-extrude by det(T). Pure shear has det 1, so this is a
// no-op until scale factors deviate from 1.
if (m_config.belt_printer.value) {
double det = std::abs(BeltTransformPipeline::build_forward_transform(m_config).linear().determinant());
if (det > EPSILON)
_mm3_per_mm /= det;
}
if (path.role() == erTopSolidInfill) { if (path.role() == erTopSolidInfill) {
_mm3_per_mm *= m_config.top_solid_infill_flow_ratio; _mm3_per_mm *= m_config.top_solid_infill_flow_ratio;
} else if (path.role() == erBottomSurface) { } else if (path.role() == erBottomSurface) {

View File

@@ -0,0 +1,147 @@
#include "MachineFrameTransform.hpp"
#include "../BeltTransform.hpp"
#include "../BoundingBox.hpp"
namespace Slic3r {
namespace {
// Build the post-gcode axis-remap transform (mirrors BeltTransformPipeline::build_preslice_remap
// but reads post_gcode_remap_* keys). Includes Rev-mode translation derived from build volume.
Transform3d build_post_gcode_remap(const PrintConfig &config)
{
Transform3d remap = Transform3d::Identity();
int rx = int(config.post_gcode_remap_x.value);
int ry = int(config.post_gcode_remap_y.value);
int rz = int(config.post_gcode_remap_z.value);
if (rx == int(RemapAxis::PosX) && ry == int(RemapAxis::PosY) && rz == int(RemapAxis::PosZ))
return remap;
auto remap_column = [](int r) -> Vec3d {
int axis = r % 3;
Vec3d col = Vec3d::Zero();
if (r < 3) col[axis] = 1.0;
else if (r < 6) col[axis] = -1.0;
else col[axis] = -1.0; // Rev: max - pos
return col;
};
Matrix3d lin;
lin.col(0) = remap_column(rx);
lin.col(1) = remap_column(ry);
lin.col(2) = remap_column(rz);
remap.linear() = lin;
if (rx >= 6 || ry >= 6 || rz >= 6) {
BoundingBoxf bbox_bed(config.printable_area.values);
Vec3d vol_max(bbox_bed.max.x(), bbox_bed.max.y(), config.printable_height.value);
Vec3d trans = Vec3d::Zero();
auto add_rev = [&](int r, int out) {
if (r >= 6) trans[out] = vol_max[r % 3];
};
add_rev(rx, 0);
add_rev(ry, 1);
add_rev(rz, 2);
remap.translation() = trans;
}
return remap;
}
// Build the 3x3 shear matrix from gcode_shear_* keys.
Matrix3d build_gcode_shear_matrix(const PrintConfig &config, bool &active)
{
struct AxisShear { BeltShearMode mode; double angle; int from; };
AxisShear axes[3] = {
{ config.gcode_shear_x.value, config.gcode_shear_x_angle.value, int(config.gcode_shear_x_from.value) },
{ config.gcode_shear_y.value, config.gcode_shear_y_angle.value, int(config.gcode_shear_y_from.value) },
{ config.gcode_shear_z.value, config.gcode_shear_z_angle.value, int(config.gcode_shear_z_from.value) },
};
Matrix3d shear = Matrix3d::Identity();
active = false;
for (int row = 0; row < 3; ++row) {
if (axes[row].mode != BeltShearMode::None) {
double factor = BeltTransformPipeline::compute_shear_factor(axes[row].mode, axes[row].angle);
if (std::abs(factor) > EPSILON) {
shear(row, axes[row].from) += factor;
active = true;
}
}
}
return shear;
}
// Build the 3x3 diagonal scale matrix from gcode_scale_* keys.
Matrix3d build_gcode_scale_matrix(const PrintConfig &config, bool &active)
{
double sx = BeltTransformPipeline::compute_scale_factor(config.gcode_scale_x.value, config.gcode_scale_x_angle.value);
double sy = BeltTransformPipeline::compute_scale_factor(config.gcode_scale_y.value, config.gcode_scale_y_angle.value);
double sz = BeltTransformPipeline::compute_scale_factor(config.gcode_scale_z.value, config.gcode_scale_z_angle.value);
active = (std::abs(sx - 1.) > EPSILON ||
std::abs(sy - 1.) > EPSILON ||
std::abs(sz - 1.) > EPSILON);
Matrix3d scale = Matrix3d::Identity();
if (active) {
scale(0, 0) = sx;
scale(1, 1) = sy;
scale(2, 2) = sz;
}
return scale;
}
bool has_post_gcode_remap(const PrintConfig &config)
{
return int(config.post_gcode_remap_x.value) != int(RemapAxis::PosX) ||
int(config.post_gcode_remap_y.value) != int(RemapAxis::PosY) ||
int(config.post_gcode_remap_z.value) != int(RemapAxis::PosZ);
}
} // namespace
bool MachineFrameTransform::init_from_config(const PrintConfig &config)
{
m_active = false;
m_transform = Transform3d::Identity();
if (!config.belt_printer.value)
return false;
Transform3d post_remap = build_post_gcode_remap(config);
bool shear_active = false;
Matrix3d shear = build_gcode_shear_matrix(config, shear_active);
bool scale_active = false;
Matrix3d scale = build_gcode_scale_matrix(config, scale_active);
if (!shear_active && !scale_active && !has_post_gcode_remap(config))
return false;
// Compose per belt_gcode_transform_order:
// ScaleThenShear: applied to p, scale runs first then shear (shear * scale).
// ShearThenScale: applied to p, shear runs first then scale (scale * shear).
Transform3d combined = Transform3d::Identity();
combined.linear() = (config.belt_gcode_transform_order.value == BeltTransformOrder::ScaleThenShear)
? Matrix3d(shear * scale)
: Matrix3d(scale * shear);
combined = combined * post_remap;
if (combined.isApprox(Transform3d::Identity()))
return false;
m_transform = combined;
m_active = true;
return true;
}
Vec3d MachineFrameTransform::apply(const Vec3d &pos) const
{
if (!m_active)
return pos;
return m_transform * pos;
}
} // namespace Slic3r

View File

@@ -0,0 +1,43 @@
#ifndef slic3r_MachineFrameTransform_hpp_
#define slic3r_MachineFrameTransform_hpp_
#include "../libslic3r.h"
#include "../Point.hpp"
#include "../PrintConfig.hpp"
namespace Slic3r {
// Post-stage machine-frame transform for belt printers.
//
// Applied in BeltGCodeWriter::to_machine_coords AFTER the back-transform
// and the existing gcode_remap_* axis remap, and BEFORE per-axis origin snap.
// Maps Cartesian (axis-permuted) G-code coordinates into the printer's
// physical machine frame using a parallel set of options:
// gcode_shear_x/y/z + _angle + _from
// gcode_scale_x/y/z + _angle
// post_gcode_remap_x/y/z
//
// Composition matches the mesh-side pipeline: shear * scale * post_remap.
class MachineFrameTransform
{
public:
MachineFrameTransform() = default;
// Initialize from belt printer config. Returns true if a non-identity
// transform was computed. Inactive when belt_printer is disabled or
// all three sub-stages are identity.
bool init_from_config(const PrintConfig &config);
// Apply the transform to a point. Returns pos unchanged if not active.
Vec3d apply(const Vec3d &pos) const;
bool is_active() const { return m_active; }
private:
bool m_active = false;
Transform3d m_transform = Transform3d::Identity();
};
} // namespace Slic3r
#endif // slic3r_MachineFrameTransform_hpp_

View File

@@ -1326,8 +1326,17 @@ static std::vector<std::string> s_Preset_printer_options {
"belt_shear_y", "belt_shear_y_angle", "belt_shear_y_from", "belt_shear_y_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_shear_z", "belt_shear_z_angle", "belt_shear_z_from", "belt_shear_z_global",
"belt_scale_x", "belt_scale_x_angle", "belt_scale_y", "belt_scale_y_angle", "belt_scale_z", "belt_scale_z_angle", "belt_scale_x", "belt_scale_x_angle", "belt_scale_y", "belt_scale_y_angle", "belt_scale_z", "belt_scale_z_angle",
"belt_mesh_transform_order",
"preslice_remap_x", "preslice_remap_y", "preslice_remap_z", "preslice_remap_global", "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_remap_x", "gcode_remap_y", "gcode_remap_z", "gcode_back_transform",
"gcode_shear_x", "gcode_shear_x_angle", "gcode_shear_x_from",
"gcode_shear_y", "gcode_shear_y_angle", "gcode_shear_y_from",
"gcode_shear_z", "gcode_shear_z_angle", "gcode_shear_z_from",
"gcode_scale_x", "gcode_scale_x_angle",
"gcode_scale_y", "gcode_scale_y_angle",
"gcode_scale_z", "gcode_scale_z_angle",
"belt_gcode_transform_order",
"post_gcode_remap_x", "post_gcode_remap_y", "post_gcode_remap_z",
"belt_preslice_global", "belt_preslice_global",
"first_layer_plane", "first_layer_plane_offset", "first_layer_plane_thickness", "first_layer_plane", "first_layer_plane_offset", "first_layer_plane_thickness",
"belt_origin_snap_x", "belt_origin_offset_x", "belt_origin_snap_x", "belt_origin_offset_x",

View File

@@ -104,6 +104,15 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"gcode_remap_x", "gcode_remap_x",
"gcode_remap_y", "gcode_remap_y",
"gcode_remap_z", "gcode_remap_z",
// Machine-frame transforms (only affect G-code output, not slicing).
"gcode_shear_x", "gcode_shear_x_angle", "gcode_shear_x_from",
"gcode_shear_y", "gcode_shear_y_angle", "gcode_shear_y_from",
"gcode_shear_z", "gcode_shear_z_angle", "gcode_shear_z_from",
"gcode_scale_x", "gcode_scale_x_angle",
"gcode_scale_y", "gcode_scale_y_angle",
"gcode_scale_z", "gcode_scale_z_angle",
"belt_gcode_transform_order",
"post_gcode_remap_x", "post_gcode_remap_y", "post_gcode_remap_z",
"belt_origin_snap_x", "belt_origin_offset_x", "belt_origin_snap_x", "belt_origin_offset_x",
"belt_origin_snap_y", "belt_origin_offset_y", "belt_origin_snap_y", "belt_origin_offset_y",
"belt_origin_snap_z", "belt_origin_offset_z", "belt_origin_snap_z", "belt_origin_offset_z",
@@ -310,6 +319,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "belt_scale_y_angle" || opt_key == "belt_scale_y_angle"
|| opt_key == "belt_scale_z" || opt_key == "belt_scale_z"
|| opt_key == "belt_scale_z_angle" || opt_key == "belt_scale_z_angle"
|| opt_key == "belt_mesh_transform_order"
|| opt_key == "belt_preslice_global"
|| opt_key == "preslice_remap_global"
|| opt_key == "preslice_remap_x" || opt_key == "preslice_remap_x"
|| opt_key == "preslice_remap_y" || opt_key == "preslice_remap_y"
|| opt_key == "preslice_remap_z") { || opt_key == "preslice_remap_z") {

View File

@@ -307,6 +307,12 @@ static t_config_enum_values s_keys_map_BeltAxis {
}; };
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltAxis) CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltAxis)
static t_config_enum_values s_keys_map_BeltTransformOrder {
{ "scale_then_shear", int(BeltTransformOrder::ScaleThenShear) },
{ "shear_then_scale", int(BeltTransformOrder::ShearThenScale) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltTransformOrder)
static t_config_enum_values s_keys_map_BeltScaleMode { static t_config_enum_values s_keys_map_BeltScaleMode {
{ "none", int(BeltScaleMode::None) }, { "none", int(BeltScaleMode::None) },
{ "inv_sin", int(BeltScaleMode::InvSin) }, { "inv_sin", int(BeltScaleMode::InvSin) },
@@ -6320,6 +6326,24 @@ void PrintConfigDef::init_fff_params()
add_belt_scale_mode("belt_scale_z", "Function", BeltScaleMode::None); add_belt_scale_mode("belt_scale_z", "Function", BeltScaleMode::None);
add_belt_scale_angle("belt_scale_z_angle", "Angle"); add_belt_scale_angle("belt_scale_z_angle", "Angle");
auto add_belt_transform_order = [this](const char *key, const char *label, const char *tooltip) {
auto def = this->add(key, coEnum);
def->label = L(label);
def->category = L("Printable space");
def->tooltip = L(tooltip);
def->enum_keys_map = &ConfigOptionEnum<BeltTransformOrder>::get_enum_values();
def->enum_values = {"scale_then_shear", "shear_then_scale"};
def->enum_labels = {L("Scale, then shear"), L("Shear, then scale")};
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<BeltTransformOrder>(BeltTransformOrder::ScaleThenShear));
};
add_belt_transform_order("belt_mesh_transform_order", "Mesh transform order",
"Order in which the mesh shear and scale matrices are composed before slicing. "
"'Scale, then shear' applies scale first and then shear (current default). "
"'Shear, then scale' applies shear first and then scale. The g-code back-transform "
"follows the same order so that it correctly inverts the mesh transform.");
// G-code axis remap with sign // G-code axis remap with sign
auto add_belt_remap = [this](const char *key, const char *label, const char *tooltip, RemapAxis default_axis) { auto add_belt_remap = [this](const char *key, const char *label, const char *tooltip, RemapAxis default_axis) {
auto def = this->add(key, coEnum); auto def = this->add(key, coEnum);
@@ -6367,6 +6391,47 @@ void PrintConfigDef::init_fff_params()
add_belt_remap("gcode_remap_y", "Y", "Which slicing axis maps to machine Y in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosY); add_belt_remap("gcode_remap_y", "Y", "Which slicing axis maps to machine Y in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosY);
add_belt_remap("gcode_remap_z", "Z", "Which slicing axis maps to machine Z in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosZ); add_belt_remap("gcode_remap_z", "Z", "Which slicing axis maps to machine Z in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosZ);
// Machine-frame G-code transforms: applied AFTER back-transform and gcode_remap,
// before per-axis origin snap. Maps Cartesian G-code to the printer's physical machine frame.
add_belt_shear_mode ("gcode_shear_x", "Function", BeltShearMode::None);
add_belt_shear_angle("gcode_shear_x_angle", "Angle");
add_belt_axis_enum ("gcode_shear_x_from", "From", "Source axis for X shear in the machine-frame stage.", BeltAxis::Z);
add_belt_shear_mode ("gcode_shear_y", "Function", BeltShearMode::None);
add_belt_shear_angle("gcode_shear_y_angle", "Angle");
add_belt_axis_enum ("gcode_shear_y_from", "From", "Source axis for Y shear in the machine-frame stage.", BeltAxis::Z);
add_belt_shear_mode ("gcode_shear_z", "Function", BeltShearMode::None);
add_belt_shear_angle("gcode_shear_z_angle", "Angle");
add_belt_axis_enum ("gcode_shear_z_from", "From", "Source axis for Z shear in the machine-frame stage.", BeltAxis::Y);
add_belt_scale_mode ("gcode_scale_x", "Function", BeltScaleMode::None);
add_belt_scale_angle("gcode_scale_x_angle", "Angle");
add_belt_scale_mode ("gcode_scale_y", "Function", BeltScaleMode::None);
add_belt_scale_angle("gcode_scale_y_angle", "Angle");
add_belt_scale_mode ("gcode_scale_z", "Function", BeltScaleMode::None);
add_belt_scale_angle("gcode_scale_z_angle", "Angle");
add_belt_transform_order("belt_gcode_transform_order", "G-code transform order",
"Order in which the machine-frame shear and scale matrices are composed when "
"applied to G-code coordinates. 'Scale, then shear' applies scale first and then "
"shear (current default). 'Shear, then scale' applies shear first and then scale.");
add_belt_remap("post_gcode_remap_x", "X",
"Axis remap in the machine-frame stage. Applied AFTER gcode_remap, "
"to put coordinates into the printer's physical axis labelling. Default +X: no change.",
RemapAxis::PosX);
add_belt_remap("post_gcode_remap_y", "Y",
"Axis remap in the machine-frame stage. Applied AFTER gcode_remap, "
"to put coordinates into the printer's physical axis labelling. Default +Y: no change.",
RemapAxis::PosY);
add_belt_remap("post_gcode_remap_z", "Z",
"Axis remap in the machine-frame stage. Applied AFTER gcode_remap, "
"to put coordinates into the printer's physical axis labelling. Default +Z: no change.",
RemapAxis::PosZ);
def = this->add("gcode_back_transform", coBool); def = this->add("gcode_back_transform", coBool);
def->label = L("G-code back-transform"); def->label = L("G-code back-transform");
def->category = L("Printable space"); def->category = L("Printable space");

View File

@@ -183,6 +183,15 @@ enum class BeltAxis
Z = 2, Z = 2,
}; };
// Order in which the belt shear and scale matrices are composed.
// ScaleThenShear: applied to a point p, the result is shear(scale(p)).
// ShearThenScale: applied to a point p, the result is scale(shear(p)).
enum class BeltTransformOrder
{
ScaleThenShear = 0,
ShearThenScale = 1,
};
enum class RemapAxis enum class RemapAxis
{ {
PosX = 0, PosY = 1, PosZ = 2, PosX = 0, PosY = 1, PosZ = 2,
@@ -571,6 +580,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltShearMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltShearMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltScaleMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltScaleMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltAxis) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltAxis)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltTransformOrder)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(RemapAxis) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(RemapAxis)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportFloorMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportFloorMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportZOffsetMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportZOffsetMode)
@@ -1535,6 +1545,26 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionEnum<RemapAxis>, gcode_remap_x)) ((ConfigOptionEnum<RemapAxis>, gcode_remap_x))
((ConfigOptionEnum<RemapAxis>, gcode_remap_y)) ((ConfigOptionEnum<RemapAxis>, gcode_remap_y))
((ConfigOptionEnum<RemapAxis>, gcode_remap_z)) ((ConfigOptionEnum<RemapAxis>, gcode_remap_z))
((ConfigOptionEnum<BeltShearMode>, gcode_shear_x))
((ConfigOptionFloat, gcode_shear_x_angle))
((ConfigOptionEnum<BeltAxis>, gcode_shear_x_from))
((ConfigOptionEnum<BeltShearMode>, gcode_shear_y))
((ConfigOptionFloat, gcode_shear_y_angle))
((ConfigOptionEnum<BeltAxis>, gcode_shear_y_from))
((ConfigOptionEnum<BeltShearMode>, gcode_shear_z))
((ConfigOptionFloat, gcode_shear_z_angle))
((ConfigOptionEnum<BeltAxis>, gcode_shear_z_from))
((ConfigOptionEnum<BeltScaleMode>, gcode_scale_x))
((ConfigOptionFloat, gcode_scale_x_angle))
((ConfigOptionEnum<BeltScaleMode>, gcode_scale_y))
((ConfigOptionFloat, gcode_scale_y_angle))
((ConfigOptionEnum<BeltScaleMode>, gcode_scale_z))
((ConfigOptionFloat, gcode_scale_z_angle))
((ConfigOptionEnum<BeltTransformOrder>, belt_mesh_transform_order))
((ConfigOptionEnum<BeltTransformOrder>, belt_gcode_transform_order))
((ConfigOptionEnum<RemapAxis>, post_gcode_remap_x))
((ConfigOptionEnum<RemapAxis>, post_gcode_remap_y))
((ConfigOptionEnum<RemapAxis>, post_gcode_remap_z))
((ConfigOptionBool, gcode_back_transform)) ((ConfigOptionBool, gcode_back_transform))
((ConfigOptionBool, belt_preslice_global)) ((ConfigOptionBool, belt_preslice_global))
((ConfigOptionEnum<FirstLayerPlaneMode>, first_layer_plane)) ((ConfigOptionEnum<FirstLayerPlaneMode>, first_layer_plane))

View File

@@ -3,6 +3,8 @@
#include "Point.hpp" #include "Point.hpp"
#include "Print.hpp" #include "Print.hpp"
#include "BeltTransform.hpp" #include "BeltTransform.hpp"
#include <thread>
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "ElephantFootCompensation.hpp" #include "ElephantFootCompensation.hpp"
@@ -425,11 +427,15 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
// 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal). // 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal).
void PrintObject::make_perimeters() void PrintObject::make_perimeters()
{ {
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] make_perimeters request tid=" << std::this_thread::get_id() << " obj=" << this;
// prerequisites // prerequisites
this->slice(); this->slice();
if (! this->set_started(posPerimeters)) if (! this->set_started(posPerimeters)) {
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] make_perimeters SKIP tid=" << std::this_thread::get_id() << " obj=" << this << " (already started/done)";
return; return;
}
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] make_perimeters ENTER tid=" << std::this_thread::get_id() << " obj=" << this;
m_print->set_status(15, L("Generating walls")); m_print->set_status(15, L("Generating walls"));
BOOST_LOG_TRIVIAL(info) << "Generating walls..." << log_memory_info(); BOOST_LOG_TRIVIAL(info) << "Generating walls..." << log_memory_info();
@@ -527,6 +533,7 @@ void PrintObject::make_perimeters()
m_print->throw_if_canceled(); m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] make_perimeters EXIT tid=" << std::this_thread::get_id() << " obj=" << this;
this->set_done(posPerimeters); this->set_done(posPerimeters);
} }
@@ -822,7 +829,9 @@ void PrintObject::detect_overhangs_for_lift()
void PrintObject::generate_support_material() void PrintObject::generate_support_material()
{ {
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] generate_support_material request tid=" << std::this_thread::get_id() << " obj=" << this;
if (this->set_started(posSupportMaterial)) { if (this->set_started(posSupportMaterial)) {
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] generate_support_material ENTER tid=" << std::this_thread::get_id() << " obj=" << this;
this->clear_support_layers(); this->clear_support_layers();
if(!has_support() && !m_print->get_no_check_flag()) { if(!has_support() && !m_print->get_no_check_flag()) {
@@ -863,7 +872,10 @@ void PrintObject::generate_support_material()
this->_generate_support_material(); this->_generate_support_material();
m_print->throw_if_canceled(); m_print->throw_if_canceled();
} }
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] generate_support_material EXIT tid=" << std::this_thread::get_id() << " obj=" << this;
this->set_done(posSupportMaterial); this->set_done(posSupportMaterial);
} else {
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] generate_support_material SKIP tid=" << std::this_thread::get_id() << " obj=" << this << " (already started/done)";
} }
} }

View File

@@ -1,5 +1,6 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <limits> #include <limits>
#include <thread>
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
@@ -837,8 +838,12 @@ void groupingVolumesForBrim(PrintObject* object, LayerPtrs& layers, int firstLay
// Resulting expolygons of layer regions are marked as Internal. // Resulting expolygons of layer regions are marked as Internal.
void PrintObject::slice() void PrintObject::slice()
{ {
if (! this->set_started(posSlice)) BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] slice request tid=" << std::this_thread::get_id() << " obj=" << this;
if (! this->set_started(posSlice)) {
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] slice SKIP tid=" << std::this_thread::get_id() << " obj=" << this << " (already started/done)";
return; return;
}
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] slice ENTER tid=" << std::this_thread::get_id() << " obj=" << this;
//BBS: add flag to reload scene for shell rendering //BBS: add flag to reload scene for shell rendering
m_print->set_status(5, L("Slicing mesh"), PrintBase::SlicingStatus::RELOAD_SCENE); m_print->set_status(5, L("Slicing mesh"), PrintBase::SlicingStatus::RELOAD_SCENE);
std::vector<coordf_t> layer_height_profile; std::vector<coordf_t> layer_height_profile;
@@ -942,11 +947,25 @@ void PrintObject::slice()
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.); Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
Vec3d c = T.linear() * d - d; Vec3d c = T.linear() * d - d;
global_z_offset = c.z(); // Per-object shape contribution: BeltSliceStrategy::apply_to_trafo
// lifts the mesh by max(0, -m_belt_min_z) to keep slicer-frame Z
// above 0. Two objects at the same bed position but different
// m_belt_min_z (e.g. cube vs inverted-cone tip) otherwise end up
// at the same print_z, which causes the inverted-cone tip to
// start on the same layer as the cube's lowest sheared corner.
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
double shear_min_z = m_belt_min_z - belt_surface_z;
global_z_offset = c.z() + shear_min_z;
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_global_xy_correction tid=" << std::this_thread::get_id()
<< " obj=" << this << " old=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y()
<< ") new=(" << c.x() << "," << c.y() << ")";
m_belt_global_xy_correction = Vec2d(c.x(), c.y()); m_belt_global_xy_correction = Vec2d(c.x(), c.y());
BOOST_LOG_TRIVIAL(warning) << "Belt preslice_global: correction=(" BOOST_LOG_TRIVIAL(warning) << "Belt preslice_global: correction=("
<< c.x() << ", " << c.y() << ", " << c.z() << ")"; << c.x() << ", " << c.y() << ", " << c.z() << ")"
<< " shear_min_z=" << shear_min_z << " (m_belt_min_z=" << m_belt_min_z << ")";
} else { } else {
struct GAxis { BeltShearMode mode; double angle; int from; bool global; }; struct GAxis { BeltShearMode mode; double angle; int from; bool global; };
GAxis gaxes[3] = { GAxis gaxes[3] = {
@@ -959,13 +978,27 @@ void PrintObject::slice()
// (X/Y row shears with global would offset X/Y, not Z — not useful here.) // (X/Y row shears with global would offset X/Y, not Z — not useful here.)
const auto &za = gaxes[2]; // Z row const auto &za = gaxes[2]; // Z row
if (za.global && za.mode != BeltShearMode::None && za.from < 2) { if (za.global && za.mode != BeltShearMode::None && za.from < 2) {
double factor = BeltTransformPipeline::compute_shear_factor(za.mode, za.angle); // Use the full forward-transform correction (same formula as
// preslice_global) so the per-bed-position offset matches what
// BeltGCode::on_set_origin's T.linear() pre-multiplication
// expects after back-transform. The simple `cy*tan(α)` form
// is exact only for ScaleThenShear; under ShearThenScale with
// sy != 1 it leaves the object bottom off the belt plane by
// cy*tan(α)*(sy-1)/sy.
Transform3d T = BeltTransformPipeline::build_forward_transform(pcfg);
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
Vec3d c = T.linear() * d - d;
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg) double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.; ? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
double shear_min_z = m_belt_min_z - belt_surface_z; double shear_min_z = m_belt_min_z - belt_surface_z;
Point phys = inst_shift; global_z_offset += c.z() + shear_min_z;
double center_on_axis = (za.from == 0) ? unscale<double>(phys.x()) : unscale<double>(phys.y()); BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_global_xy_correction tid=" << std::this_thread::get_id()
global_z_offset += center_on_axis * factor + shear_min_z; << " obj=" << this << " old=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y()
<< ") new=(" << c.x() << "," << c.y() << ")";
m_belt_global_xy_correction = Vec2d(c.x(), c.y());
BOOST_LOG_TRIVIAL(warning) << "Belt per-axis Z-shear-global: correction=("
<< c.x() << ", " << c.y() << ", " << c.z() << ")"
<< " shear_min_z=" << shear_min_z << " (m_belt_min_z=" << m_belt_min_z << ")";
} }
// Pre-slice remap global mode: when on, the remap accounts for the // Pre-slice remap global mode: when on, the remap accounts for the
@@ -983,6 +1016,8 @@ void PrintObject::slice()
BOOST_LOG_TRIVIAL(warning) << "Belt global: z_offset=" << global_z_offset BOOST_LOG_TRIVIAL(warning) << "Belt global: z_offset=" << global_z_offset
<< " (relative to min across " << this->print()->objects().size() << " objects)"; << " (relative to min across " << this->print()->objects().size() << " objects)";
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_global_z_offset tid=" << std::this_thread::get_id()
<< " obj=" << this << " old=" << m_belt_global_z_offset << " new=" << global_z_offset;
m_belt_global_z_offset = global_z_offset; m_belt_global_z_offset = global_z_offset;
if (std::abs(global_z_offset) > EPSILON) { if (std::abs(global_z_offset) > EPSILON) {
for (Layer *layer : m_layers) for (Layer *layer : m_layers)
@@ -1003,6 +1038,10 @@ void PrintObject::slice()
} }
// BBS // BBS
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] slice EXIT tid=" << std::this_thread::get_id() << " obj=" << this
<< " layers=" << m_layers.size() << " belt_min_z=" << m_belt_min_z
<< " belt_global_z_offset=" << m_belt_global_z_offset
<< " belt_xy=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y() << ")";
this->set_done(posSlice); this->set_done(posSlice);
} }

View File

@@ -4491,6 +4491,7 @@ void TabPrinter::build_fff()
line.append_option(optgroup->get_option("belt_scale_z_angle")); line.append_option(optgroup->get_option("belt_scale_z_angle"));
optgroup->append_line(line); optgroup->append_line(line);
} }
optgroup->append_single_option_line("belt_mesh_transform_order");
{ {
Line line = { L("Pre-slice axis remap"), Line line = { L("Pre-slice axis remap"),
L("Remap model axes before slicing so the slicer's coordinate system matches " L("Remap model axes before slicing so the slicer's coordinate system matches "
@@ -4548,6 +4549,63 @@ void TabPrinter::build_fff()
line.append_option(optgroup->get_option("belt_support_z_offset_mode")); line.append_option(optgroup->get_option("belt_support_z_offset_mode"));
optgroup->append_line(line); optgroup->append_line(line);
} }
// Machine-frame transforms: applied to G-code after back-transform and
// gcode_remap, before per-axis origin snap. Maps Cartesian G-code into
// the printer's physical machine frame.
{
auto mf = page->new_optgroup(L("Machine frame transforms"), L"param_advanced");
{
Line line = { L("G-code shear X"), L("Shear applied to the X axis in the machine-frame stage (after back-transform and gcode_remap).") };
line.append_option(mf->get_option("gcode_shear_x"));
line.append_option(mf->get_option("gcode_shear_x_angle"));
line.append_option(mf->get_option("gcode_shear_x_from"));
mf->append_line(line);
}
{
Line line = { L("G-code shear Y"), L("Shear applied to the Y axis in the machine-frame stage (after back-transform and gcode_remap).") };
line.append_option(mf->get_option("gcode_shear_y"));
line.append_option(mf->get_option("gcode_shear_y_angle"));
line.append_option(mf->get_option("gcode_shear_y_from"));
mf->append_line(line);
}
{
Line line = { L("G-code shear Z"), L("Shear applied to the Z axis in the machine-frame stage (after back-transform and gcode_remap).") };
line.append_option(mf->get_option("gcode_shear_z"));
line.append_option(mf->get_option("gcode_shear_z_angle"));
line.append_option(mf->get_option("gcode_shear_z_from"));
mf->append_line(line);
}
{
Line line = { L("G-code scale X"), L("Scale applied to the X axis in the machine-frame stage.") };
line.append_option(mf->get_option("gcode_scale_x"));
line.append_option(mf->get_option("gcode_scale_x_angle"));
mf->append_line(line);
}
{
Line line = { L("G-code scale Y"), L("Scale applied to the Y axis in the machine-frame stage.") };
line.append_option(mf->get_option("gcode_scale_y"));
line.append_option(mf->get_option("gcode_scale_y_angle"));
mf->append_line(line);
}
{
Line line = { L("G-code scale Z"), L("Scale applied to the Z axis in the machine-frame stage.") };
line.append_option(mf->get_option("gcode_scale_z"));
line.append_option(mf->get_option("gcode_scale_z_angle"));
mf->append_line(line);
}
mf->append_single_option_line("belt_gcode_transform_order");
{
Line line = { L("Post-gcode axis remap"),
L("Axis remap in the machine-frame stage. Applied AFTER gcode_remap, "
"to put coordinates into the printer's physical axis labelling.") };
line.append_option(mf->get_option("post_gcode_remap_x"));
line.append_option(mf->get_option("post_gcode_remap_y"));
line.append_option(mf->get_option("post_gcode_remap_z"));
mf->append_line(line);
}
}
option = optgroup->get_option("thumbnails"); option = optgroup->get_option("thumbnails");
option.opt.full_width = true; option.opt.full_width = true;
optgroup->append_single_option_line(option, "printer_basic_information_advanced#g-code-thumbnails"); optgroup->append_single_option_line(option, "printer_basic_information_advanced#g-code-thumbnails");
@@ -5395,6 +5453,7 @@ void TabPrinter::toggle_options()
toggle_line("belt_printer_infinite_y", is_belt); toggle_line("belt_printer_infinite_y", is_belt);
for (auto el : {"belt_shear_x", "belt_shear_y", "belt_shear_z", for (auto el : {"belt_shear_x", "belt_shear_y", "belt_shear_z",
"belt_scale_x", "belt_scale_y", "belt_scale_z", "belt_scale_x", "belt_scale_y", "belt_scale_z",
"belt_mesh_transform_order",
"belt_origin_snap_x", "belt_origin_snap_y", "belt_origin_snap_z"}) "belt_origin_snap_x", "belt_origin_snap_y", "belt_origin_snap_z"})
toggle_line(el, is_belt); toggle_line(el, is_belt);
@@ -5435,6 +5494,34 @@ void TabPrinter::toggle_options()
auto scz = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_z")->value; auto scz = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_z")->value;
toggle_option("belt_scale_z_angle", is_belt && scz != BeltScaleMode::None); toggle_option("belt_scale_z_angle", is_belt && scz != BeltScaleMode::None);
// Machine-frame transforms: shown only in belt mode.
for (auto el : {"gcode_shear_x", "gcode_shear_y", "gcode_shear_z",
"gcode_scale_x", "gcode_scale_y", "gcode_scale_z",
"belt_gcode_transform_order",
"post_gcode_remap_x"})
toggle_line(el, is_belt);
auto gsx = m_config->option<ConfigOptionEnum<BeltShearMode>>("gcode_shear_x")->value;
toggle_option("gcode_shear_x_angle", is_belt && gsx != BeltShearMode::None);
toggle_option("gcode_shear_x_from", is_belt && gsx != BeltShearMode::None);
auto gsy = m_config->option<ConfigOptionEnum<BeltShearMode>>("gcode_shear_y")->value;
toggle_option("gcode_shear_y_angle", is_belt && gsy != BeltShearMode::None);
toggle_option("gcode_shear_y_from", is_belt && gsy != BeltShearMode::None);
auto gsz = m_config->option<ConfigOptionEnum<BeltShearMode>>("gcode_shear_z")->value;
toggle_option("gcode_shear_z_angle", is_belt && gsz != BeltShearMode::None);
toggle_option("gcode_shear_z_from", is_belt && gsz != BeltShearMode::None);
auto gscx = m_config->option<ConfigOptionEnum<BeltScaleMode>>("gcode_scale_x")->value;
toggle_option("gcode_scale_x_angle", is_belt && gscx != BeltScaleMode::None);
auto gscy = m_config->option<ConfigOptionEnum<BeltScaleMode>>("gcode_scale_y")->value;
toggle_option("gcode_scale_y_angle", is_belt && gscy != BeltScaleMode::None);
auto gscz = m_config->option<ConfigOptionEnum<BeltScaleMode>>("gcode_scale_z")->value;
toggle_option("gcode_scale_z_angle", is_belt && gscz != BeltScaleMode::None);
// Origin snap is superseded by belt_preslice_global // Origin snap is superseded by belt_preslice_global
toggle_option("belt_origin_offset_x", is_belt && m_config->opt_bool("belt_origin_snap_x") && !belt_global); toggle_option("belt_origin_offset_x", is_belt && m_config->opt_bool("belt_origin_snap_x") && !belt_global);
toggle_option("belt_origin_offset_y", is_belt && m_config->opt_bool("belt_origin_snap_y") && !belt_global); toggle_option("belt_origin_offset_y", is_belt && m_config->opt_bool("belt_origin_snap_y") && !belt_global);