Part 3.1: refactor BeltTransform pipeline

add BeltGCodeWriter

add BeltGCode

consolidate changes into shared classes for BeltGcode
This commit is contained in:
harrierpigeon
2026-03-27 17:34:19 -05:00
parent 9bbac19de4
commit 2facaac9e8
24 changed files with 1522 additions and 1229 deletions

119
src/libslic3r/BeltGCode.cpp Normal file
View File

@@ -0,0 +1,119 @@
#include "BeltGCode.hpp"
#include "BeltGCodeWriter.hpp"
#include "BeltTransform.hpp"
#include "Print.hpp"
#include <limits>
namespace Slic3r {
void BeltGCode::init_belt_writer(Print &print, bool is_bbl_printers)
{
if (!print.config().belt_printer.value)
return;
auto belt_writer = std::make_unique<BeltGCodeWriter>();
belt_writer->set_is_bbl_machine(is_bbl_printers);
belt_writer->set_belt_angle(print.config().belt_printer_angle.value);
belt_writer->set_axis_remap(
int(print.config().belt_gcode_remap_x.value),
int(print.config().belt_gcode_remap_y.value),
int(print.config().belt_gcode_remap_z.value));
BoundingBoxf bbox_bed(print.config().printable_area.values);
belt_writer->set_build_volume_max(Vec3d(bbox_bed.max.x(), bbox_bed.max.y(),
print.config().printable_height.value));
belt_writer->set_belt_back_transform(print.config());
m_writer = std::move(belt_writer);
// Per-axis origin snap config.
m_origin_snap[0] = print.config().belt_origin_snap_x.value;
m_origin_snap[1] = print.config().belt_origin_snap_y.value;
m_origin_snap[2] = print.config().belt_origin_snap_z.value;
m_origin_snap_offset[0] = print.config().belt_origin_offset_x.value;
m_origin_snap_offset[1] = print.config().belt_origin_offset_y.value;
m_origin_snap_offset[2] = print.config().belt_origin_offset_z.value;
}
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);
// Pre-slice remap configs
file.write_format("; belt_preslice_remap_x = %s\n", full_cfg.opt_serialize("belt_preslice_remap_x").c_str());
file.write_format("; belt_preslice_remap_y = %s\n", full_cfg.opt_serialize("belt_preslice_remap_y").c_str());
file.write_format("; belt_preslice_remap_z = %s\n", full_cfg.opt_serialize("belt_preslice_remap_z").c_str());
}
void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift)
{
if (!m_origin_snap[0] && !m_origin_snap[1] && !m_origin_snap[2])
return;
auto *belt_writer = dynamic_cast<BeltGCodeWriter*>(m_writer.get());
if (!belt_writer)
return;
// Clear existing snap so to_machine_coords gives raw machine coords for bbox computation.
for (int a = 0; a < 3; ++a)
belt_writer->set_origin_snap(a, false, 0., 0.);
// Reconstruct the belt pipeline transform for this object.
Transform3d belt = BeltTransformPipeline::build_forward_transform(m_config);
// Z-shift
double zs = (obj->belt_min_z() < 0.) ? -obj->belt_min_z() : 0.;
if (zs > 0.) {
Transform3d zsh = Transform3d::Identity();
zsh.matrix()(2, 3) = zs;
belt = zsh * belt;
}
// Full transform: belt * trafo_centered
Transform3d full = belt * obj->trafo_centered();
// Instance shift in slicer space + global Z offset
Vec3d shift(unscale<double>(inst_shift.x()),
unscale<double>(inst_shift.y()),
obj->belt_global_z_offset());
// Compute this instance's machine-space bbox min
BoundingBoxf3 bb = obj->model_object()->raw_bounding_box();
Vec3d mn = bb.min.cast<double>(), mx = bb.max.cast<double>();
Vec3d inst_min(std::numeric_limits<double>::max(),
std::numeric_limits<double>::max(),
std::numeric_limits<double>::max());
for (int i = 0; i < 8; ++i) {
Vec3d c((i & 1) ? mx.x() : mn.x(),
(i & 2) ? mx.y() : mn.y(),
(i & 4) ? mx.z() : mn.z());
Vec3d mc = belt_writer->to_machine_coords(full * c + shift);
for (int a = 0; a < 3; ++a)
inst_min[a] = std::min(inst_min[a], mc[a]);
}
// Update writer snap for each enabled axis
for (int a = 0; a < 3; ++a)
belt_writer->set_origin_snap(a, m_origin_snap[a], m_origin_snap_offset[a], inst_min[a]);
}
} // namespace Slic3r

View File

@@ -0,0 +1,28 @@
#pragma once
#include "GCode.hpp"
namespace Slic3r {
// Belt-printer-specific GCode export.
//
// Inherits from GCode and overrides virtual hooks to:
// - Create a BeltGCodeWriter instead of a plain GCodeWriter
// - Write belt configuration to the G-code header
// - Update per-axis origin snap when switching instances
// - Disable arc fitting (G2/G3 not supported on belt printers)
class BeltGCode : public GCode
{
protected:
void init_belt_writer(Print &print, bool is_bbl_printers) override;
void write_belt_header(GCodeOutputStream &file, const Print &print) override;
void on_set_origin(const PrintObject *obj, const Point &inst_shift) override;
bool should_disable_arc_fitting() const override { return true; }
private:
// Per-axis origin snap config (set during init, used in on_set_origin).
bool m_origin_snap[3] = {false, false, false};
double m_origin_snap_offset[3] = {0., 0., 0.};
};
} // namespace Slic3r

View File

@@ -0,0 +1,243 @@
#include "BeltGCodeWriter.hpp"
#include "Geometry.hpp"
namespace Slic3r {
// ---- Belt configuration ---------------------------------------------------
void BeltGCodeWriter::set_belt_angle(double angle_deg)
{
m_belt_angle_rad = Geometry::deg2rad(angle_deg);
}
void BeltGCodeWriter::set_axis_remap(int rx, int ry, int rz)
{
m_remap_x = rx;
m_remap_y = ry;
m_remap_z = rz;
}
void BeltGCodeWriter::set_build_volume_max(const Vec3d &max)
{
m_build_vol_max = max;
}
void BeltGCodeWriter::set_belt_back_transform(const PrintConfig &config)
{
m_belt_back_transform.init_from_config(config);
}
void BeltGCodeWriter::set_origin_snap(int axis, bool enable, double offset, double bbox_min)
{
if (axis >= 0 && axis < 3) {
m_origin_snap[axis] = enable;
m_origin_offset[axis] = offset;
m_origin_bbox_min[axis] = bbox_min;
}
}
Vec3d BeltGCodeWriter::to_machine_coords(const Vec3d &pos) const
{
// Step 1: Undo the shear/scale applied during slicing.
Vec3d p = m_belt_back_transform.apply(pos);
// Step 2: Apply axis remapping for the machine's coordinate convention.
// BeltRemapAxis: 0-2 = +X/+Y/+Z, 3-5 = -X/-Y/-Z, 6-8 = Rev X/Y/Z
auto remap = [this, &p](int r) -> double {
int axis = r % 3;
if (r < 3) return p[axis];
if (r < 6) return -p[axis];
return m_build_vol_max[axis] - p[axis];
};
Vec3d result = { remap(m_remap_x), remap(m_remap_y), remap(m_remap_z) };
// Per-axis origin snap: shift so bbox min on each enabled axis = offset.
for (int i = 0; i < 3; ++i)
if (m_origin_snap[i])
result[i] -= (m_origin_bbox_min[i] - m_origin_offset[i]);
return result;
}
// ---- Overridden movement methods ------------------------------------------
std::string BeltGCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
{
m_pos(0) = point(0);
m_pos(1) = point(1);
this->set_current_position_clear(true);
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
// Belt printer: transform to machine coordinates (XY travel also needs Z due to YZ rotation)
Vec3d machine = to_machine_coords(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z()));
GCodeG1Formatter w;
w.emit_xyz(machine);
auto speed = m_is_first_layer
? this->config.get_abs_value("initial_layer_travel_speed") : this->config.travel_speed.value;
w.emit_f(speed * 60.0);
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
return w.string();
}
std::string BeltGCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase)
{
// Belt printer: force NormalLift since SpiralLift and SlopeLift compute
// slope angles that don't account for the YZ coordinate rotation.
return GCodeWriter::lazy_lift(LiftType::NormalLift, spiral_vase);
}
std::string BeltGCodeWriter::eager_lift(const LiftType type)
{
// Belt printer: force NormalLift (SpiralLift/SlopeLift don't account for YZ rotation).
return GCodeWriter::eager_lift(LiftType::NormalLift);
}
std::string BeltGCodeWriter::_travel_to_z(double z, const std::string &comment)
{
m_pos(2) = z;
double speed = this->config.travel_speed_z.value;
if (speed == 0.) {
speed = m_is_first_layer ? this->config.get_abs_value("initial_layer_travel_speed")
: this->config.travel_speed.value;
}
// Belt printer: a Z-only move in slicing frame needs to emit both Y and Z in machine coords.
Vec3d machine = to_machine_coords(Vec3d(m_pos.x() - m_x_offset, m_pos.y() - m_y_offset, z));
GCodeG1Formatter w;
w.emit_xyz(machine);
w.emit_f(speed * 60.0);
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
return w.string();
}
std::string BeltGCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment, bool force_no_extrusion)
{
m_pos(0) = point(0);
m_pos(1) = point(1);
if (std::abs(dE) <= std::numeric_limits<double>::epsilon())
force_no_extrusion = true;
if (!force_no_extrusion)
filament()->extrude(dE);
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
// Belt printer: transform and emit XYZ (Y and Z are coupled)
Vec3d machine = to_machine_coords(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z()));
GCodeG1Formatter w;
w.emit_xyz(machine);
if (!force_no_extrusion)
w.emit_e(filament()->E());
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
return w.string();
}
std::string BeltGCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment, bool force_no_extrusion)
{
m_pos = point;
m_lifted = 0;
if (!force_no_extrusion)
filament()->extrude(dE);
Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) };
point_on_plate = to_machine_coords(point_on_plate);
GCodeG1Formatter w;
w.emit_xyz(point_on_plate);
if (!force_no_extrusion)
w.emit_e(filament()->E());
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
return w.string();
}
std::string BeltGCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment, bool force_z)
{
// Belt-specific override of travel_to_xyz.
// Key differences from base:
// 1. All coordinates go through to_machine_coords()
// 2. Always emit full XYZ (can't split XY and Z due to coupling)
// 3. Lift type forced to NormalLift (handled by lazy_lift/eager_lift overrides)
Vec3d dest_point = point;
auto travel_speed =
m_is_first_layer ? this->config.get_abs_value("initial_layer_travel_speed") : this->config.travel_speed.value;
// Handle pending z_hop
if (std::abs(m_to_lift) > EPSILON) {
assert(std::abs(m_lifted) < EPSILON);
if ((!this->is_current_position_clear() || m_pos != dest_point) &&
m_to_lift + m_pos(2) > point(2)) {
m_lifted = m_to_lift + m_pos(2) - point(2);
dest_point(2) = m_to_lift + m_pos(2);
}
m_to_lift = 0.;
std::string slop_move;
Vec3d source = { m_pos(0) - m_x_offset, m_pos(1) - m_y_offset, m_pos(2) };
Vec3d target = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) };
Vec3d delta = target - source;
Vec2d delta_no_z = { delta(0), delta(1) };
if (delta(2) > 0 && delta_no_z.norm() != 0.0f) {
// Belt: SpiralLift and SlopeLift are disabled (lazy_lift forces NormalLift),
// but handle NormalLift and fallthrough.
if (m_to_lift_type == LiftType::SlopeLift &&
this->is_current_position_clear() &&
atan2(delta(2), delta_no_z.norm()) < this->filament()->travel_slope()) {
Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->filament()->travel_slope());
Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source;
slope_top_point = to_machine_coords(slope_top_point);
GCodeG1Formatter w0;
w0.emit_xyz(slope_top_point);
w0.emit_f(travel_speed * 60.0);
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
slop_move = w0.string();
}
else if (m_to_lift_type == LiftType::NormalLift) {
slop_move = _travel_to_z(target.z(), "normal lift Z");
}
}
std::string xy_z_move;
{
Vec3d emit_target = to_machine_coords(target);
GCodeG1Formatter w0;
// Belt mode: always emit full XYZ since Y and Z are coupled
w0.emit_xyz(emit_target);
w0.emit_f(travel_speed * 60.0);
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
xy_z_move = w0.string();
}
m_pos = dest_point;
this->set_current_position_clear(true);
return slop_move + xy_z_move;
}
else if (!force_z && !this->will_move_z(point(2))) {
double nominal_z = m_pos(2) - m_lifted;
m_lifted -= (point(2) - nominal_z);
if (std::abs(m_lifted) < EPSILON)
m_lifted = 0.;
this->set_current_position_clear(true);
return this->travel_to_xy(to_2d(point));
}
else {
m_lifted = 0;
}
Vec3d point_on_plate = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) };
point_on_plate = to_machine_coords(point_on_plate);
// Belt mode: always emit full XYZ
GCodeG1Formatter w;
w.emit_xyz(point_on_plate);
w.emit_f(this->config.travel_speed.value * 60.0);
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
m_pos = dest_point;
this->set_current_position_clear(true);
return w.string();
}
} // namespace Slic3r

View File

@@ -0,0 +1,50 @@
#pragma once
#include "GCodeWriter.hpp"
#include "GCode/BeltBackTransform.hpp"
namespace Slic3r {
// Belt-printer-specific GCode writer.
//
// Inherits from GCodeWriter and overrides movement methods to apply
// coordinate transformation (back-transform, axis remap, origin snap)
// and emit coupled XYZ moves (Y and Z are coupled due to belt tilt).
class BeltGCodeWriter : public GCodeWriter
{
public:
BeltGCodeWriter() : GCodeWriter() {}
// Belt configuration
void set_belt_angle(double angle_deg);
bool is_belt_printer() const { return m_belt_angle_rad != 0.; }
void set_axis_remap(int rx, int ry, int rz);
void set_build_volume_max(const Vec3d &max);
void set_belt_back_transform(const PrintConfig &config);
void set_origin_snap(int axis, bool enable, double offset, double bbox_min);
Vec3d to_machine_coords(const Vec3d &pos) const;
// Overridden movement methods
std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string()) override;
std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string(), bool force_z = false) override;
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false) override;
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false) override;
std::string lazy_lift(LiftType lift_type = LiftType::NormalLift, bool spiral_vase = false) override;
std::string eager_lift(const LiftType type) override;
protected:
std::string _travel_to_z(double z, const std::string &comment) override;
private:
double m_belt_angle_rad = 0.;
int m_remap_x = 0;
int m_remap_y = 1;
int m_remap_z = 2;
Vec3d m_build_vol_max = Vec3d::Zero();
BeltBackTransform m_belt_back_transform;
bool m_origin_snap[3] = {false, false, false};
double m_origin_offset[3] = {0., 0., 0.};
double m_origin_bbox_min[3] = {0., 0., 0.};
};
} // namespace Slic3r

View File

@@ -0,0 +1,62 @@
#include "BeltSliceStrategy.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
std::unique_ptr<BeltSliceStrategy> BeltSliceStrategy::create(const PrintConfig &config)
{
if (!config.belt_printer.value)
return nullptr;
return std::unique_ptr<BeltSliceStrategy>(new BeltSliceStrategy(config));
}
BeltSliceStrategy::BeltSliceStrategy(const PrintConfig &config)
{
m_has_remap = BeltTransformPipeline::has_preslice_remap(config);
if (m_has_remap)
m_pre_remap = BeltTransformPipeline::build_preslice_remap(config);
m_shear = BeltTransformPipeline::build_shear_matrix(config, &m_has_shear);
m_scale = BeltTransformPipeline::build_scale_matrix(config, &m_has_scale);
}
void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo,
const ModelVolumePtrs &model_volumes,
double *out_belt_min_z) const
{
// Step 1: Pre-slice axis remap.
if (m_has_remap)
trafo = m_pre_remap * trafo;
// Step 2: Shear + scale.
if (m_has_shear || m_has_scale) {
Transform3d belt_xform = Transform3d::Identity();
belt_xform.linear() = m_scale * m_shear;
trafo = belt_xform * trafo;
}
// Step 3: Z-shift — detect if mesh clips below build plate after transforms.
if (m_has_remap || m_has_shear || m_has_scale) {
double min_z = std::numeric_limits<double>::max();
for (const ModelVolume *mv : model_volumes) {
if (!mv->is_model_part()) continue;
for (const stl_vertex &v : mv->mesh().its.vertices) {
Vec3d pt = trafo * v.cast<double>();
min_z = std::min(min_z, pt.z());
}
}
double belt_z_shift_val = (min_z < 0. && min_z != std::numeric_limits<double>::max()) ? -min_z : 0.;
BOOST_LOG_TRIVIAL(warning) << "Belt Z-shift: min_z=" << min_z
<< " z_shift=" << belt_z_shift_val;
if (belt_z_shift_val > 0.) {
Transform3d z_shift = Transform3d::Identity();
z_shift.matrix()(2, 3) = belt_z_shift_val;
trafo = z_shift * trafo;
}
if (out_belt_min_z)
*out_belt_min_z = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.;
}
}
} // namespace Slic3r

View File

@@ -0,0 +1,45 @@
#pragma once
#include "libslic3r.h"
#include "Point.hpp"
#include "BeltTransform.hpp"
#include "PrintConfig.hpp"
#include "Model.hpp"
#include <limits>
#include <memory>
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.
class BeltSliceStrategy
{
public:
// Create a strategy if belt_printer is enabled; returns nullptr otherwise.
static std::unique_ptr<BeltSliceStrategy> create(const PrintConfig &config);
// Apply belt transforms to the slicing trafo.
// Modifies trafo in-place: trafo = z_shift * scale * shear * pre_remap * trafo
// Scans all model_part volumes to detect minimum Z and add z-shift if needed.
// Sets *out_belt_min_z to the minimum Z of the mesh after transforms.
void apply_to_trafo(Transform3d &trafo,
const ModelVolumePtrs &model_volumes,
double *out_belt_min_z) const;
private:
explicit BeltSliceStrategy(const PrintConfig &config);
bool m_has_remap = false;
bool m_has_shear = false;
bool m_has_scale = false;
Transform3d m_pre_remap = Transform3d::Identity();
Matrix3d m_shear = Matrix3d::Identity();
Matrix3d m_scale = Matrix3d::Identity();
};
} // namespace Slic3r

View File

@@ -0,0 +1,246 @@
#include "BeltTransform.hpp"
#include "Model.hpp"
#include <limits>
namespace Slic3r {
// ---- Matrix builders ------------------------------------------------------
Transform3d BeltTransformPipeline::build_preslice_remap(const PrintConfig &config)
{
Transform3d pre_remap = Transform3d::Identity();
if (!has_preslice_remap(config))
return pre_remap;
int pre_rx = int(config.belt_preslice_remap_x.value);
int pre_ry = int(config.belt_preslice_remap_y.value);
int pre_rz = int(config.belt_preslice_remap_z.value);
// Each remap value selects a source axis and sign.
auto remap_column = [](int r) -> Vec3d {
int axis = r % 3;
Vec3d col = Vec3d::Zero();
if (r < 3) col[axis] = 1.0; // +axis
else if (r < 6) col[axis] = -1.0; // -axis
else col[axis] = -1.0; // Rev: max - pos = -(pos - max)
return col;
};
Matrix3d remap_lin;
remap_lin.col(0) = remap_column(pre_rx);
remap_lin.col(1) = remap_column(pre_ry);
remap_lin.col(2) = remap_column(pre_rz);
pre_remap.linear() = remap_lin;
// Translation for Rev modes (needs build volume extents).
if (pre_rx >= 6 || pre_ry >= 6 || pre_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 remap_trans = Vec3d::Zero();
auto add_rev = [&](int r, int out) {
if (r >= 6) remap_trans[out] = vol_max[r % 3];
};
add_rev(pre_rx, 0);
add_rev(pre_ry, 1);
add_rev(pre_rz, 2);
pre_remap.translation() = remap_trans;
}
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;
}
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);
// Pipeline: scale * shear * pre_remap
Transform3d combined = Transform3d::Identity();
combined.linear() = scale * shear;
combined = combined * pre_remap;
return combined;
}
// ---- Bounding box remap ---------------------------------------------------
BoundingBoxf3 BeltTransformPipeline::remap_bbox(const BoundingBoxf3 &bb, const PrintConfig &config)
{
int pre_rx = int(config.belt_preslice_remap_x.value);
int pre_ry = int(config.belt_preslice_remap_y.value);
int pre_rz = int(config.belt_preslice_remap_z.value);
if (pre_rx == int(BeltRemapAxis::PosX) &&
pre_ry == int(BeltRemapAxis::PosY) &&
pre_rz == int(BeltRemapAxis::PosZ))
return bb; // Identity remap.
auto remap_coord = [](int r, const Vec3d &v) -> double {
int axis = r % 3;
if (r < 3) return v[axis];
return -v[axis];
};
Vec3d mn = bb.min.cast<double>(), mx = bb.max.cast<double>();
BoundingBoxf3 rbb;
for (int i = 0; i < 8; ++i) {
Vec3d c((i & 1) ? mx.x() : mn.x(),
(i & 2) ? mx.y() : mn.y(),
(i & 4) ? mx.z() : mn.z());
Vec3d rc(remap_coord(pre_rx, c), remap_coord(pre_ry, c), remap_coord(pre_rz, c));
if (i == 0) rbb = BoundingBoxf3(rc, rc);
else rbb.merge(rc);
}
return rbb;
}
BoundingBoxf3 BeltTransformPipeline::remap_bbox(const ModelObject &model_object, const PrintConfig &config)
{
return remap_bbox(model_object.raw_bounding_box(), config);
}
// ---- Belt floor parameters ------------------------------------------------
// Shared implementation for both PrintConfig and DynamicPrintConfig.
// Template avoids duplicating the math for the two config types.
namespace {
template<typename Config>
BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
const Config &config, const BoundingBoxf3 &bb, double original_height)
{
BeltTransformPipeline::BeltHeightResult result;
result.object_height = original_height;
// Extract Z-axis shear/scale config.
BeltShearMode z_shear_mode;
double z_shear_angle;
BeltScaleMode z_scale_mode;
double z_scale_angle;
int z_shear_from;
if constexpr (std::is_same_v<Config, PrintConfig>) {
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);
} else {
// DynamicPrintConfig path
auto get_shear = [&](const char *key) {
auto *opt = config.template option<ConfigOptionEnum<BeltShearMode>>(key);
return opt ? opt->value : BeltShearMode::None;
};
auto get_scale = [&](const char *key) {
auto *opt = config.template option<ConfigOptionEnum<BeltScaleMode>>(key);
return opt ? opt->value : BeltScaleMode::None;
};
auto get_float = [&](const char *key) {
auto *opt = config.template option<ConfigOptionFloat>(key);
return opt ? opt->value : 45.0;
};
auto get_axis = [&](const char *key) {
auto *opt = config.template option<ConfigOptionEnum<BeltAxis>>(key);
return opt ? int(opt->value) : 1;
};
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");
}
bool has_z_shear = z_shear_mode != BeltShearMode::None;
bool has_z_scale = z_scale_mode != BeltScaleMode::None;
if (!has_z_shear && !has_z_scale)
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);
if (has_z_shear && std::abs(shear_factor) > EPSILON) {
int from = z_shear_from;
double min_rz = std::numeric_limits<double>::max();
double max_rz = std::numeric_limits<double>::lowest();
for (double vz : {bb.min.z(), bb.max.z()})
for (double vs : {bb.min(from), bb.max(from)}) {
double new_z = 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;
result.floor_params.shear_factor = shear_factor;
result.floor_params.from_axis = from;
result.floor_params.z_shift = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.);
} else {
result.object_height = original_height * scale_z;
}
return result;
}
} // anonymous namespace
BeltTransformPipeline::BeltHeightResult BeltTransformPipeline::compute_belt_height_and_floor(
const PrintConfig &config, const BoundingBoxf3 &remapped_bbox, double original_height)
{
return compute_belt_height_and_floor_impl(config, remapped_bbox, original_height);
}
BeltTransformPipeline::BeltHeightResult BeltTransformPipeline::compute_belt_height_and_floor(
const DynamicPrintConfig &config, const BoundingBoxf3 &remapped_bbox, double original_height)
{
return compute_belt_height_and_floor_impl(config, remapped_bbox, original_height);
}
} // namespace Slic3r

View File

@@ -0,0 +1,146 @@
#pragma once
#include "libslic3r.h"
#include "Point.hpp"
#include "BoundingBox.hpp"
#include "PrintConfig.hpp"
#include "Geometry.hpp"
#include <cmath>
namespace Slic3r {
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
//
// 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().
class BeltTransformPipeline
{
public:
// ---- Pure math helpers ------------------------------------------------
static double compute_shear_factor(BeltShearMode mode, double angle_deg)
{
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.;
}
}
static double compute_scale_factor(BeltScaleMode mode, double angle_deg)
{
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.;
}
}
// ---- Identity checks --------------------------------------------------
static bool has_preslice_remap(const PrintConfig &config)
{
return int(config.belt_preslice_remap_x.value) != int(BeltRemapAxis::PosX) ||
int(config.belt_preslice_remap_y.value) != int(BeltRemapAxis::PosY) ||
int(config.belt_preslice_remap_z.value) != int(BeltRemapAxis::PosZ);
}
// Overload accepting DynamicPrintConfig (used in static slicing_parameters).
static bool has_preslice_remap(const DynamicPrintConfig &config)
{
auto get_int = [&](const char *key) -> int {
auto *opt = config.option<ConfigOptionEnum<BeltRemapAxis>>(key);
return opt ? int(opt->value) : 0;
};
return get_int("belt_preslice_remap_x") != int(BeltRemapAxis::PosX) ||
get_int("belt_preslice_remap_y") != int(BeltRemapAxis::PosY) ||
get_int("belt_preslice_remap_z") != int(BeltRemapAxis::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;
}
// ---- 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);
// Combined forward transform: scale * shear * pre_remap.
// Does NOT include the per-object Z-shift.
static Transform3d build_forward_transform(const PrintConfig &config);
// ---- Bounding box remap -----------------------------------------------
// Remap a bounding box through the pre-slice axis remap.
// Returns the original bbox if remap is identity.
static BoundingBoxf3 remap_bbox(const BoundingBoxf3 &bb, const PrintConfig &config);
static BoundingBoxf3 remap_bbox(const ModelObject &model_object, const PrintConfig &config);
// ---- Belt floor parameters --------------------------------------------
struct BeltFloorParams {
double shear_factor = 0.0;
int from_axis = 1;
double z_shift = 0.0;
};
// Result of computing belt height + floor params.
struct BeltHeightResult {
double object_height; // Effective object height after shear/scale
BeltFloorParams floor_params;
};
// Compute effective object height and belt floor parameters from config
// and pre-remapped bounding box. original_height is the input height
// (bb.size().z() or model_object.max_z()).
static BeltHeightResult compute_belt_height_and_floor(
const PrintConfig &config, const BoundingBoxf3 &remapped_bbox,
double original_height);
// Overload for DynamicPrintConfig (used by static slicing_parameters).
static BeltHeightResult compute_belt_height_and_floor(
const DynamicPrintConfig &config, const BoundingBoxf3 &remapped_bbox,
double original_height);
};
} // namespace Slic3r

View File

@@ -79,6 +79,14 @@ set(lisbslic3r_sources
BoundingBox.hpp
BridgeDetector.cpp
BridgeDetector.hpp
BeltGCode.cpp
BeltGCode.hpp
BeltGCodeWriter.cpp
BeltGCodeWriter.hpp
BeltSliceStrategy.cpp
BeltSliceStrategy.hpp
BeltTransform.cpp
BeltTransform.hpp
Brim.cpp
BrimEarsPoint.hpp
Brim.hpp
@@ -417,6 +425,8 @@ set(lisbslic3r_sources
SlicingAdaptive.hpp
Slicing.cpp
Slicing.hpp
Support/BeltFloorContext.cpp
Support/BeltFloorContext.hpp
Support/SupportCommon.cpp
Support/SupportCommon.hpp
Support/SupportLayer.hpp

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
#include "libslic3r.h"
#include "ExPolygon.hpp"
#include "GCodeWriter.hpp"
#include "BeltGCodeWriter.hpp"
#include "Layer.hpp"
#include "Point.hpp"
#include "PlaceholderParser.hpp"
@@ -206,16 +207,18 @@ public:
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max())),
// BBS
m_toolchange_count(0),
m_nominal_z(0.)
m_nominal_z(0.),
m_writer(std::make_unique<GCodeWriter>())
{}
~GCode() = default;
virtual ~GCode() = default;
public:
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
void do_export(Print* print, const char* path, GCodeProcessorResult* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
void export_layer_filaments(GCodeProcessorResult* result);
//BBS: set offset for gcode writer
void set_gcode_offset(double x, double y) { m_writer.set_xy_offset(x, y); m_processor.set_xy_offset(x, y);}
void set_gcode_offset(double x, double y) { m_writer->set_xy_offset(x, y); m_processor.set_xy_offset(x, y);}
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
const Vec2d& origin() const { return m_origin; }
@@ -227,8 +230,8 @@ public:
Vec2d point_to_gcode_quantized(const Point& point) const;
const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; }
GCodeWriter& writer() { return m_writer; }
const GCodeWriter& writer() const { return m_writer; }
GCodeWriter& writer() { return *m_writer; }
const GCodeWriter& writer() const { return *m_writer; }
PlaceholderParser& placeholder_parser() { return m_placeholder_parser_integration.parser; }
const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser_integration.parser; }
// Process a template through the placeholder parser, collect error messages to be reported
@@ -248,7 +251,7 @@ public:
std::string travel_to(const Point& point, ExtrusionRole role, std::string comment, double z = DBL_MAX);
bool needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type);
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone);
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); }
std::string unretract() { return m_writer->unlift() + m_writer->unretract(); }
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1);
bool is_BBL_Printer();
WipeTowerType wipe_tower_type();
@@ -300,7 +303,7 @@ public:
}
};
private:
protected:
class GCodeOutputStream {
public:
GCodeOutputStream(FILE *f, GCodeProcessor &processor) : f(f), m_processor(processor) {}
@@ -328,9 +331,17 @@ private:
FILE *f = nullptr;
GCodeProcessor &m_processor;
};
// Virtual hooks for belt printer subclass (BeltGCode).
// No-ops in base GCode; overridden in BeltGCode.
virtual void init_belt_writer(Print &print, bool is_bbl_printers) {}
virtual void write_belt_header(GCodeOutputStream &file, const Print &print) {}
virtual void on_set_origin(const PrintObject *obj, const Point &inst_shift) {}
virtual bool should_disable_arc_fitting() const { return false; }
void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb);
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object);
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object, bool skip_empty_first_layer = false);
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
std::string generate_skirt(const Print &print,
@@ -493,16 +504,11 @@ private:
This affects the input arguments supplied to the extrude*() and travel_to()
methods. */
Vec2d m_origin;
// Per-axis origin snap: shift G-code so each object's bbox min = offset.
bool m_origin_snap[3] = {false, false, false};
double m_origin_snap_offset[3] = {0., 0., 0.};
// Called when switching instances to recompute the writer's snap for this instance.
void update_origin_snap(const PrintObject *obj, const Point &inst_shift);
FullPrintConfig m_config;
DynamicConfig m_calib_config;
// scaled G-code resolution
double m_scaled_resolution;
GCodeWriter m_writer;
std::unique_ptr<GCodeWriter> m_writer;
struct PlaceholderParserIntegration {
void reset();

View File

@@ -1,40 +1,8 @@
#include "BeltBackTransform.hpp"
#include "../Geometry.hpp"
#include <cmath>
#include "../BeltTransform.hpp"
namespace Slic3r {
// Keep in sync with PrintObjectSlice.cpp compute_shear_factor (lines ~147-157).
static double compute_shear_factor(BeltShearMode mode, double angle_deg)
{
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.;
}
}
// Keep in sync with PrintObjectSlice.cpp compute_scale_factor (lines ~180-192).
static double compute_scale_factor(BeltScaleMode mode, double angle_deg)
{
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.;
}
}
bool BeltBackTransform::init_from_config(const PrintConfig &config)
{
m_active = false;
@@ -43,99 +11,19 @@ bool BeltBackTransform::init_from_config(const PrintConfig &config)
if (!config.belt_printer.value || !config.belt_gcode_back_transform.value)
return false;
// --- Pre-slice axis remap (same as PrintObjectSlice.cpp) ---
int pre_rx = int(config.belt_preslice_remap_x.value);
int pre_ry = int(config.belt_preslice_remap_y.value);
int pre_rz = int(config.belt_preslice_remap_z.value);
bool has_preslice_remap = (pre_rx != int(BeltRemapAxis::PosX) ||
pre_ry != int(BeltRemapAxis::PosY) ||
pre_rz != int(BeltRemapAxis::PosZ));
// 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;
if (!has_global_shear && !has_preslice_remap)
if (!has_global_shear && !BeltTransformPipeline::has_preslice_remap(config))
return false;
// Build pre-slice remap matrix.
Transform3d pre_remap = Transform3d::Identity();
if (has_preslice_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 remap_lin;
remap_lin.col(0) = remap_column(pre_rx);
remap_lin.col(1) = remap_column(pre_ry);
remap_lin.col(2) = remap_column(pre_rz);
pre_remap.linear() = remap_lin;
// Rev mode translation (needs build volume extents).
Vec3d remap_trans = Vec3d::Zero();
if (pre_rx >= 6 || pre_ry >= 6 || pre_rz >= 6) {
BoundingBoxf bbox_bed(config.printable_area.values);
Vec3d vol_max(bbox_bed.max.x(), bbox_bed.max.y(),
config.printable_height.value);
auto add_rev = [&](int r, int out) {
if (r >= 6) remap_trans[out] = vol_max[r % 3];
};
add_rev(pre_rx, 0);
add_rev(pre_ry, 1);
add_rev(pre_rz, 2);
}
pre_remap.translation() = remap_trans;
}
// Build per-axis shear matrix (same as PrintObjectSlice.cpp).
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 has_shear = 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;
has_shear = true;
}
}
}
// Build per-axis scale diagonal matrix (same as PrintObjectSlice.cpp).
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);
Matrix3d scale = Matrix3d::Identity();
bool has_scale = (std::abs(sx - 1.) > EPSILON ||
std::abs(sy - 1.) > EPSILON ||
std::abs(sz - 1.) > EPSILON);
if (has_scale) {
scale(0, 0) = sx;
scale(1, 1) = sy;
scale(2, 2) = sz;
}
if (!has_shear && !has_scale && !has_preslice_remap)
// Build the forward pipeline (scale * shear * pre_remap) and store its inverse.
Transform3d forward = BeltTransformPipeline::build_forward_transform(config);
if (forward.isApprox(Transform3d::Identity()))
return false;
// Forward pipeline: scale * shear * pre_remap (same order as PrintObjectSlice.cpp).
Transform3d combined = Transform3d::Identity();
combined.linear() = scale * shear;
combined = combined * pre_remap;
m_inverse = combined.inverse();
m_inverse = forward.inverse();
m_active = true;
return true;
}

View File

@@ -20,59 +20,6 @@ namespace Slic3r {
bool GCodeWriter::full_gcode_comment = true;
void GCodeWriter::set_belt_angle(double angle_deg)
{
m_belt_angle_rad = Geometry::deg2rad(angle_deg);
}
void GCodeWriter::set_axis_remap(int rx, int ry, int rz)
{
m_remap_x = rx;
m_remap_y = ry;
m_remap_z = rz;
}
void GCodeWriter::set_build_volume_max(const Vec3d &max)
{
m_build_vol_max = max;
}
void GCodeWriter::set_belt_back_transform(const PrintConfig &config)
{
m_belt_back_transform.init_from_config(config);
}
void GCodeWriter::set_origin_snap(int axis, bool enable, double offset, double bbox_min)
{
if (axis >= 0 && axis < 3) {
m_origin_snap[axis] = enable;
m_origin_offset[axis] = offset;
m_origin_bbox_min[axis] = bbox_min;
}
}
Vec3d GCodeWriter::to_machine_coords(const Vec3d &pos) const
{
if (!is_belt_printer())
return pos;
// Step 1: Undo the shear/scale applied during slicing.
Vec3d p = m_belt_back_transform.apply(pos);
// Step 2: Apply axis remapping for the machine's coordinate convention.
// BeltRemapAxis: 0-2 = +X/+Y/+Z, 3-5 = -X/-Y/-Z, 6-8 = Rev X/Y/Z
auto remap = [this, &p](int r) -> double {
int axis = r % 3;
if (r < 3) return p[axis];
if (r < 6) return -p[axis];
return m_build_vol_max[axis] - p[axis];
};
Vec3d result = { remap(m_remap_x), remap(m_remap_y), remap(m_remap_z) };
// Per-axis origin snap: shift so bbox min on each enabled axis = offset.
for (int i = 0; i < 3; ++i)
if (m_origin_snap[i])
result[i] -= (m_origin_bbox_min[i] - m_origin_offset[i]);
return result;
}
bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor)
{
return (flavor == gcfRepetier || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware);
@@ -615,13 +562,7 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
GCodeG1Formatter w;
if (is_belt_printer()) {
// Belt printer: transform to machine coordinates (XY travel also needs Z due to YZ rotation)
Vec3d machine = to_machine_coords(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z()));
w.emit_xyz(machine);
} else {
w.emit_xy(point_on_plate);
}
w.emit_xy(point_on_plate);
auto speed = m_is_first_layer
? this->config.get_abs_value("initial_layer_travel_speed") : this->config.travel_speed.value;
w.emit_f(speed * 60.0);
@@ -635,11 +576,6 @@ it will not perform subsequent lifts, even if Z was raised manually
(i.e. with travel_to_z()) and thus _lifted was reduced. */
std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase)
{
// Belt printer: force NormalLift since SpiralLift and SlopeLift compute slope angles
// that don't account for the YZ coordinate rotation.
if (is_belt_printer())
lift_type = LiftType::NormalLift;
// check whether the above/below conditions are met
double target_lift = 0;
{
@@ -668,8 +604,7 @@ std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase)
// BBS: immediately execute an undelayed lift move with a spiral lift pattern
// designed specifically for subsequent gcode injection (e.g. timelapse)
std::string GCodeWriter::eager_lift(const LiftType type) {
// Belt printer: force NormalLift (SpiralLift/SlopeLift don't account for YZ rotation).
const LiftType effective_type = is_belt_printer() ? LiftType::NormalLift : type;
const LiftType effective_type = type;
std::string lift_move;
double target_lift = 0;
{
@@ -756,8 +691,6 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
// / to make the z list early to avoid to hit some warping place when travel is long.
Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->filament()->travel_slope());
Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source;
if (is_belt_printer())
slope_top_point = to_machine_coords(slope_top_point);
GCodeG1Formatter w0;
w0.emit_xyz(slope_top_point);
w0.emit_f(travel_speed * 60.0);
@@ -772,27 +705,18 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
std::string xy_z_move;
{
Vec3d emit_target = is_belt_printer() ? to_machine_coords(target) : target;
GCodeG1Formatter w0;
if (this->is_current_position_clear()) {
w0.emit_xyz(emit_target);
w0.emit_xyz(target);
w0.emit_f(travel_speed * 60.0);
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
xy_z_move = w0.string();
}
else {
if (is_belt_printer()) {
// Belt mode: can't split XY and Z moves independently, emit full XYZ
w0.emit_xyz(emit_target);
w0.emit_f(travel_speed * 60.0);
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
xy_z_move = w0.string();
} else {
w0.emit_xy(Vec2d(target.x(), target.y()));
w0.emit_f(travel_speed * 60.0);
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
xy_z_move = w0.string() + _travel_to_z(target.z(), comment);
}
w0.emit_xy(Vec2d(target.x(), target.y()));
w0.emit_f(travel_speed * 60.0);
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
xy_z_move = w0.string() + _travel_to_z(target.z(), comment);
}
}
m_pos = dest_point;
@@ -818,25 +742,15 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
//BBS: take plate offset into consider
Vec3d point_on_plate = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) };
if (is_belt_printer())
point_on_plate = to_machine_coords(point_on_plate);
std::string out_string;
GCodeG1Formatter w;
if (!this->is_current_position_clear())
{
if (is_belt_printer()) {
// Belt mode: emit full XYZ since Y and Z are coupled
w.emit_xyz(point_on_plate);
w.emit_f(this->config.travel_speed.value * 60.0);
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
out_string = w.string();
} else {
//force to move xy first then z after filament change
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
w.emit_f(this->config.travel_speed.value * 60.0);
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
out_string = w.string() + _travel_to_z(point_on_plate.z(), comment);
}
//force to move xy first then z after filament change
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
w.emit_f(this->config.travel_speed.value * 60.0);
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
out_string = w.string() + _travel_to_z(point_on_plate.z(), comment);
} else {
GCodeG1Formatter w;
w.emit_xyz(point_on_plate);
@@ -880,13 +794,7 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
}
GCodeG1Formatter w;
if (is_belt_printer()) {
// Belt printer: a Z-only move in slicing frame needs to emit both Y and Z in machine coords.
Vec3d machine = to_machine_coords(Vec3d(m_pos.x() - m_x_offset, m_pos.y() - m_y_offset, z));
w.emit_xyz(machine);
} else {
w.emit_z(z);
}
w.emit_z(z);
w.emit_f(speed * 60.0);
//BBS
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
@@ -991,12 +899,7 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
GCodeG1Formatter w;
if (is_belt_printer()) {
Vec3d machine = to_machine_coords(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z()));
w.emit_xyz(machine);
} else {
w.emit_xy(point_on_plate);
}
w.emit_xy(point_on_plate);
if (!force_no_extrusion)
w.emit_e(filament()->E());
//BBS
@@ -1037,8 +940,6 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) };
GCodeG1Formatter w;
if (is_belt_printer())
point_on_plate = to_machine_coords(point_on_plate);
w.emit_xyz(point_on_plate);
if (!force_no_extrusion)
w.emit_e(filament()->E());

View File

@@ -8,12 +8,11 @@
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "GCode/CoolingBuffer.hpp"
#include "GCode/BeltBackTransform.hpp"
namespace Slic3r {
class GCodeWriter {
public:
virtual ~GCodeWriter() = default;
GCodeConfig config;
bool multiple_extruders;
@@ -74,21 +73,21 @@ public:
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string());
// SoftFever NOTE: the returned speed is mm/minute
double get_current_speed() const { return m_current_speed;}
std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string(), bool force_z = false);
virtual std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
virtual std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string(), bool force_z = false);
std::string travel_to_z(double z, const std::string &comment = std::string(), bool force = false);
bool will_move_z(double z) const;
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
virtual std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
//BBS: generate G2 or G3 extrude which moves by arc
std::string extrude_arc_to_xy(const Vec2d &point, const Vec2d &center_offset, double dE, const bool is_ccw, const std::string &comment = std::string(), bool force_no_extrusion = false);
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
virtual std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
std::string retract(bool before_wipe = false, double retract_length = 0);
std::string retract_for_toolchange(bool before_wipe = false, double retract_length = 0);
std::string unretract();
// do lift instantly
std::string eager_lift(const LiftType type);
virtual std::string eager_lift(const LiftType type);
// record a lift request, do realy lift in next travel
std::string lazy_lift(LiftType lift_type = LiftType::NormalLift, bool spiral_vase = false);
virtual std::string lazy_lift(LiftType lift_type = LiftType::NormalLift, bool spiral_vase = false);
std::string unlift();
const Vec3d& get_position() const { return m_pos; }
Vec3d& get_position() { return m_pos; }
@@ -126,23 +125,23 @@ public:
void set_is_first_layer(bool bval) { m_is_first_layer = bval; }
GCodeFlavor get_gcode_flavor() const { return config.gcode_flavor; }
// Belt printer: set the belt angle and precompute sin/cos for coordinate transformation.
void set_belt_angle(double angle_deg);
bool is_belt_printer() const { return m_belt_angle_rad != 0.; }
// Set axis remap for G-code output (BeltRemapAxis enum values).
void set_axis_remap(int rx, int ry, int rz);
// Set build volume extents for Rev remap mode (max X, Y, Z).
void set_build_volume_max(const Vec3d &max);
// Initialize the belt back-transform that undoes slicing shear/scale.
void set_belt_back_transform(const PrintConfig &config);
// Set per-axis origin snap: shifts G-code so bbox min on this axis = offset.
void set_origin_snap(int axis, bool enable, double offset, double bbox_min);
// Transform a point from the slicing frame to machine coordinates.
Vec3d to_machine_coords(const Vec3d &pos) const;
// Returns whether this flavor supports separate print and travel acceleration.
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
private:
protected:
// Position/lift/offset state — accessible to subclasses (e.g. BeltGCodeWriter)
Vec3d m_pos = Vec3d::Zero();
double m_x_offset{ 0 };
double m_y_offset{ 0 };
double m_lifted;
double m_to_lift;
LiftType m_to_lift_type;
bool m_is_first_layer = true;
bool m_is_current_pos_clear = false;
double m_current_speed;
virtual std::string _travel_to_z(double z, const std::string &comment);
private:
// Extruders are sorted by their ID, so that binary search is possible.
std::vector<Extruder> m_filament_extruders;
bool m_single_extruder_multi_material;
@@ -170,48 +169,21 @@ public:
unsigned int m_last_additional_fan_speed;
int m_last_bed_temperature;
bool m_last_bed_temperature_reached;
double m_lifted;
// BBS
double m_to_lift;
LiftType m_to_lift_type;
Vec3d m_pos = Vec3d::Zero();
//BBS: this flag is used to indicate whether the m_pos is real.
//A example that of the first move, the m_pos is zero, but the real position of extruder doesn't
//Pos must be clear after the first xyz travel move
bool m_is_current_pos_clear = false;
//BBS: x, y offset for gcode generated
double m_x_offset{ 0 };
double m_y_offset{ 0 };
// Orca: slicing resolution in mm
double m_resolution = 0.01;
std::string m_gcode_label_objects_start;
std::string m_gcode_label_objects_end;
//SoftFever
bool m_is_bbl_printers = false;
// Belt printer state
double m_belt_angle_rad = 0.;
int m_remap_x = 0;
int m_remap_y = 1;
int m_remap_z = 2;
Vec3d m_build_vol_max = Vec3d::Zero();
BeltBackTransform m_belt_back_transform;
bool m_origin_snap[3] = {false, false, false};
double m_origin_offset[3] = {0., 0., 0.}; // target coord for bbox min
double m_origin_bbox_min[3] = {0., 0., 0.}; // computed bbox min in machine space
double m_current_speed;
bool m_is_first_layer = true;
enum class Acceleration {
Travel,
Print
};
std::string _travel_to_z(double z, const std::string &comment);
std::string _spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment);
std::string _retract(double length, double restart_extra, const std::string &comment);
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);

View File

@@ -12,6 +12,7 @@
#include "Thread.hpp"
#include "Time.hpp"
#include "GCode.hpp"
#include "BeltGCode.hpp"
#include "GCode/WipeTower.hpp"
#include "GCode/WipeTower2.hpp"
#include "Utils.hpp"
@@ -2551,12 +2552,17 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
this->set_status(80, message);
// The following line may die for multiple reasons.
GCode gcode;
// Factory: use BeltGCode for belt printers, plain GCode otherwise.
std::unique_ptr<GCode> gcode;
if (m_config.belt_printer.value)
gcode = std::make_unique<BeltGCode>();
else
gcode = std::make_unique<GCode>();
//BBS: compute plate offset for gcode-generator
const Vec3d origin = this->get_plate_origin();
gcode.set_gcode_offset(origin(0), origin(1));
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
gcode.export_layer_filaments(result);
gcode->set_gcode_offset(origin(0), origin(1));
gcode->do_export(this, path.c_str(), result, thumbnail_cb);
gcode->export_layer_filaments(result);
//BBS
result->conflict_result = m_conflict_result;
return path.c_str();

View File

@@ -17,6 +17,7 @@
#include "GCode/ThumbnailData.hpp"
#include "GCode/GCodeProcessor.hpp"
#include "MultiMaterialSegmentation.hpp"
#include "BeltTransform.hpp"
#include "libslic3r.h"
#include <Eigen/Geometry>
@@ -190,30 +191,7 @@ class ConstSupportLayerPtrsAdaptor : public ConstVectorOfPtrsAdaptor<SupportLaye
// When no remap is active, returns the unmodified raw_bounding_box().
inline BoundingBoxf3 belt_remapped_bbox(const ModelObject &model_object, const PrintConfig &config)
{
BoundingBoxf3 bb = model_object.raw_bounding_box();
int pre_rx = int(config.belt_preslice_remap_x.value);
int pre_ry = int(config.belt_preslice_remap_y.value);
int pre_rz = int(config.belt_preslice_remap_z.value);
if (pre_rx == int(BeltRemapAxis::PosX) &&
pre_ry == int(BeltRemapAxis::PosY) &&
pre_rz == int(BeltRemapAxis::PosZ))
return bb; // Identity remap, no change.
auto remap_coord = [](int r, const Vec3d &v) -> double {
int axis = r % 3;
if (r < 3) return v[axis];
return -v[axis];
};
Vec3d mn = bb.min.cast<double>(), mx = bb.max.cast<double>();
BoundingBoxf3 rbb;
for (int i = 0; i < 8; ++i) {
Vec3d c((i & 1) ? mx.x() : mn.x(),
(i & 2) ? mx.y() : mn.y(),
(i & 4) ? mx.z() : mn.z());
Vec3d rc(remap_coord(pre_rx, c), remap_coord(pre_ry, c), remap_coord(pre_rz, c));
if (i == 0) rbb = BoundingBoxf3(rc, rc);
else rbb.merge(rc);
}
return rbb;
return BeltTransformPipeline::remap_bbox(model_object, config);
}
// Single instance of a PrintObject.

View File

@@ -1,5 +1,6 @@
#include "Exception.hpp"
#include "Print.hpp"
#include "BeltTransform.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "ElephantFootCompensation.hpp"
@@ -3398,76 +3399,22 @@ 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 floor parameters for support clipping (populated below if belt Z-shear is active).
double belt_floor_shear_factor_out = 0.0;
int belt_floor_from_axis_out = 1;
double belt_floor_z_shift_out = 0.0;
// Belt shear/scale/pre-remap may change the effective Z height.
BeltTransformPipeline::BeltFloorParams belt_floor;
const auto &pcfg = this->print()->config();
if (pcfg.belt_printer.value) {
BoundingBoxf3 bb = belt_remapped_bbox(*this->model_object(), pcfg);
bool has_preslice_remap = (int(pcfg.belt_preslice_remap_x.value) != int(BeltRemapAxis::PosX) ||
int(pcfg.belt_preslice_remap_y.value) != int(BeltRemapAxis::PosY) ||
int(pcfg.belt_preslice_remap_z.value) != int(BeltRemapAxis::PosZ));
if (has_preslice_remap)
BoundingBoxf3 bb = BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg);
if (BeltTransformPipeline::has_preslice_remap(pcfg))
object_height = bb.size().z();
bool has_z_shear = pcfg.belt_shear_z.value != BeltShearMode::None;
bool has_z_scale = pcfg.belt_scale_z.value != BeltScaleMode::None;
if (has_z_shear || has_z_scale) {
auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double {
double angle_rad = Geometry::deg2rad(angle_deg);
double sin_a = std::sin(angle_rad), 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), 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.;
}
};
double shear_factor = has_z_shear ? compute_shear_factor(pcfg.belt_shear_z.value, pcfg.belt_shear_z_angle.value) : 0.;
double scale_z = compute_scale_factor(pcfg.belt_scale_z.value, pcfg.belt_scale_z_angle.value);
if (has_z_shear && std::abs(shear_factor) > EPSILON) {
int from = int(pcfg.belt_shear_z_from.value);
double min_rz = std::numeric_limits<double>::max();
double max_rz = std::numeric_limits<double>::lowest();
for (double vz : {bb.min.z(), bb.max.z()})
for (double vs : {bb.min(from), bb.max(from)}) {
double new_z = scale_z * (vz + shear_factor * vs);
min_rz = std::min(min_rz, new_z);
max_rz = std::max(max_rz, new_z);
}
object_height = max_rz - min_rz;
belt_floor_shear_factor_out = shear_factor;
belt_floor_from_axis_out = from;
// Belt contact surface starts at bb.min.z() pre-shear; add the
// slicing Z-shift that keeps the mesh above Z=0.
// Exact value is patched after slice_volumes() in posSlice.
belt_floor_z_shift_out = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.);
} else {
object_height *= scale_z;
}
}
auto hr = BeltTransformPipeline::compute_belt_height_and_floor(pcfg, bb, object_height);
object_height = hr.object_height;
belt_floor = hr.floor_params;
}
m_slicing_params = SlicingParameters::create_from_config(pcfg, m_config, object_height,
this->object_extruders(), this->print()->shrinkage_compensation());
// Populate belt floor parameters into slicing params for support clipping.
m_slicing_params.belt_floor_shear_factor = belt_floor_shear_factor_out;
m_slicing_params.belt_floor_from_axis = belt_floor_from_axis_out;
m_slicing_params.belt_floor_z_shift = belt_floor_z_shift_out;
m_slicing_params.belt_floor_shear_factor = belt_floor.shear_factor;
m_slicing_params.belt_floor_from_axis = belt_floor.from_axis;
m_slicing_params.belt_floor_z_shift = belt_floor.z_shift;
}
}
@@ -3505,75 +3452,23 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
sort_remove_duplicates(object_extruders);
//FIXME add painting extruders
// Belt floor parameters for support clipping (populated below if belt Z-shear is active).
double belt_floor_shear_factor_out = 0.0;
int belt_floor_from_axis_out = 1;
double belt_floor_z_shift_out = 0.0;
BeltTransformPipeline::BeltFloorParams belt_floor;
if (object_max_z <= 0.f) {
BoundingBoxf3 bb = model_object.raw_bounding_box();
object_max_z = (float)bb.size().z();
// Belt pre-remap/shear/scale may change the effective Z height.
if (print_config.belt_printer.value) {
bb = belt_remapped_bbox(model_object, print_config);
bool has_preslice_remap = (int(print_config.belt_preslice_remap_x.value) != int(BeltRemapAxis::PosX) ||
int(print_config.belt_preslice_remap_y.value) != int(BeltRemapAxis::PosY) ||
int(print_config.belt_preslice_remap_z.value) != int(BeltRemapAxis::PosZ));
if (has_preslice_remap)
bb = BeltTransformPipeline::remap_bbox(model_object, print_config);
if (BeltTransformPipeline::has_preslice_remap(print_config))
object_max_z = (float)bb.size().z();
bool has_z_shear = print_config.belt_shear_z.value != BeltShearMode::None;
bool has_z_scale = print_config.belt_scale_z.value != BeltScaleMode::None;
if (has_z_shear || has_z_scale) {
auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double {
double angle_rad = Geometry::deg2rad(angle_deg);
double sin_a = std::sin(angle_rad), 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), 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.;
}
};
double shear_factor = has_z_shear ? compute_shear_factor(print_config.belt_shear_z.value, print_config.belt_shear_z_angle.value) : 0.;
double scale_z = compute_scale_factor(print_config.belt_scale_z.value, print_config.belt_scale_z_angle.value);
if (has_z_shear && std::abs(shear_factor) > EPSILON) {
int from = int(print_config.belt_shear_z_from.value);
double min_rz = std::numeric_limits<double>::max();
double max_rz = std::numeric_limits<double>::lowest();
for (double vz : {bb.min.z(), bb.max.z()})
for (double vs : {bb.min(from), bb.max(from)}) {
double new_z = scale_z * (vz + shear_factor * vs);
min_rz = std::min(min_rz, new_z);
max_rz = std::max(max_rz, new_z);
}
object_max_z = (float)(max_rz - min_rz);
belt_floor_shear_factor_out = shear_factor;
belt_floor_from_axis_out = from;
belt_floor_z_shift_out = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.);
} else {
object_max_z *= (float)scale_z;
}
}
auto hr = BeltTransformPipeline::compute_belt_height_and_floor(print_config, bb, object_max_z);
object_max_z = (float)hr.object_height;
belt_floor = hr.floor_params;
}
}
SlicingParameters params = SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders, object_shrinkage_compensation);
params.belt_floor_shear_factor = belt_floor_shear_factor_out;
params.belt_floor_from_axis = belt_floor_from_axis_out;
params.belt_floor_z_shift = belt_floor_z_shift_out;
params.belt_floor_shear_factor = belt_floor.shear_factor;
params.belt_floor_from_axis = belt_floor.from_axis;
params.belt_floor_z_shift = belt_floor.z_shift;
return params;
}

View File

@@ -9,6 +9,8 @@
#include "Layer.hpp"
#include "MultiMaterialSegmentation.hpp"
#include "Print.hpp"
#include "BeltTransform.hpp"
#include "BeltSliceStrategy.hpp"
#include "Geometry.hpp"
//BBS
#include "ShortestPath.hpp"
@@ -142,147 +144,11 @@ 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;
if (print_config.belt_printer.value) {
// --- Pre-slice axis remap ---
// Permutes/negates model axes before slicing so the slicer's coordinate
// system matches the physical bed orientation (e.g. XZ bed instead of XY).
int pre_rx = int(print_config.belt_preslice_remap_x.value);
int pre_ry = int(print_config.belt_preslice_remap_y.value);
int pre_rz = int(print_config.belt_preslice_remap_z.value);
bool has_preslice_remap = (pre_rx != int(BeltRemapAxis::PosX) ||
pre_ry != int(BeltRemapAxis::PosY) ||
pre_rz != int(BeltRemapAxis::PosZ));
if (has_preslice_remap) {
// Build volume extents for Rev mode.
BoundingBoxf bbox_bed(print_config.printable_area.values);
Vec3d vol_max(bbox_bed.max.x(), bbox_bed.max.y(),
print_config.printable_height.value);
// Each remap value selects a source axis and sign.
// The column vector tells the matrix which input axis feeds this output.
auto remap_column = [](int r) -> Vec3d {
int axis = r % 3;
Vec3d col = Vec3d::Zero();
if (r < 3) col[axis] = 1.0; // +axis
else if (r < 6) col[axis] = -1.0; // -axis
else col[axis] = -1.0; // Rev: max - pos = -(pos - max)
return col;
};
Matrix3d remap_lin;
remap_lin.col(0) = remap_column(pre_rx);
remap_lin.col(1) = remap_column(pre_ry);
remap_lin.col(2) = remap_column(pre_rz);
// Translation for Rev modes: output = max[src] - input[src].
Vec3d remap_trans = Vec3d::Zero();
auto add_rev_offset = [&](int r, int out_axis) {
if (r >= 6) {
int src_axis = r % 3;
remap_trans[out_axis] = vol_max[src_axis];
}
};
add_rev_offset(pre_rx, 0);
add_rev_offset(pre_ry, 1);
add_rev_offset(pre_rz, 2);
Transform3d pre_remap = Transform3d::Identity();
pre_remap.linear() = remap_lin;
pre_remap.translation() = remap_trans;
params_base.trafo = pre_remap * params_base.trafo;
}
// Build per-axis shear matrix from 3 independent axis configs.
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.;
}
};
struct AxisShear { BeltShearMode mode; double angle; int from; };
AxisShear axes[3] = {
{ print_config.belt_shear_x.value, print_config.belt_shear_x_angle.value, int(print_config.belt_shear_x_from.value) },
{ print_config.belt_shear_y.value, print_config.belt_shear_y_angle.value, int(print_config.belt_shear_y_from.value) },
{ print_config.belt_shear_z.value, print_config.belt_shear_z_angle.value, int(print_config.belt_shear_z_from.value) },
};
Transform3d belt_shear = Transform3d::Identity();
bool has_shear = 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) {
belt_shear.matrix()(row, axes[row].from) += factor;
has_shear = true;
}
}
}
// Build per-axis scale matrix.
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.;
}
};
Transform3d belt_scale = Transform3d::Identity();
bool has_scale = false;
double sx = compute_scale_factor(print_config.belt_scale_x.value, print_config.belt_scale_x_angle.value);
double sy = compute_scale_factor(print_config.belt_scale_y.value, print_config.belt_scale_y_angle.value);
double sz = compute_scale_factor(print_config.belt_scale_z.value, print_config.belt_scale_z_angle.value);
if (std::abs(sx - 1.) > EPSILON || std::abs(sy - 1.) > EPSILON || std::abs(sz - 1.) > EPSILON) {
belt_scale.matrix()(0, 0) = sx;
belt_scale.matrix()(1, 1) = sy;
belt_scale.matrix()(2, 2) = sz;
has_scale = true;
}
// Apply: scale * shear * trafo (shear first, then scale).
if (has_shear || has_scale)
params_base.trafo = belt_scale * belt_shear * params_base.trafo;
// After pre-remap/shear/scale, the mesh may clip through the build
// plate (Z < 0). Detect this and shift the mesh up along slicer Z.
if (has_preslice_remap || has_shear || has_scale) {
Transform3d combined = params_base.trafo;
double min_z = std::numeric_limits<double>::max();
for (const ModelVolume *mv : model_volumes) {
if (!mv->is_model_part()) continue;
for (const stl_vertex &v : mv->mesh().its.vertices) {
Vec3d pt = combined * v.cast<double>();
min_z = std::min(min_z, pt.z());
}
}
double belt_z_shift_val = (min_z < 0. && min_z != std::numeric_limits<double>::max()) ? -min_z : 0.;
BOOST_LOG_TRIVIAL(warning) << "Belt Z-shift: min_z=" << min_z
<< " z_shift=" << belt_z_shift_val
<< " trafo_z=" << object_trafo.matrix()(2, 3);
if (belt_z_shift_val > 0.) {
Transform3d z_shift = Transform3d::Identity();
z_shift.matrix()(2, 3) = belt_z_shift_val;
params_base.trafo = z_shift * params_base.trafo;
}
if (out_belt_min_z)
*out_belt_min_z = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.;
}
{
// Belt printer: apply pre-slice transforms (remap, shear, scale, z-shift) via strategy.
auto belt_strategy = BeltSliceStrategy::create(print_config);
if (belt_strategy)
belt_strategy->apply_to_trafo(params_base.trafo, model_volumes, out_belt_min_z);
}
//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.
@@ -968,11 +834,8 @@ void PrintObject::slice()
// Without pre-remap, the belt surface IS at Z=0 and bb.min.z() is
// already folded into m_belt_min_z, so use 0.
const auto &pcfg = this->print()->config();
bool has_preslice_remap = (int(pcfg.belt_preslice_remap_x.value) != int(BeltRemapAxis::PosX) ||
int(pcfg.belt_preslice_remap_y.value) != int(BeltRemapAxis::PosY) ||
int(pcfg.belt_preslice_remap_z.value) != int(BeltRemapAxis::PosZ));
double belt_surface_z = has_preslice_remap
? belt_remapped_bbox(*this->model_object(), pcfg).min.z() : 0.;
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
m_slicing_params.belt_floor_z_shift = belt_surface_z + z_shift_val;
}
@@ -1025,18 +888,6 @@ void PrintObject::slice()
<< " belt_shear_z_global=" << pcfg.belt_shear_z_global.value
<< " object=" << this->model_object()->name;
if (pcfg.belt_printer.value) {
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.;
}
};
Point inst_shift = this->instances().empty() ? Point(0, 0)
: this->instances().front().shift - this->center_offset();
@@ -1058,7 +909,7 @@ void PrintObject::slice()
// PrintObjects so the lowest-positioned object stays at Z=0.
const auto &za = gaxes[2]; // Z row
if (za.global && za.mode != BeltShearMode::None && za.from < 2) {
double factor = compute_shear_factor(za.mode, za.angle);
double factor = BeltTransformPipeline::compute_shear_factor(za.mode, za.angle);
// The global Z offset accounts for the instance's position-
// dependent shear contribution. m_belt_min_z is the minimum Z
// of the mesh after pre_remap + shear + trafo_centered, which
@@ -1066,12 +917,8 @@ void PrintObject::slice()
// Subtract the belt surface's centered Z position so we get
// only the shear-induced contribution (same correction as the
// belt_floor_z_shift fix).
// Same pre-remap guard as belt_floor_z_shift above.
bool has_preslice_remap2 = (int(pcfg.belt_preslice_remap_x.value) != int(BeltRemapAxis::PosX) ||
int(pcfg.belt_preslice_remap_y.value) != int(BeltRemapAxis::PosY) ||
int(pcfg.belt_preslice_remap_z.value) != int(BeltRemapAxis::PosZ));
double belt_surface_z = has_preslice_remap2
? belt_remapped_bbox(*this->model_object(), this->print()->config()).min.z() : 0.;
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;
Point phys = inst_shift; // already has center_offset subtracted
double center_on_axis = (za.from == 0) ? unscale<double>(phys.x()) : unscale<double>(phys.y());

View File

@@ -0,0 +1,124 @@
#include "BeltFloorContext.hpp"
#include <cmath>
#include <limits>
namespace Slic3r {
bool BeltFloorContext::init(const SlicingParameters &sp, const PrintConfig &pcfg)
{
m_active = false;
m_shear_factor = sp.belt_floor_shear_factor;
m_from_axis = sp.belt_floor_from_axis;
m_z_shift = sp.belt_floor_z_shift;
m_floor_offset = pcfg.belt_support_floor_offset.value;
if (std::abs(m_shear_factor) < EPSILON)
return false;
m_active = true;
return true;
}
bool BeltFloorContext::init_local(const SlicingParameters &sp, const PrintConfig &pcfg,
double global_z_offset)
{
if (!init(sp, pcfg))
return false;
// Local Z: subtract the global Z offset so polygon computation
// works in the object's local coordinate space.
m_z_shift -= global_z_offset;
return true;
}
Polygons BeltFloorContext::surface_polygon(coordf_t print_z) const
{
return half_plane(print_z, /*belt_surface=*/true);
}
Polygons BeltFloorContext::valid_region_polygon(coordf_t print_z) const
{
return half_plane(print_z, /*belt_surface=*/false);
}
double BeltFloorContext::floor_print_z(const Point &pos_slicing) const
{
if (!m_active)
return -std::numeric_limits<double>::infinity();
double pos = unscale<double>(m_from_axis == 0 ? pos_slicing.x() : pos_slicing.y());
return m_shear_factor * pos + m_floor_offset + m_z_shift;
}
std::vector<Polygons> BeltFloorContext::compute_per_layer_floors(
size_t num_layers,
const std::function<double(size_t)> &layer_print_z) const
{
std::vector<Polygons> result(num_layers);
if (!m_active)
return result;
for (size_t i = 0; i < num_layers; ++i)
result[i] = surface_polygon(layer_print_z(i));
return result;
}
Polygons BeltFloorContext::half_plane(coordf_t print_z, bool belt_surface) const
{
if (!m_active)
return {};
const double cutoff = (print_z - m_z_shift - m_floor_offset) / m_shear_factor;
const coord_t cutoff_scaled = scale_(cutoff);
const coord_t large_bound = scale_(1e3);
// The belt surface is on one side of the cutoff line; the valid region
// is on the other side. Which side depends on shear_factor sign.
//
// belt_surface=true → the belt side (where support should NOT exist)
// belt_surface=false → the valid side (where support IS allowed)
//
// For shear_factor > 0: belt surface is from_axis >= cutoff
// For shear_factor < 0: belt surface is from_axis <= cutoff
bool high_side = (m_shear_factor > 0) == belt_surface;
Polygon poly;
if (m_from_axis == 0) {
if (high_side) {
// X >= cutoff
poly.points = {
Point(cutoff_scaled, -large_bound),
Point(large_bound, -large_bound),
Point(large_bound, large_bound),
Point(cutoff_scaled, large_bound)
};
} else {
// X < cutoff
poly.points = {
Point(-large_bound, -large_bound),
Point(cutoff_scaled, -large_bound),
Point(cutoff_scaled, large_bound),
Point(-large_bound, large_bound)
};
}
} else {
if (high_side) {
// Y >= cutoff
poly.points = {
Point(-large_bound, cutoff_scaled),
Point( large_bound, cutoff_scaled),
Point( large_bound, large_bound),
Point(-large_bound, large_bound)
};
} else {
// Y < cutoff
poly.points = {
Point(-large_bound, -large_bound),
Point( large_bound, -large_bound),
Point( large_bound, cutoff_scaled),
Point(-large_bound, cutoff_scaled)
};
}
}
return { poly };
}
} // namespace Slic3r

View File

@@ -0,0 +1,74 @@
#pragma once
#include "../libslic3r.h"
#include "../Point.hpp"
#include "../Polygon.hpp"
#include "../Slicing.hpp"
#include "../PrintConfig.hpp"
namespace Slic3r {
class PrintObject;
// Belt floor context: encapsulates the parameters and polygon computation
// for belt printer floor clipping in support generation.
//
// All belt floor code across SupportMaterial, TreeSupport, TreeSupport3D,
// and TreeModelVolumes uses the same 4 parameters and the same 4-case
// polygon construction. This class consolidates that logic.
//
// Construct once per PrintObject, then call surface_polygon() or
// valid_region_polygon() per layer with the layer's print_z.
class BeltFloorContext
{
public:
BeltFloorContext() = default;
// Initialize from slicing parameters and print config.
// Uses global Z coordinates (for SupportMaterial, non-organic TreeSupport).
bool init(const SlicingParameters &sp, const PrintConfig &pcfg);
// Initialize with a Z offset subtracted from z_shift.
// Uses local Z coordinates (for TreeSupport3D, TreeModelVolumes organic pipeline).
bool init_local(const SlicingParameters &sp, const PrintConfig &pcfg,
double global_z_offset);
bool is_active() const { return m_active; }
// Compute the belt-side half-plane polygon at a given print_z.
// This is the region where the belt surface exists.
Polygons surface_polygon(coordf_t print_z) const;
// Compute the valid-region half-plane polygon at a given print_z.
// This is the complement: the region where support is allowed.
Polygons valid_region_polygon(coordf_t print_z) const;
// Compute the belt floor Z position at a given XY position (in slicing coords).
// Returns -infinity if not active.
double floor_print_z(const Point &pos_slicing) const;
// Pre-compute belt floor polygons for a range of layers.
// layer_print_z(i) returns the print_z for layer index i.
std::vector<Polygons> compute_per_layer_floors(
size_t num_layers,
const std::function<double(size_t)> &layer_print_z) const;
// Accessors
double shear_factor() const { return m_shear_factor; }
int from_axis() const { return m_from_axis; }
double z_shift() const { return m_z_shift; }
double floor_offset() const { return m_floor_offset; }
private:
bool m_active = false;
double m_shear_factor = 0.0;
int m_from_axis = 1; // 0=X, 1=Y
double m_z_shift = 0.0;
double m_floor_offset = 0.0;
// Internal: compute the raw half-plane polygon.
// If belt_surface=true, returns the belt side; otherwise the valid (complement) side.
Polygons half_plane(coordf_t print_z, bool belt_surface) const;
};
} // namespace Slic3r

View File

@@ -5,6 +5,7 @@
#include "Print.hpp"
#include "SupportMaterial.hpp"
#include "SupportCommon.hpp"
#include "BeltFloorContext.hpp"
#include "Geometry.hpp"
#include "Point.hpp"
#include "MutablePolygon.hpp"
@@ -625,64 +626,10 @@ Polygons belt_floor_surface_polygon(
const PrintObject &object,
coordf_t print_z)
{
const double shear_factor = slicing_params.belt_floor_shear_factor;
if (std::abs(shear_factor) < EPSILON)
BeltFloorContext ctx;
if (!ctx.init(slicing_params, print_config))
return {};
const int from_axis = slicing_params.belt_floor_from_axis; // 0=X, 1=Y
const double floor_offset = print_config.belt_support_floor_offset.value;
// Belt floor line in slicing coordinates: Z = sf * Y + z_shift.
// z_shift accounts for the upward shift applied when post-shear geometry
// extends below the bed (overhangs). Solving for Y:
// cutoff = (print_z - z_shift - floor_offset) / sf
const double z_shift = slicing_params.belt_floor_z_shift;
const double cutoff = (print_z - z_shift - floor_offset) / shear_factor;
const coord_t cutoff_scaled = scale_(cutoff);
const coord_t large_bound = scale_(1e4);
// Build the belt-side half-plane (inverted from the valid region).
// If shear_factor > 0: valid region is from_axis < cutoff, so belt surface is from_axis >= cutoff.
// If shear_factor < 0: valid region is from_axis > cutoff, so belt surface is from_axis <= cutoff.
Polygon belt_poly;
if (from_axis == 0) {
if (shear_factor > 0) {
// Belt surface: X >= cutoff
belt_poly.points = {
Point(cutoff_scaled, -large_bound),
Point(large_bound, -large_bound),
Point(large_bound, large_bound),
Point(cutoff_scaled, large_bound)
};
} else {
// Belt surface: X <= cutoff
belt_poly.points = {
Point(-large_bound, -large_bound),
Point(cutoff_scaled, -large_bound),
Point(cutoff_scaled, large_bound),
Point(-large_bound, large_bound)
};
}
} else {
if (shear_factor > 0) {
// Belt surface: Y >= cutoff
belt_poly.points = {
Point(-large_bound, cutoff_scaled),
Point( large_bound, cutoff_scaled),
Point( large_bound, large_bound),
Point(-large_bound, large_bound)
};
} else {
// Belt surface: Y <= cutoff
belt_poly.points = {
Point(-large_bound, -large_bound),
Point( large_bound, -large_bound),
Point( large_bound, cutoff_scaled),
Point(-large_bound, cutoff_scaled)
};
}
}
return { belt_poly };
return ctx.surface_polygon(print_z);
}
// Belt printer: compute the valid-region half-plane polygon at a given print_z.
@@ -694,58 +641,10 @@ static Polygons belt_floor_valid_region_polygon(
const PrintObject &object,
coordf_t print_z)
{
const double shear_factor = slicing_params.belt_floor_shear_factor;
if (std::abs(shear_factor) < EPSILON)
BeltFloorContext ctx;
if (!ctx.init(slicing_params, print_config))
return {};
const int from_axis = slicing_params.belt_floor_from_axis;
const double floor_offset = print_config.belt_support_floor_offset.value;
const double z_shift = slicing_params.belt_floor_z_shift;
const double cutoff = (print_z - z_shift - floor_offset) / shear_factor;
const coord_t cutoff_scaled = scale_(cutoff);
const coord_t large_bound = scale_(1e4);
// Valid region: the complement of the belt surface polygon.
Polygon valid_poly;
if (from_axis == 0) {
if (shear_factor > 0) {
// Valid: X < cutoff
valid_poly.points = {
Point(-large_bound, -large_bound),
Point(cutoff_scaled, -large_bound),
Point(cutoff_scaled, large_bound),
Point(-large_bound, large_bound)
};
} else {
// Valid: X > cutoff
valid_poly.points = {
Point(cutoff_scaled, -large_bound),
Point(large_bound, -large_bound),
Point(large_bound, large_bound),
Point(cutoff_scaled, large_bound)
};
}
} else {
if (shear_factor > 0) {
// Valid: Y < cutoff
valid_poly.points = {
Point(-large_bound, -large_bound),
Point( large_bound, -large_bound),
Point( large_bound, cutoff_scaled),
Point(-large_bound, cutoff_scaled)
};
} else {
// Valid: Y > cutoff
valid_poly.points = {
Point(-large_bound, cutoff_scaled),
Point( large_bound, cutoff_scaled),
Point( large_bound, large_bound),
Point(-large_bound, large_bound)
};
}
}
return { valid_poly };
return ctx.valid_region_polygon(print_z);
}
// Collect outer contours of all slices of this layer.
@@ -3373,22 +3272,18 @@ static void trim_support_layers_by_belt_floor(
const PrintObject &object,
SupportGeneratorLayersPtr &support_layers)
{
if (std::abs(slicing_params.belt_floor_shear_factor) < EPSILON)
BeltFloorContext ctx;
if (!ctx.init(slicing_params, print_config))
return;
if (print_config.belt_support_floor_mode.value != BeltSupportFloorMode::GeneratorOnly)
return;
tbb::parallel_for(tbb::blocked_range<size_t>(0, support_layers.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++ i) {
SupportGeneratorLayer *layer = support_layers[i];
if (layer->polygons.empty())
continue;
Polygons belt_surface = belt_floor_surface_polygon(
slicing_params, print_config, object, layer->print_z);
if (! belt_surface.empty())
layer->polygons = diff(layer->polygons, belt_surface);
}
for (size_t i = range.begin(); i < range.end(); ++i)
if (support_layers[i])
support_layers[i]->polygons = diff(support_layers[i]->polygons,
ctx.surface_polygon(support_layers[i]->print_z));
});
}

View File

@@ -8,6 +8,7 @@
#include "TreeModelVolumes.hpp"
#include "TreeSupportCommon.hpp"
#include "BeltFloorContext.hpp"
#include "../BuildVolume.hpp"
#include "../ClipperUtils.hpp"
@@ -101,13 +102,11 @@ TreeModelVolumes::TreeModelVolumes(
{
const auto &sp = print_object.slicing_parameters();
const auto &pcfg = print_object.print()->config();
const double sf = sp.belt_floor_shear_factor;
if (std::abs(sf) > EPSILON
BeltFloorContext ctx;
ctx.init_local(sp, pcfg, print_object.belt_global_z_offset());
if (ctx.is_active()
&& std::abs(print_object.belt_global_z_offset()) > EPSILON
&& pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
const int from_axis = sp.belt_floor_from_axis;
const double floor_off = pcfg.belt_support_floor_offset.value;
const double z_shift = sp.belt_floor_z_shift - print_object.belt_global_z_offset();
size_t num_layers_needed = print_object.layer_count();
// Ensure m_anti_overhang is large enough.
if (m_anti_overhang.size() < num_layers_needed)
@@ -115,22 +114,7 @@ TreeModelVolumes::TreeModelVolumes(
for (size_t layer_idx = 0; layer_idx < num_layers_needed; ++layer_idx) {
double print_z = print_object.get_layer(layer_idx)->print_z
- print_object.belt_global_z_offset();
double cutoff = (print_z - z_shift - floor_off) / sf;
coord_t cutoff_sc = scale_(cutoff);
coord_t big = scale_(1e4);
Polygon belt_poly;
if (from_axis == 0) {
if (sf > 0)
belt_poly.points = {{cutoff_sc,-big},{big,-big},{big,big},{cutoff_sc,big}};
else
belt_poly.points = {{-big,-big},{cutoff_sc,-big},{cutoff_sc,big},{-big,big}};
} else {
if (sf > 0)
belt_poly.points = {{-big,cutoff_sc},{big,cutoff_sc},{big,big},{-big,big}};
else
belt_poly.points = {{-big,-big},{big,-big},{big,cutoff_sc},{-big,cutoff_sc}};
}
append(m_anti_overhang[layer_idx], Polygons{belt_poly});
append(m_anti_overhang[layer_idx], ctx.surface_polygon(print_z));
}
}
}
@@ -180,40 +164,20 @@ TreeModelVolumes::TreeModelVolumes(
// Branches grow toward the belt and their slices are clipped at the belt
// surface in organic_draw_branches(). The organic pipeline works in LOCAL
// Z (no global_z_offset), so use local z_shift and local print_z.
const auto &slicing_params = print_object.slicing_parameters();
const auto &pcfg = print_object.print()->config();
const double sf = slicing_params.belt_floor_shear_factor;
if (std::abs(sf) > EPSILON
&& pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
const int from_axis = slicing_params.belt_floor_from_axis;
const double floor_off = pcfg.belt_support_floor_offset.value;
// Subtract global_z_offset to get the LOCAL z_shift — the organic
// pipeline's Z coordinates don't include the global offset.
const double z_shift = slicing_params.belt_floor_z_shift
- print_object.belt_global_z_offset();
m_belt_floor.assign(num_layers, Polygons{});
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
// Use local print_z (subtract global offset from object layer).
double print_z = (layer_idx >= num_raft_layers)
? print_object.get_layer(layer_idx - num_raft_layers)->print_z
- print_object.belt_global_z_offset()
: 0.;
double cutoff = (print_z - z_shift - floor_off) / sf;
coord_t cutoff_sc = scale_(cutoff);
coord_t big = scale_(1e4);
Polygon belt_poly;
if (from_axis == 0) {
if (sf > 0)
belt_poly.points = {{cutoff_sc,-big},{big,-big},{big,big},{cutoff_sc,big}};
else
belt_poly.points = {{-big,-big},{cutoff_sc,-big},{cutoff_sc,big},{-big,big}};
} else {
if (sf > 0)
belt_poly.points = {{-big,cutoff_sc},{big,cutoff_sc},{big,big},{-big,big}};
else
belt_poly.points = {{-big,-big},{big,-big},{big,cutoff_sc},{-big,cutoff_sc}};
}
m_belt_floor[layer_idx] = { belt_poly };
{
const auto &slicing_params = print_object.slicing_parameters();
const auto &pcfg2 = print_object.print()->config();
BeltFloorContext ctx;
ctx.init_local(slicing_params, pcfg2, print_object.belt_global_z_offset());
if (ctx.is_active()
&& pcfg2.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
m_belt_floor = ctx.compute_per_layer_floors(num_layers, [&](size_t layer_idx) -> double {
// Use local print_z (subtract global offset from object layer).
return (layer_idx >= num_raft_layers)
? print_object.get_layer(layer_idx - num_raft_layers)->print_z
- print_object.belt_global_z_offset()
: 0.;
});
}
}
}

View File

@@ -14,6 +14,7 @@
#include "TreeSupportCommon.hpp"
#include "TreeSupport.hpp"
#include "TreeSupport3D.hpp"
#include "BeltFloorContext.hpp"
#include <libnest2d/backends/libslic3r/geometries.hpp>
#include <libnest2d/placers/nfpplacer.hpp>
@@ -639,16 +640,10 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p
double TreeSupport::belt_floor_print_z(const Point &pos_slicing) const
{
double sf = m_slicing_params.belt_floor_shear_factor;
if (std::abs(sf) < EPSILON)
return -std::numeric_limits<double>::max(); // no belt floor
int from = m_slicing_params.belt_floor_from_axis;
// Belt floor in slicing coords: Z = sf * Y + z_shift + floor_offset.
// Inverse of cutoff = (Z - z_shift - floor_offset) / sf.
double pos = unscale<double>(from == 0 ? pos_slicing.x() : pos_slicing.y());
double floor_offset = m_print_config->belt_support_floor_offset.value;
double z_shift = m_slicing_params.belt_floor_z_shift;
return sf * pos + floor_offset + z_shift;
BeltFloorContext ctx;
if (!ctx.init(m_slicing_params, *m_print_config))
return -std::numeric_limits<double>::infinity();
return ctx.floor_print_z(pos_slicing);
}
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
@@ -1738,18 +1733,11 @@ void TreeSupport::generate()
// layer and clipped at the belt surface. These layers bypass the tree
// algorithm entirely — they're pure geometry added after draw_circles().
{
const auto &sp = m_slicing_params;
const auto &pcfg = *m_print_config;
const double sf = sp.belt_floor_shear_factor;
if (std::abs(sf) > EPSILON
&& pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly
BeltFloorContext ctx;
if (ctx.init(m_slicing_params, *m_print_config)
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly
&& m_object->support_layer_count() > 0) {
const int from_axis = sp.belt_floor_from_axis;
const double floor_off = pcfg.belt_support_floor_offset.value;
// Support layer print_z values are in GLOBAL Z (non-organic inherits
// from object layers which include global_z_offset). Use the GLOBAL
// belt_floor_z_shift to match.
const double z_shift = sp.belt_floor_z_shift;
const auto &sp = m_slicing_params;
// Find the lowest non-empty, non-brim support layer.
ExPolygons source_areas;
double source_z = 0;
@@ -1778,7 +1766,7 @@ void TreeSupport::generate()
}
if (!source_areas.empty()) {
BoundingBoxf3 bb = belt_remapped_bbox(*m_object->model_object(), m_object->print()->config());
double from_extent = std::abs(bb.min(from_axis));
double from_extent = std::abs(bb.min(ctx.from_axis()));
double bb_min_z = std::abs(bb.min.z());
double first_z = m_object->get_support_layer(0)->print_z;
// Depth = from-axis extent + pre-shear bbox Z offset (ensure_on_bed
@@ -1792,18 +1780,8 @@ void TreeSupport::generate()
for (int i = num_extra; i >= 1 && !prev_areas.empty(); --i) {
double print_z = first_z - i * sp.layer_height;
if (print_z < -sp.layer_height) continue;
double cutoff = (print_z - z_shift - floor_off) / sf;
coord_t cutoff_sc = scale_(cutoff);
coord_t big = scale_(1e3);
Polygon belt_poly;
if (from_axis == 0) {
if (sf > 0) belt_poly.points = {{cutoff_sc,-big},{big,-big},{big,big},{cutoff_sc,big}};
else belt_poly.points = {{-big,-big},{cutoff_sc,-big},{cutoff_sc,big},{-big,big}};
} else {
if (sf > 0) belt_poly.points = {{-big,cutoff_sc},{big,cutoff_sc},{big,big},{-big,big}};
else belt_poly.points = {{-big,-big},{big,-big},{big,cutoff_sc},{-big,cutoff_sc}};
}
ExPolygons clipped = diff_ex(source_areas, Polygons{belt_poly});
Polygons belt_surface = ctx.surface_polygon(print_z);
ExPolygons clipped = diff_ex(source_areas, belt_surface);
if (clipped.empty()) continue;
SupportLayer *sl = new SupportLayer(0, 0, m_object, sp.layer_height, print_z, -1);
sl->base_areas = clipped;
@@ -2250,37 +2228,19 @@ void TreeSupport::draw_circles()
// Belt floor: clip tree support polygons by the belt surface plane.
// ts_layer->print_z is at LOCAL Z (global offset applied later in
// _generate_support_material), but belt_floor_z_shift includes
// global_z_offset — subtract it to get the cutoff in local coords.
if (std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
const double sf = m_slicing_params.belt_floor_shear_factor;
const int from_axis = m_slicing_params.belt_floor_from_axis;
const double floor_off = m_print_config->belt_support_floor_offset.value;
const double z_shift_local = m_slicing_params.belt_floor_z_shift
- m_object->belt_global_z_offset();
const double cutoff = (ts_layer->print_z - z_shift_local - floor_off) / sf;
const coord_t cutoff_sc = scale_(cutoff);
const coord_t big = scale_(1e4);
Polygon belt_poly;
if (from_axis == 0) {
if (sf > 0)
belt_poly.points = { {cutoff_sc,-big}, {big,-big}, {big,big}, {cutoff_sc,big} };
else
belt_poly.points = { {-big,-big}, {cutoff_sc,-big}, {cutoff_sc,big}, {-big,big} };
} else {
if (sf > 0)
belt_poly.points = { {-big,cutoff_sc}, {big,cutoff_sc}, {big,big}, {-big,big} };
else
belt_poly.points = { {-big,-big}, {big,-big}, {big,cutoff_sc}, {-big,cutoff_sc} };
// _generate_support_material), so use init_local to subtract
// belt_global_z_offset from z_shift.
{
BeltFloorContext ctx;
if (ctx.init_local(m_slicing_params, *m_print_config, m_object->belt_global_z_offset())
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
Polygons belt_surface = ctx.surface_polygon(ts_layer->print_z);
base_areas = diff_ex(base_areas, belt_surface);
roof_areas = diff_ex(roof_areas, belt_surface);
roof_1st_layer = diff_ex(roof_1st_layer, belt_surface);
floor_areas = diff_ex(floor_areas, belt_surface);
roof_gap_areas = diff_ex(roof_gap_areas, belt_surface);
}
Polygons belt_surface = { belt_poly };
base_areas = diff_ex(base_areas, belt_surface);
roof_areas = diff_ex(roof_areas, belt_surface);
roof_1st_layer = diff_ex(roof_1st_layer, belt_surface);
floor_areas = diff_ex(floor_areas, belt_surface);
roof_gap_areas = diff_ex(roof_gap_areas, belt_surface);
}
if (SQUARE_SUPPORT) {
@@ -3593,6 +3553,20 @@ TreeSupportData::TreeSupportData(const PrintObject &object, coordf_t xy_distance
poly.simplify(scale_(m_radius_sample_resolution), &outline);
}
// Belt floor: add belt surface polygon to layer outlines so the
// collision system treats the belt as a physical surface.
{
BeltFloorContext ctx;
double local_print_z = layer->print_z - object.belt_global_z_offset();
if (ctx.init_local(object.slicing_parameters(), object.print()->config(),
object.belt_global_z_offset())
&& object.print()->config().belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
Polygons belt_surface = ctx.surface_polygon(local_print_z);
for (auto &p : belt_surface)
outline.emplace_back(ExPolygon(p));
}
}
if (layer_nr == 0)
m_layer_outlines_below.push_back(outline);
else

View File

@@ -19,6 +19,7 @@
#include "Polygon.hpp"
#include "Polyline.hpp"
#include "MutablePolygon.hpp"
#include "BeltFloorContext.hpp"
#include "SupportCommon.hpp"
#include "TriangleMeshSlicer.hpp"
#include "TreeSupport.hpp"
@@ -3373,8 +3374,9 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
PrintObject &po = *print.get_object(processing.second.front());
const auto &sp = po.slicing_parameters();
const auto &pcfg = po.print()->config();
const double sf = sp.belt_floor_shear_factor;
if (std::abs(sf) > EPSILON && std::abs(po.belt_global_z_offset()) > EPSILON
BeltFloorContext ctx;
ctx.init_local(sp, pcfg, po.belt_global_z_offset());
if (ctx.is_active() && std::abs(po.belt_global_z_offset()) > EPSILON
&& pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
// z_shift_local is the belt surface height at Y=0 in local coords.
// Extend below the belt so the base expansion and build-plate
@@ -3598,32 +3600,16 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
// Compute the belt floor polygon directly from each layer's print_z
// rather than mapping to a layer index (avoids index mismatch issues).
{
const auto &sp = print_object.slicing_parameters();
const double sf = sp.belt_floor_shear_factor;
const double z_shift = sp.belt_floor_z_shift - print_object.belt_global_z_offset();
const double floor_off = print_object.print()->config().belt_support_floor_offset.value;
const int from_axis = sp.belt_floor_from_axis;
if (std::abs(sf) > EPSILON
&& print_object.print()->config().belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
const auto &sp = print_object.slicing_parameters();
const auto &pcfg = print_object.print()->config();
BeltFloorContext ctx;
ctx.init_local(sp, pcfg, print_object.belt_global_z_offset());
if (ctx.is_active()
&& pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
tbb::parallel_for_each(layers_sorted.begin(), layers_sorted.end(), [&](SupportGeneratorLayer *layer) {
if (!layer || layer->polygons.empty())
return;
double cutoff = (layer->print_z - z_shift - floor_off) / sf;
coord_t cutoff_sc = scale_(cutoff);
coord_t big = scale_(1e4);
Polygon belt_poly;
if (from_axis == 0) {
if (sf > 0)
belt_poly.points = {{cutoff_sc,-big},{big,-big},{big,big},{cutoff_sc,big}};
else
belt_poly.points = {{-big,-big},{cutoff_sc,-big},{cutoff_sc,big},{-big,big}};
} else {
if (sf > 0)
belt_poly.points = {{-big,cutoff_sc},{big,cutoff_sc},{big,big},{-big,big}};
else
belt_poly.points = {{-big,-big},{big,-big},{big,cutoff_sc},{-big,cutoff_sc}};
}
layer->polygons = diff(layer->polygons, Polygons{belt_poly});
layer->polygons = diff(layer->polygons, ctx.surface_polygon(layer->print_z));
});
}
}