Part 2: Replace belt rotation w/ per-axis shear transforms and G-code axis remap

- Replace monolithic belt rotation transform with independent per-axis
    shear controls (mode/angle/source-axis for X, Y, Z) and G-code axis
    remapping, giving full flexibility to match any belt printer's
    coordinate system
  - Remove all rotation mode logic and intermediate type+axes dropdowns,
    simplifying the pipeline to pure shear matrices while preserving the
    default behavior (Y += Z*cot(45deg) with identity remap)
  - Clean up GCodeWriter, GCodeProcessor, and GCodeViewer for the new
    shear-only model; expose 12 new settings in printer UI via
    Tab.cpp/Preset.cpp

Implement belt printer tilted slicing

Implement the core belt slicing pipeline that makes the slicer
tilt-aware:

Step 1: GCodeWriter::to_machine_coords() - R(+alpha, X) rotation
  from slicing frame to machine frame
Step 2: PrintObject - belt-rotated object height calculation
  (y*sin(a) + z*cos(a)) for correct layer count
Step 3: PrintObjectSlice - apply R(-alpha, X) rotation trafo so
  horizontal slice planes correspond to belt-parallel planes,
  with Z-shift computed from model volumes
Step 4: GCodeProcessor - machine-frame preview (no transform needed)
Step 5: 3DBed - rotate bed visualization about X by belt angle

Fix: belt surface IS the build plate, no mesh rotation

Currently still slicing perpendicular to the belt normal.  Need to figure out why.

Fix G-code Z sign: use R(-alpha, X) so Z+ is away from belt

The previous R(+alpha, X) transform produced negative Z values
(-y*sin(a) term dominated). Changed to R(-alpha, X) which gives
machine_z = y*sin(a) + z*cos(a), always positive for points
above the belt surface. Z increases with each layer as expected.

reverting and changing slice methodology

Add pink slicing direction arrow from origin

Shows the effective slicing direction (gantry normal) as a pink
arrow from the origin. Shorter and wider than the gravity arrow.
Direction: R(+alpha, X) * Z = (0, -sin(a), cos(a)), which is
the layer stacking direction in the original mesh frame.

Fix slicing arrow visibility and add raw G-code toggle

- Disable depth test for pink slicing arrow so it renders on top of
  the tilted bed geometry (was being occluded)
- Remove unnecessary 5mm Z-offset from arrow position
- Add m_belt_show_raw toggle to GCodeViewer
- Add "Show raw G-code (slicing frame)" checkbox in legend when
  belt mode is active

Implement to_machine_coords inverse rotation for belt printer G-code

The slicing pipeline rotates the mesh by R(-alpha, X) and shifts Z to
start at 0. The G-code output now undoes this transform via
to_machine_coords: R(+alpha, X) * T(0,0,+z_shift), recovering the
original machine-frame coordinates where Y is horizontal and Z is
vertical.

Changes:
- GCodeWriter: implement to_machine_coords with inverse rotation + Z-shift
- GCodeWriter: add belt_z_shift member and setter/getter
- GCode.cpp: compute Z-shift from print objects (same logic as
  PrintObjectSlice) and pass to writer; write z_shift to G-code header
- GCodeProcessor: parse belt_z_shift from G-code header
- GCodeViewer: store belt_z_shift from processor result

Wire raw G-code toggle to apply slicing-frame view transform

When "Show raw G-code (slicing frame)" is checked in the preview
legend, the view matrix is modified to apply R(-alpha, X) * T(0,0,-z_shift)
to the toolpath rendering. This shows the G-code as it was during
slicing: rotated part with horizontal layers.

Default (unchecked): machine-frame view — upright part with tilted layers.

Remove belt printer placeholder comment from GCodeProcessor

The preview now correctly displays machine-frame G-code with the
optional raw view toggle. No transform is needed in the processor.
This commit is contained in:
harrierpigeon
2026-03-12 14:25:30 -05:00
parent c808653565
commit 501aff7e53
11 changed files with 183 additions and 21 deletions

View File

@@ -8,6 +8,7 @@
#include "Exception.hpp"
#include "ExtrusionEntity.hpp"
#include "EdgeGrid.hpp"
#include "Geometry.hpp"
#include "Geometry/ConvexHull.hpp"
#include "GCode/PrintExtents.hpp"
#include "GCode/Thumbnails.hpp"
@@ -2414,8 +2415,28 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_writer.set_is_bbl_machine(is_bbl_printers);
// Belt printer: initialize coordinate transformation on the writer.
if (print.config().belt_printer.value)
if (print.config().belt_printer.value) {
m_writer.set_belt_angle(print.config().belt_printer_angle.value);
// Compute the Z-shift that was applied during slicing (same logic as PrintObjectSlice.cpp).
// This is needed by to_machine_coords() to undo the slicing transform.
// For multiple objects, use the minimum min_z_rotated across all objects.
double angle_rad = Geometry::deg2rad(print.config().belt_printer_angle.value);
Transform3d belt_rotation = Transform3d::Identity();
belt_rotation.rotate(Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX()));
double global_min_z = std::numeric_limits<double>::max();
for (const PrintObject *obj : print.objects()) {
Transform3d obj_trafo = belt_rotation * obj->trafo_centered();
for (const ModelVolume *mv : obj->model_object()->volumes) {
if (! mv->is_model_part() && ! mv->is_modifier())
continue;
BoundingBoxf3 bb = mv->mesh().bounding_box();
bb = bb.transformed(obj_trafo * mv->get_matrix());
global_min_z = std::min(global_min_z, bb.min.z());
}
}
if (global_min_z != std::numeric_limits<double>::max() && std::abs(global_min_z) > EPSILON)
m_writer.set_belt_z_shift(global_min_z); // typically negative
}
// How many times will be change_layer() called?
// change_layer() in turn increments the progress bar status.
@@ -2507,8 +2528,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// Write information on the generator.
file.write_format("; generated by %s on %s\n", Slic3r::header_slic3r_generated().c_str(), Slic3r::Utils::local_timestamp().c_str());
// Belt printer: embed angle in header for G-code processor detection.
if (print.config().belt_printer.value)
if (print.config().belt_printer.value) {
file.write_format("; belt_printer_angle = %.1f\n", print.config().belt_printer_angle.value);
file.write_format("; belt_z_shift = %.4f\n", m_writer.belt_z_shift());
}
if (is_bbl_printers)
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
//BBS: total layer number

View File

@@ -2597,8 +2597,6 @@ void GCodeProcessor::finalize(bool post_process)
}
}
// Belt printer: preview coordinate transform placeholder (to be implemented in next cycle).
calculate_time(m_result);
// process the time blocks
@@ -3053,6 +3051,13 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
} catch (...) {}
return;
}
// Belt printer Z-shift for raw G-code toggle in preview.
if (boost::starts_with(comment, " belt_z_shift = ")) {
try {
m_result.belt_z_shift = std::stof(std::string(comment.substr(16)));
} catch (...) {}
return;
}
// wipe start tag
if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) {

View File

@@ -227,8 +227,9 @@ class Print;
bool support_traditional_timelapse{true};
float printable_height;
float z_offset;
// Belt printer: angle for coordinate transformation in preview.
// Belt printer: angle and Z-shift for coordinate transformation in preview.
float belt_printer_angle{ 0.f };
float belt_z_shift{ 0.f };
SettingsIds settings_ids;
size_t filaments_count;
bool backtrace_enabled;

View File

@@ -29,8 +29,20 @@ void GCodeWriter::set_belt_angle(double angle_deg)
Vec3d GCodeWriter::to_machine_coords(const Vec3d &pos) const
{
// Belt printer: coordinate transform placeholder (to be implemented in next cycle).
return pos;
if (!is_belt_printer())
return pos;
// Undo the slicing transform to recover machine-frame coordinates.
// Slicing applied: T(0,0,-min_z_rot) * R(-alpha, X) * original_pos
// Inverse: R(+alpha, X) * T(0,0,+min_z_rot) * slicing_pos
//
// First undo the Z-shift, then apply R(+alpha, X).
double sz = pos.z() + m_belt_z_shift; // undo T(0,0,-min_z_rot)
return Vec3d(
pos.x(),
pos.y() * m_belt_cos - sz * m_belt_sin, // R(+alpha, X)
pos.y() * m_belt_sin + sz * m_belt_cos
);
}
bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor)

View File

@@ -127,8 +127,10 @@ public:
// Belt printer: set the belt angle and precompute sin/cos for coordinate transformation.
void set_belt_angle(double angle_deg);
void set_belt_z_shift(double z_shift) { m_belt_z_shift = z_shift; }
double belt_z_shift() const { return m_belt_z_shift; }
bool is_belt_printer() const { return m_belt_angle_rad != 0.; }
// Transform a point from the slicing frame to machine/world coordinates (inverse shear).
// Transform a point from the slicing frame to machine/world coordinates (inverse rotation).
Vec3d to_machine_coords(const Vec3d &pos) const;
// Returns whether this flavor supports separate print and travel acceleration.
@@ -184,10 +186,11 @@ public:
//SoftFever
bool m_is_bbl_printers = false;
// Belt printer coordinate transformation (YZ shear)
// Belt printer coordinate transformation (inverse of slicing rotation)
double m_belt_angle_rad = 0.;
double m_belt_cos = 1.0;
double m_belt_sin = 0.0;
double m_belt_z_shift = 0.; // Z-shift applied during slicing (min_z_rotated, typically negative)
double m_current_speed;
bool m_is_first_layer = true;

View File

@@ -3392,7 +3392,13 @@ void PrintObject::update_slicing_parameters()
// Orca: updated function call for XYZ shrinkage compensation
if (!m_slicing_params.valid) {
coordf_t object_height = this->model_object()->max_z();
// Belt printer: height adjustment placeholder (to be implemented in next cycle).
if (this->print()->config().belt_printer.value) {
// After mesh rotation R(-alpha, X), effective height in the slicing
// direction is y_extent*sin(a) + z_extent*cos(a).
double angle_rad = Geometry::deg2rad(this->print()->config().belt_printer_angle.value);
BoundingBoxf3 bb = this->model_object()->raw_bounding_box();
object_height = bb.size().y() * std::sin(angle_rad) + bb.size().z() * std::cos(angle_rad);
}
m_slicing_params = SlicingParameters::create_from_config(this->print()->config(), m_config, object_height,
this->object_extruders(), this->print()->shrinkage_compensation());
}
@@ -3432,9 +3438,14 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
sort_remove_duplicates(object_extruders);
//FIXME add painting extruders
if (object_max_z <= 0.f)
object_max_z = (float)model_object.raw_bounding_box().size().z();
// Belt printer: height adjustment placeholder (to be implemented in next cycle).
if (object_max_z <= 0.f) {
BoundingBoxf3 bb = model_object.raw_bounding_box();
object_max_z = (float)bb.size().z();
if (print_config.belt_printer.value) {
double angle_rad = Geometry::deg2rad(print_config.belt_printer_angle.value);
object_max_z = (float)(bb.size().y() * std::sin(angle_rad) + bb.size().z() * std::cos(angle_rad));
}
}
return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders, object_shrinkage_compensation);
}

View File

@@ -140,7 +140,25 @@ static std::vector<VolumeSlices> slice_volumes_inner(
params_base.closing_radius = print_object_config.slice_closing_radius.value;
params_base.extra_offset = 0;
params_base.trafo = object_trafo;
// Belt printer: mesh transform placeholder (to be implemented in next cycle).
if (print_config.belt_printer.value) {
double angle_rad = Geometry::deg2rad(print_config.belt_printer_angle.value);
// Rotate mesh by -alpha about X so horizontal slice planes = gantry-parallel planes.
// The gantry (XY) is tilted by belt_printer_angle; this rotation aligns it with horizontal.
Transform3d belt_rotation = Transform3d::Identity();
belt_rotation.rotate(Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX()));
params_base.trafo = belt_rotation * params_base.trafo;
// Compute Z-shift from model_volumes: find min-Z of all rotated meshes
// so the rotated geometry starts at Z=0 (the belt surface in slicing frame).
double min_z_rotated = std::numeric_limits<double>::max();
for (const ModelVolume *mv : model_volumes) {
if (!model_volume_needs_slicing(*mv)) continue;
BoundingBoxf3 bb = mv->mesh().bounding_box();
bb = bb.transformed(params_base.trafo * mv->get_matrix());
min_z_rotated = std::min(min_z_rotated, bb.min.z());
}
if (min_z_rotated != std::numeric_limits<double>::max() && std::abs(min_z_rotated) > EPSILON)
params_base.trafo = Eigen::Translation3d(0, 0, -min_z_rotated) * params_base.trafo;
}
//BBS: 0.0025mm is safe enough to simplify the data to speed slicing up for high-resolution model.
//Also has on influence on arc fitting which has default resolution 0.0125mm.
params_base.resolution = print_config.resolution <= 0.001 ? 0.0f : 0.0025;

View File

@@ -387,17 +387,18 @@ void Bed3D::render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix,
m_model.set_color(m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR);
// Belt printer: bed view transform placeholder (to be implemented in next cycle).
Transform3d belt_view_matrix = view_matrix;
// Belt printer: bed rotation is applied inside render_model() and render_default()
// using m_is_belt_printer and m_belt_angle members.
switch (m_type)
{
case Type::System: { render_system(canvas, belt_view_matrix, projection_matrix, bottom); break; }
case Type::System: { render_system(canvas, view_matrix, projection_matrix, bottom); break; }
default:
case Type::Custom: { render_custom(canvas, belt_view_matrix, projection_matrix, bottom); break; }
case Type::Custom: { render_custom(canvas, view_matrix, projection_matrix, bottom); break; }
}
render_gravity_arrow(view_matrix, projection_matrix);
render_slicing_arrow(view_matrix, projection_matrix);
render_slicing_plane(view_matrix, projection_matrix);
glsafe(::glDisable(GL_DEPTH_TEST));
@@ -698,7 +699,13 @@ void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& proj
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("emission_factor", 0.0f);
const Transform3d model_matrix = Geometry::assemble_transform(m_model_offset);
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.
if (m_is_belt_printer && m_belt_angle > 0.f) {
double angle_rad = Geometry::deg2rad(static_cast<double>(m_belt_angle));
model_matrix = Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX()) * model_matrix;
}
shader->set_uniform("volume_world_matrix", model_matrix);
shader->set_uniform("view_model_matrix", view_matrix * model_matrix);
shader->set_uniform("projection_matrix", projection_matrix);
@@ -798,6 +805,58 @@ void Bed3D::render_gravity_arrow(const Transform3d& view_matrix, const Transform
shader->stop_using();
}
void Bed3D::render_slicing_arrow(const Transform3d& view_matrix, const Transform3d& projection_matrix)
{
if (!m_is_belt_printer || m_belt_angle <= 0.f)
return;
// Build the arrow model: shorter and wider than the gravity arrow.
if (!m_slicing_arrow.is_initialized()) {
const float stem_length = 15.0f; // shorter than gravity arrow (25)
const float stem_radius = 1.0f; // wider than gravity arrow (~0.33)
const float tip_radius = 3.0f; // wider tip
const float tip_length = 5.0f;
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)).
double angle_rad = Geometry::deg2rad(static_cast<double>(m_belt_angle));
Vec3d slice_dir = Vec3d(0., -std::sin(angle_rad), std::cos(angle_rad)).normalized();
// Compute rotation to align +Z (arrow default) with slice_dir.
Vec3d from = Vec3d::UnitZ();
double dot = from.dot(slice_dir);
Transform3d rot = Transform3d::Identity();
if (dot < -0.9999) {
rot = Eigen::AngleAxisd(M_PI, Vec3d::UnitX()) * rot;
} else if (dot < 0.9999) {
Vec3d axis = from.cross(slice_dir).normalized();
double angle = std::acos(std::clamp(dot, -1.0, 1.0));
rot = Eigen::AngleAxisd(angle, axis) * rot;
}
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
// Disable depth test so the arrow is always visible (not occluded by the tilted bed).
glsafe(::glDisable(GL_DEPTH_TEST));
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
Transform3d model_matrix = rot;
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * model_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
m_slicing_arrow.set_color({ 1.0f, 0.2f, 0.6f, 1.0f }); // pink
m_slicing_arrow.render();
shader->stop_using();
glsafe(::glEnable(GL_DEPTH_TEST));
}
void Bed3D::render_slicing_plane(const Transform3d& view_matrix, const Transform3d& projection_matrix)
{
if (!m_is_belt_printer || m_belt_angle <= 0.f)
@@ -863,7 +922,15 @@ void Bed3D::render_default(bool bottom, const Transform3d& view_matrix, const Tr
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("view_model_matrix", view_matrix);
// Belt printer: rotate the default bed about X so the belt tilt is visible.
Transform3d view_model_matrix = view_matrix;
if (m_is_belt_printer && m_belt_angle > 0.f) {
double angle_rad = Geometry::deg2rad(static_cast<double>(m_belt_angle));
Transform3d belt_rotation = Transform3d::Identity();
belt_rotation.rotate(Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX()));
view_model_matrix = view_matrix * belt_rotation;
}
shader->set_uniform("view_model_matrix", view_model_matrix);
shader->set_uniform("projection_matrix", projection_matrix);
glsafe(::glEnable(GL_DEPTH_TEST));

View File

@@ -111,6 +111,7 @@ private:
GLModel m_model;
Vec3d m_model_offset{ Vec3d::Zero() };
GLModel m_gravity_arrow;
GLModel m_slicing_arrow; // Pink arrow showing the effective slicing direction
GLModel m_slicing_plane; // Debug: shows the intended slicing plane direction
Axes m_axes;
@@ -189,6 +190,7 @@ private:
void render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom);
void render_default(bool bottom, const Transform3d& view_matrix, const Transform3d& projection_matrix);
void render_gravity_arrow(const Transform3d& view_matrix, const Transform3d& projection_matrix);
void render_slicing_arrow(const Transform3d& view_matrix, const Transform3d& projection_matrix);
void render_slicing_plane(const Transform3d& view_matrix, const Transform3d& projection_matrix);
// BBS: remove the bed picking logic

View File

@@ -1271,6 +1271,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
m_max_print_height = gcode_result.printable_height;
m_z_offset = gcode_result.z_offset;
m_belt_z_shift = gcode_result.belt_z_shift;
// load_toolpaths(gcode_result, build_volume, exclude_bounding_box);
@@ -2209,7 +2210,16 @@ void GCodeViewer::render_toolpaths()
{
const Camera& camera = wxGetApp().plater()->get_camera();
Matrix4f view = camera.get_view_matrix().matrix().cast<float>();
// Belt view: view matrix transform placeholder (to be implemented in next cycle).
// Belt "raw" view: apply slicing rotation to view matrix so toolpaths appear
// in the slicing frame (rotated part with horizontal layers).
if (m_belt_show_raw && m_belt_view_enabled && m_belt_angle_deg > 0.f) {
double angle_rad = Geometry::deg2rad(static_cast<double>(m_belt_angle_deg));
// Apply R(-alpha, X) * T(0,0,-z_shift) to bring machine coords back to slicing frame.
Transform3d slicing_trafo = Transform3d::Identity();
slicing_trafo.translate(Vec3d(0., 0., -static_cast<double>(m_belt_z_shift)));
slicing_trafo = Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX()) * slicing_trafo;
view = (camera.get_view_matrix() * slicing_trafo).matrix().cast<float>();
}
const libvgcode::Mat4x4 converted_view_matrix = libvgcode::convert(view);
const libvgcode::Mat4x4 converted_projetion_matrix = libvgcode::convert(static_cast<Matrix4f>(camera.get_projection_matrix().matrix().cast<float>()));
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
@@ -4406,6 +4416,14 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
if (m_nozzle_nums > 1 && (m_viewer.get_view_type() == libvgcode::EViewType::Summary || m_viewer.get_view_type() == libvgcode::EViewType::ColorPrint)) // ORCA show only on summary and filament tab
render_legend_color_arr_recommen(window_padding);
// Belt printer: toggle for viewing raw slicing-frame G-code
if (m_belt_view_enabled) {
ImGui::Spacing();
ImGui::Dummy({ window_padding, 0 });
ImGui::SameLine();
ImGui::Checkbox("Show raw G-code (slicing frame)", &m_belt_show_raw);
}
legend_height = ImGui::GetCurrentWindow()->Size.y;
imgui.end();
ImGui::PopStyleColor(7);

View File

@@ -239,6 +239,8 @@ mutable bool m_no_render_path { false };
bool m_belt_view_enabled = false;
float m_belt_angle_deg = 0.f;
float m_belt_z_shift = 0.f;
bool m_belt_show_raw = false; // Toggle: show raw slicing-frame G-code (rotated part)
libvgcode::Viewer m_viewer;
bool m_loaded_as_preview{ false };