mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 11:21:30 +00:00
Part 2.7: Add G-code back-transform and tree support belt floor clipping
- Add BeltBackTransform class that inverts the shear/scale matrix and applies it in GCodeWriter::to_machine_coords() so G-code outputs in the machine's physical coordinate space, gated by new belt_gcode_back_transform config option - Extend belt floor clipping to all three tree support pipelines (Prusa-style, Orca organic, TreeModelVolumes) with per-layer polygon clipping, anti-overhang integration, and belt raft extension layers - Fix tree drop_nodes() belt termination, organic support global Z offset, collision calculation index bug, and first-layer brim/empty layer checks for belt printers two-shot - first build built but didn't plumb to UI. Woah. add pre-slice axis remap, because Y needs to be Z going to change tactic and move based on bbox min switch to per axis snapping per axis swap snap now per object build plate tilt wasn't invalidating slicer settings support upper bound now correct, need to get lower bound corrected axis swapped support termination corrected Z Shear works with and without pre-slice remap now
This commit is contained in:
@@ -208,6 +208,8 @@ set(lisbslic3r_sources
|
|||||||
GCode/AdaptivePAProcessor.hpp
|
GCode/AdaptivePAProcessor.hpp
|
||||||
GCode/AvoidCrossingPerimeters.cpp
|
GCode/AvoidCrossingPerimeters.cpp
|
||||||
GCode/AvoidCrossingPerimeters.hpp
|
GCode/AvoidCrossingPerimeters.hpp
|
||||||
|
GCode/BeltBackTransform.cpp
|
||||||
|
GCode/BeltBackTransform.hpp
|
||||||
GCode/ConflictChecker.cpp
|
GCode/ConflictChecker.cpp
|
||||||
GCode/ConflictChecker.hpp
|
GCode/ConflictChecker.hpp
|
||||||
GCode/CoolingBuffer.cpp
|
GCode/CoolingBuffer.cpp
|
||||||
|
|||||||
@@ -2432,6 +2432,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
// Build volume extents for Rev remap mode.
|
// Build volume extents for Rev remap mode.
|
||||||
BoundingBoxf bbox_bed(print.config().printable_area.values);
|
BoundingBoxf bbox_bed(print.config().printable_area.values);
|
||||||
m_writer.set_build_volume_max(Vec3d(bbox_bed.max.x(), bbox_bed.max.y(), print.config().printable_height.value));
|
m_writer.set_build_volume_max(Vec3d(bbox_bed.max.x(), bbox_bed.max.y(), print.config().printable_height.value));
|
||||||
|
// Initialize the back-transform that undoes slicing shear/scale.
|
||||||
|
m_writer.set_belt_back_transform(print.config());
|
||||||
|
// Per-axis origin snap: store config; actual snap is computed
|
||||||
|
// per-instance in update_origin_snap() called from set_origin().
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// How many times will be change_layer() called?
|
// How many times will be change_layer() called?
|
||||||
@@ -2544,6 +2554,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
file.write_format("; belt_scale_y_angle = %.1f\n", print.config().belt_scale_y_angle.value);
|
file.write_format("; belt_scale_y_angle = %.1f\n", print.config().belt_scale_y_angle.value);
|
||||||
file.write_format("; belt_scale_z = %s\n", full_cfg.opt_serialize("belt_scale_z").c_str());
|
file.write_format("; belt_scale_z = %s\n", full_cfg.opt_serialize("belt_scale_z").c_str());
|
||||||
file.write_format("; belt_scale_z_angle = %.1f\n", print.config().belt_scale_z_angle.value);
|
file.write_format("; belt_scale_z_angle = %.1f\n", print.config().belt_scale_z_angle.value);
|
||||||
|
file.write_format("; belt_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());
|
||||||
}
|
}
|
||||||
if (is_bbl_printers)
|
if (is_bbl_printers)
|
||||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
|
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
|
||||||
@@ -3248,6 +3261,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
}
|
}
|
||||||
print.throw_if_canceled();
|
print.throw_if_canceled();
|
||||||
this->set_origin(unscale((*print_object_instance_sequential_active)->shift));
|
this->set_origin(unscale((*print_object_instance_sequential_active)->shift));
|
||||||
|
this->update_origin_snap((*print_object_instance_sequential_active)->print_object,
|
||||||
|
(*print_object_instance_sequential_active)->shift);
|
||||||
|
|
||||||
// BBS: prime extruder if extruder change happens before this object instance
|
// BBS: prime extruder if extruder change happens before this object instance
|
||||||
bool prime_extruder = false;
|
bool prime_extruder = false;
|
||||||
@@ -5263,6 +5278,7 @@ LayerResult GCode::process_layer(
|
|||||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||||
m_last_obj_copy = this_object_copy;
|
m_last_obj_copy = this_object_copy;
|
||||||
this->set_origin(unscale(offset));
|
this->set_origin(unscale(offset));
|
||||||
|
this->update_origin_snap(&instance_to_print.print_object, offset);
|
||||||
if (instance_to_print.object_by_extruder.support != nullptr) {
|
if (instance_to_print.object_by_extruder.support != nullptr) {
|
||||||
m_layer = layers[instance_to_print.layer_id].support_layer;
|
m_layer = layers[instance_to_print.layer_id].support_layer;
|
||||||
m_object_layer_over_raft = false;
|
m_object_layer_over_raft = false;
|
||||||
@@ -5286,6 +5302,7 @@ LayerResult GCode::process_layer(
|
|||||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||||
m_last_obj_copy = this_object_copy;
|
m_last_obj_copy = this_object_copy;
|
||||||
this->set_origin(unscale(offset));
|
this->set_origin(unscale(offset));
|
||||||
|
this->update_origin_snap(&instance_to_print.print_object, offset);
|
||||||
ExtrusionEntityCollection support_eec;
|
ExtrusionEntityCollection support_eec;
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
@@ -5334,6 +5351,7 @@ LayerResult GCode::process_layer(
|
|||||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||||
m_last_obj_copy = this_object_copy;
|
m_last_obj_copy = this_object_copy;
|
||||||
this->set_origin(unscale(offset));
|
this->set_origin(unscale(offset));
|
||||||
|
this->update_origin_snap(&instance_to_print.print_object, offset);
|
||||||
//FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
|
//FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
|
||||||
|
|
||||||
auto has_infill = [](const std::vector<ObjectByExtruder::Island::Region> &by_region) {
|
auto has_infill = [](const std::vector<ObjectByExtruder::Island::Region> &by_region) {
|
||||||
@@ -5560,6 +5578,130 @@ void GCode::set_origin(const Vec2d &pointf)
|
|||||||
m_origin = pointf;
|
m_origin = pointf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GCode::update_origin_snap(const PrintObject *obj, const Point &inst_shift)
|
||||||
|
{
|
||||||
|
if (!m_origin_snap[0] && !m_origin_snap[1] && !m_origin_snap[2])
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Clear existing snap so to_machine_coords gives raw machine coords for bbox computation.
|
||||||
|
for (int a = 0; a < 3; ++a)
|
||||||
|
m_writer.set_origin_snap(a, false, 0., 0.);
|
||||||
|
|
||||||
|
// Reconstruct the belt pipeline transform for this object (same as
|
||||||
|
// PrintObjectSlice.cpp: z_shift * scale * shear * pre_remap).
|
||||||
|
const auto &cfg = m_config;
|
||||||
|
Transform3d belt = Transform3d::Identity();
|
||||||
|
|
||||||
|
// Pre-slice remap
|
||||||
|
int pre_rx = int(cfg.belt_preslice_remap_x.value);
|
||||||
|
int pre_ry = int(cfg.belt_preslice_remap_y.value);
|
||||||
|
int pre_rz = int(cfg.belt_preslice_remap_z.value);
|
||||||
|
if (pre_rx != int(BeltRemapAxis::PosX) ||
|
||||||
|
pre_ry != int(BeltRemapAxis::PosY) ||
|
||||||
|
pre_rz != int(BeltRemapAxis::PosZ)) {
|
||||||
|
auto remap_col = [](int r) -> Vec3d {
|
||||||
|
int a = r % 3; Vec3d c = Vec3d::Zero();
|
||||||
|
c[a] = (r < 3) ? 1.0 : -1.0;
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
Matrix3d lin;
|
||||||
|
lin.col(0) = remap_col(pre_rx);
|
||||||
|
lin.col(1) = remap_col(pre_ry);
|
||||||
|
lin.col(2) = remap_col(pre_rz);
|
||||||
|
Transform3d pre = Transform3d::Identity();
|
||||||
|
pre.linear() = lin;
|
||||||
|
if (pre_rx >= 6 || pre_ry >= 6 || pre_rz >= 6) {
|
||||||
|
BoundingBoxf bb(cfg.printable_area.values);
|
||||||
|
Vec3d vm(bb.max.x(), bb.max.y(), cfg.printable_height.value);
|
||||||
|
Vec3d tr = Vec3d::Zero();
|
||||||
|
if (pre_rx >= 6) tr[0] = vm[pre_rx % 3];
|
||||||
|
if (pre_ry >= 6) tr[1] = vm[pre_ry % 3];
|
||||||
|
if (pre_rz >= 6) tr[2] = vm[pre_rz % 3];
|
||||||
|
pre.translation() = tr;
|
||||||
|
}
|
||||||
|
belt = pre * belt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shear
|
||||||
|
auto shear_f = [](BeltShearMode m, double a) -> double {
|
||||||
|
double r = Geometry::deg2rad(a), s = std::sin(r), c = std::cos(r);
|
||||||
|
switch (m) {
|
||||||
|
case BeltShearMode::PosCot: return (s > EPSILON) ? c/s : 0.;
|
||||||
|
case BeltShearMode::NegCot: return (s > EPSILON) ? -c/s : 0.;
|
||||||
|
case BeltShearMode::PosTan: return (c > EPSILON) ? s/c : 0.;
|
||||||
|
case BeltShearMode::NegTan: return (c > EPSILON) ? -s/c : 0.;
|
||||||
|
default: return 0.;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
struct AS { BeltShearMode m; double a; int f; };
|
||||||
|
AS axes[3] = {
|
||||||
|
{cfg.belt_shear_x.value, cfg.belt_shear_x_angle.value, int(cfg.belt_shear_x_from.value)},
|
||||||
|
{cfg.belt_shear_y.value, cfg.belt_shear_y_angle.value, int(cfg.belt_shear_y_from.value)},
|
||||||
|
{cfg.belt_shear_z.value, cfg.belt_shear_z_angle.value, int(cfg.belt_shear_z_from.value)},
|
||||||
|
};
|
||||||
|
Transform3d shear = Transform3d::Identity();
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
if (axes[i].m != BeltShearMode::None) {
|
||||||
|
double f = shear_f(axes[i].m, axes[i].a);
|
||||||
|
if (std::abs(f) > EPSILON)
|
||||||
|
shear.matrix()(i, axes[i].f) += f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale
|
||||||
|
auto scale_f = [](BeltScaleMode m, double a) -> double {
|
||||||
|
if (m == BeltScaleMode::None) return 1.;
|
||||||
|
double r = Geometry::deg2rad(a), s = std::sin(r), c = std::cos(r);
|
||||||
|
switch (m) {
|
||||||
|
case BeltScaleMode::InvSin: return (s > EPSILON) ? 1./s : 1.;
|
||||||
|
case BeltScaleMode::InvCos: return (c > EPSILON) ? 1./c : 1.;
|
||||||
|
case BeltScaleMode::Sin: return s;
|
||||||
|
case BeltScaleMode::Cos: return c;
|
||||||
|
default: return 1.;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Transform3d sc = Transform3d::Identity();
|
||||||
|
sc.matrix()(0,0) = scale_f(cfg.belt_scale_x.value, cfg.belt_scale_x_angle.value);
|
||||||
|
sc.matrix()(1,1) = scale_f(cfg.belt_scale_y.value, cfg.belt_scale_y_angle.value);
|
||||||
|
sc.matrix()(2,2) = scale_f(cfg.belt_scale_z.value, cfg.belt_scale_z_angle.value);
|
||||||
|
|
||||||
|
belt = sc * shear * belt;
|
||||||
|
|
||||||
|
// 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 = m_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)
|
||||||
|
m_writer.set_origin_snap(a, m_origin_snap[a], m_origin_snap_offset[a], inst_min[a]);
|
||||||
|
}
|
||||||
|
|
||||||
std::string GCode::preamble()
|
std::string GCode::preamble()
|
||||||
{
|
{
|
||||||
std::string gcode = m_writer.preamble();
|
std::string gcode = m_writer.preamble();
|
||||||
|
|||||||
@@ -493,6 +493,11 @@ private:
|
|||||||
This affects the input arguments supplied to the extrude*() and travel_to()
|
This affects the input arguments supplied to the extrude*() and travel_to()
|
||||||
methods. */
|
methods. */
|
||||||
Vec2d m_origin;
|
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;
|
FullPrintConfig m_config;
|
||||||
DynamicConfig m_calib_config;
|
DynamicConfig m_calib_config;
|
||||||
// scaled G-code resolution
|
// scaled G-code resolution
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
#include "BeltBackTransform.hpp"
|
||||||
|
#include "../Geometry.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
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;
|
||||||
|
m_inverse = Transform3d::Identity();
|
||||||
|
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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_active = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d BeltBackTransform::apply(const Vec3d &pos) const
|
||||||
|
{
|
||||||
|
if (!m_active)
|
||||||
|
return pos;
|
||||||
|
return m_inverse * pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#ifndef slic3r_BeltBackTransform_hpp_
|
||||||
|
#define slic3r_BeltBackTransform_hpp_
|
||||||
|
|
||||||
|
#include "../libslic3r.h"
|
||||||
|
#include "../Point.hpp"
|
||||||
|
#include "../PrintConfig.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Reverses the pre-slice remap + shear + scale transforms that
|
||||||
|
// PrintObjectSlice.cpp applies to belt printer geometry, converting G-code
|
||||||
|
// coordinates from the sliced (remapped/sheared/scaled) frame back to the
|
||||||
|
// machine's real coordinate space.
|
||||||
|
//
|
||||||
|
// Initialized once from PrintConfig, then applied per-point in
|
||||||
|
// GCodeWriter::to_machine_coords() before axis remapping.
|
||||||
|
//
|
||||||
|
// Active when belt_gcode_back_transform is true AND at least one of:
|
||||||
|
// - a shear axis has global mode enabled, or
|
||||||
|
// - a pre-slice axis remap is non-identity.
|
||||||
|
class BeltBackTransform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BeltBackTransform() = default;
|
||||||
|
|
||||||
|
// Initialize from belt printer config. Rebuilds the same pre-slice remap,
|
||||||
|
// shear, and scale matrices as PrintObjectSlice.cpp and precomputes the
|
||||||
|
// affine inverse. Returns true if a non-identity back-transform was computed.
|
||||||
|
bool init_from_config(const PrintConfig &config);
|
||||||
|
|
||||||
|
// Apply the inverse transform to a point. Returns pos unchanged if
|
||||||
|
// no back-transform is active.
|
||||||
|
Vec3d apply(const Vec3d &pos) const;
|
||||||
|
|
||||||
|
// True if a non-identity back-transform is active.
|
||||||
|
bool is_active() const { return m_active; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_active = false;
|
||||||
|
Transform3d m_inverse = Transform3d::Identity();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // slic3r_BeltBackTransform_hpp_
|
||||||
@@ -3128,6 +3128,28 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
|
|||||||
if (boost::starts_with(comment, " belt_scale_z_angle = ")) {
|
if (boost::starts_with(comment, " belt_scale_z_angle = ")) {
|
||||||
try { m_result.belt_scale_z_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return;
|
try { m_result.belt_scale_z_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return;
|
||||||
}
|
}
|
||||||
|
// Pre-slice axis remap
|
||||||
|
auto parse_remap_axis = [](const std::string &s) -> BeltRemapAxis {
|
||||||
|
if (s == "pos_x") return BeltRemapAxis::PosX;
|
||||||
|
if (s == "pos_y") return BeltRemapAxis::PosY;
|
||||||
|
if (s == "pos_z") return BeltRemapAxis::PosZ;
|
||||||
|
if (s == "neg_x") return BeltRemapAxis::NegX;
|
||||||
|
if (s == "neg_y") return BeltRemapAxis::NegY;
|
||||||
|
if (s == "neg_z") return BeltRemapAxis::NegZ;
|
||||||
|
if (s == "rev_x") return BeltRemapAxis::RevX;
|
||||||
|
if (s == "rev_y") return BeltRemapAxis::RevY;
|
||||||
|
if (s == "rev_z") return BeltRemapAxis::RevZ;
|
||||||
|
return BeltRemapAxis::PosX;
|
||||||
|
};
|
||||||
|
if (boost::starts_with(comment, " belt_preslice_remap_x = ")) {
|
||||||
|
m_result.belt_preslice_remap_x = parse_remap_axis(trim(std::string(comment.substr(25)))); return;
|
||||||
|
}
|
||||||
|
if (boost::starts_with(comment, " belt_preslice_remap_y = ")) {
|
||||||
|
m_result.belt_preslice_remap_y = parse_remap_axis(trim(std::string(comment.substr(25)))); return;
|
||||||
|
}
|
||||||
|
if (boost::starts_with(comment, " belt_preslice_remap_z = ")) {
|
||||||
|
m_result.belt_preslice_remap_z = parse_remap_axis(trim(std::string(comment.substr(25)))); return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// wipe start tag
|
// wipe start tag
|
||||||
if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) {
|
if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) {
|
||||||
|
|||||||
@@ -246,6 +246,9 @@ class Print;
|
|||||||
float belt_scale_y_angle{ 45.f };
|
float belt_scale_y_angle{ 45.f };
|
||||||
BeltScaleMode belt_scale_z{ BeltScaleMode::None };
|
BeltScaleMode belt_scale_z{ BeltScaleMode::None };
|
||||||
float belt_scale_z_angle{ 45.f };
|
float belt_scale_z_angle{ 45.f };
|
||||||
|
BeltRemapAxis belt_preslice_remap_x{ BeltRemapAxis::PosX };
|
||||||
|
BeltRemapAxis belt_preslice_remap_y{ BeltRemapAxis::PosY };
|
||||||
|
BeltRemapAxis belt_preslice_remap_z{ BeltRemapAxis::PosZ };
|
||||||
SettingsIds settings_ids;
|
SettingsIds settings_ids;
|
||||||
size_t filaments_count;
|
size_t filaments_count;
|
||||||
bool backtrace_enabled;
|
bool backtrace_enabled;
|
||||||
@@ -321,6 +324,9 @@ class Print;
|
|||||||
belt_scale_y_angle = other.belt_scale_y_angle;
|
belt_scale_y_angle = other.belt_scale_y_angle;
|
||||||
belt_scale_z = other.belt_scale_z;
|
belt_scale_z = other.belt_scale_z;
|
||||||
belt_scale_z_angle = other.belt_scale_z_angle;
|
belt_scale_z_angle = other.belt_scale_z_angle;
|
||||||
|
belt_preslice_remap_x = other.belt_preslice_remap_x;
|
||||||
|
belt_preslice_remap_y = other.belt_preslice_remap_y;
|
||||||
|
belt_preslice_remap_z = other.belt_preslice_remap_z;
|
||||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||||
time = other.time;
|
time = other.time;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -37,18 +37,40 @@ void GCodeWriter::set_build_volume_max(const Vec3d &max)
|
|||||||
m_build_vol_max = 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
|
Vec3d GCodeWriter::to_machine_coords(const Vec3d &pos) const
|
||||||
{
|
{
|
||||||
if (!is_belt_printer())
|
if (!is_belt_printer())
|
||||||
return pos;
|
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
|
// BeltRemapAxis: 0-2 = +X/+Y/+Z, 3-5 = -X/-Y/-Z, 6-8 = Rev X/Y/Z
|
||||||
auto remap = [this, &pos](int r) -> double {
|
auto remap = [this, &p](int r) -> double {
|
||||||
int axis = r % 3;
|
int axis = r % 3;
|
||||||
if (r < 3) return pos[axis];
|
if (r < 3) return p[axis];
|
||||||
if (r < 6) return -pos[axis];
|
if (r < 6) return -p[axis];
|
||||||
return m_build_vol_max[axis] - pos[axis];
|
return m_build_vol_max[axis] - p[axis];
|
||||||
};
|
};
|
||||||
return { remap(m_remap_x), remap(m_remap_y), remap(m_remap_z) };
|
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)
|
bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "GCode/CoolingBuffer.hpp"
|
#include "GCode/CoolingBuffer.hpp"
|
||||||
|
#include "GCode/BeltBackTransform.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
@@ -132,6 +133,10 @@ public:
|
|||||||
void set_axis_remap(int rx, int ry, int rz);
|
void set_axis_remap(int rx, int ry, int rz);
|
||||||
// Set build volume extents for Rev remap mode (max X, Y, Z).
|
// Set build volume extents for Rev remap mode (max X, Y, Z).
|
||||||
void set_build_volume_max(const Vec3d &max);
|
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.
|
// Transform a point from the slicing frame to machine coordinates.
|
||||||
Vec3d to_machine_coords(const Vec3d &pos) const;
|
Vec3d to_machine_coords(const Vec3d &pos) const;
|
||||||
|
|
||||||
@@ -194,6 +199,10 @@ public:
|
|||||||
int m_remap_y = 1;
|
int m_remap_y = 1;
|
||||||
int m_remap_z = 2;
|
int m_remap_z = 2;
|
||||||
Vec3d m_build_vol_max = Vec3d::Zero();
|
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;
|
double m_current_speed;
|
||||||
bool m_is_first_layer = true;
|
bool m_is_first_layer = true;
|
||||||
|
|
||||||
|
|||||||
@@ -1014,7 +1014,11 @@ static std::vector<std::string> s_Preset_printer_options {
|
|||||||
"belt_shear_y", "belt_shear_y_angle", "belt_shear_y_from", "belt_shear_y_global",
|
"belt_shear_y", "belt_shear_y_angle", "belt_shear_y_from", "belt_shear_y_global",
|
||||||
"belt_shear_z", "belt_shear_z_angle", "belt_shear_z_from", "belt_shear_z_global",
|
"belt_shear_z", "belt_shear_z_angle", "belt_shear_z_from", "belt_shear_z_global",
|
||||||
"belt_scale_x", "belt_scale_x_angle", "belt_scale_y", "belt_scale_y_angle", "belt_scale_z", "belt_scale_z_angle",
|
"belt_scale_x", "belt_scale_x_angle", "belt_scale_y", "belt_scale_y_angle", "belt_scale_z", "belt_scale_z_angle",
|
||||||
"belt_gcode_remap_x", "belt_gcode_remap_y", "belt_gcode_remap_z",
|
"belt_preslice_remap_x", "belt_preslice_remap_y", "belt_preslice_remap_z",
|
||||||
|
"belt_gcode_remap_x", "belt_gcode_remap_y", "belt_gcode_remap_z", "belt_gcode_back_transform",
|
||||||
|
"belt_origin_snap_x", "belt_origin_offset_x",
|
||||||
|
"belt_origin_snap_y", "belt_origin_offset_y",
|
||||||
|
"belt_origin_snap_z", "belt_origin_offset_z",
|
||||||
"belt_support_floor_offset", "belt_support_floor_mode", "belt_support_z_offset_mode",
|
"belt_support_floor_offset", "belt_support_floor_mode", "belt_support_z_offset_mode",
|
||||||
"gcode_flavor",
|
"gcode_flavor",
|
||||||
"fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs",
|
"fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs",
|
||||||
|
|||||||
+10
-1
@@ -103,6 +103,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
"belt_gcode_remap_x",
|
"belt_gcode_remap_x",
|
||||||
"belt_gcode_remap_y",
|
"belt_gcode_remap_y",
|
||||||
"belt_gcode_remap_z",
|
"belt_gcode_remap_z",
|
||||||
|
"belt_origin_snap_x", "belt_origin_offset_x",
|
||||||
|
"belt_origin_snap_y", "belt_origin_offset_y",
|
||||||
|
"belt_origin_snap_z", "belt_origin_offset_z",
|
||||||
//BBS
|
//BBS
|
||||||
"additional_cooling_fan_speed",
|
"additional_cooling_fan_speed",
|
||||||
"reduce_crossing_wall",
|
"reduce_crossing_wall",
|
||||||
@@ -280,6 +283,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
|
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
|
||||||
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
|
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
|
||||||
|| opt_key == "spiral_mode"
|
|| opt_key == "spiral_mode"
|
||||||
|
// Build plate tilt changes slicing plane orientation.
|
||||||
|
|| opt_key == "build_plate_tilt_x"
|
||||||
|
|| opt_key == "build_plate_tilt_y"
|
||||||
// Belt printer transform options change the mesh geometry before slicing.
|
// Belt printer transform options change the mesh geometry before slicing.
|
||||||
|| opt_key == "belt_printer"
|
|| opt_key == "belt_printer"
|
||||||
|| opt_key == "belt_printer_angle"
|
|| opt_key == "belt_printer_angle"
|
||||||
@@ -300,7 +306,10 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
|| opt_key == "belt_scale_y"
|
|| opt_key == "belt_scale_y"
|
||||||
|| opt_key == "belt_scale_y_angle"
|
|| opt_key == "belt_scale_y_angle"
|
||||||
|| opt_key == "belt_scale_z"
|
|| opt_key == "belt_scale_z"
|
||||||
|| opt_key == "belt_scale_z_angle") {
|
|| opt_key == "belt_scale_z_angle"
|
||||||
|
|| opt_key == "belt_preslice_remap_x"
|
||||||
|
|| opt_key == "belt_preslice_remap_y"
|
||||||
|
|| opt_key == "belt_preslice_remap_z") {
|
||||||
osteps.emplace_back(posSlice);
|
osteps.emplace_back(posSlice);
|
||||||
} else if (
|
} else if (
|
||||||
opt_key == "belt_support_floor_offset"
|
opt_key == "belt_support_floor_offset"
|
||||||
|
|||||||
@@ -186,6 +186,36 @@ class ConstSupportLayerPtrsAdaptor : public ConstVectorOfPtrsAdaptor<SupportLaye
|
|||||||
ConstSupportLayerPtrsAdaptor(const SupportLayerPtrs *data) : ConstVectorOfPtrsAdaptor<SupportLayer>(data) {}
|
ConstSupportLayerPtrsAdaptor(const SupportLayerPtrs *data) : ConstVectorOfPtrsAdaptor<SupportLayer>(data) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Returns the model's raw bounding box with pre-slice axis remap applied.
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
// Single instance of a PrintObject.
|
// Single instance of a PrintObject.
|
||||||
// As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z),
|
// As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z),
|
||||||
// ModelObject's instancess will be distributed among these multiple PrintObjects.
|
// ModelObject's instancess will be distributed among these multiple PrintObjects.
|
||||||
@@ -579,6 +609,7 @@ private:
|
|||||||
double m_belt_min_z { 0.0 };
|
double m_belt_min_z { 0.0 };
|
||||||
public:
|
public:
|
||||||
double belt_global_z_offset() const { return m_belt_global_z_offset; }
|
double belt_global_z_offset() const { return m_belt_global_z_offset; }
|
||||||
|
double belt_min_z() const { return m_belt_min_z; }
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6136,9 +6136,63 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->set_default_value(new ConfigOptionEnum<BeltRemapAxis>(default_axis));
|
def->set_default_value(new ConfigOptionEnum<BeltRemapAxis>(default_axis));
|
||||||
};
|
};
|
||||||
|
|
||||||
add_belt_remap("belt_gcode_remap_x", "X", "Which slicing axis maps to machine X in G-code output.", BeltRemapAxis::PosX);
|
add_belt_remap("belt_preslice_remap_x", "X",
|
||||||
add_belt_remap("belt_gcode_remap_y", "Y", "Which slicing axis maps to machine Y in G-code output.", BeltRemapAxis::PosY);
|
"Before slicing, which model-space axis becomes the slicer's X axis. "
|
||||||
add_belt_remap("belt_gcode_remap_z", "Z", "Which slicing axis maps to machine Z in G-code output.", BeltRemapAxis::PosZ);
|
"Use this to re-orient the coordinate system so the slicer's XY plane matches "
|
||||||
|
"your belt printer's physical bed plane. For a printer whose bed is in the XZ plane, "
|
||||||
|
"set Y to +Z and Z to +Y (or -Y) to swap the vertical and belt-travel axes. "
|
||||||
|
"Default +X: no change.",
|
||||||
|
BeltRemapAxis::PosX);
|
||||||
|
add_belt_remap("belt_preslice_remap_y", "Y",
|
||||||
|
"Before slicing, which model-space axis becomes the slicer's Y axis. "
|
||||||
|
"The slicer treats Y as one of the two horizontal bed axes. If your physical "
|
||||||
|
"belt surface runs along the Z axis, map Y to +Z here so the slicer slices "
|
||||||
|
"along the correct plane. Default +Y: no change.",
|
||||||
|
BeltRemapAxis::PosY);
|
||||||
|
add_belt_remap("belt_preslice_remap_z", "Z",
|
||||||
|
"Before slicing, which model-space axis becomes the slicer's Z axis (layer stacking direction). "
|
||||||
|
"The slicer builds layers upward along this axis. If your printer's layer-stacking "
|
||||||
|
"direction is the physical Y axis, map Z to +Y (or -Y for inverted direction). "
|
||||||
|
"Rev mode mirrors relative to the build volume maximum. Default +Z: no change.",
|
||||||
|
BeltRemapAxis::PosZ);
|
||||||
|
|
||||||
|
add_belt_remap("belt_gcode_remap_x", "X", "Which slicing axis maps to machine X in G-code output. Applied AFTER slicing, during G-code generation.", BeltRemapAxis::PosX);
|
||||||
|
add_belt_remap("belt_gcode_remap_y", "Y", "Which slicing axis maps to machine Y in G-code output. Applied AFTER slicing, during G-code generation.", BeltRemapAxis::PosY);
|
||||||
|
add_belt_remap("belt_gcode_remap_z", "Z", "Which slicing axis maps to machine Z in G-code output. Applied AFTER slicing, during G-code generation.", BeltRemapAxis::PosZ);
|
||||||
|
|
||||||
|
def = this->add("belt_gcode_back_transform", coBool);
|
||||||
|
def->label = L("G-code back-transform");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Reverse the shear/scale transform applied during slicing so G-code "
|
||||||
|
"coordinates are in the machine's physical coordinate space. "
|
||||||
|
"Requires at least one shear axis with global mode enabled.");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
auto add_belt_origin_snap = [this](const char *key_snap, const char *key_offset,
|
||||||
|
const char *axis_label) {
|
||||||
|
auto def = this->add(key_snap, coBool);
|
||||||
|
def->label = L(axis_label);
|
||||||
|
def->category = L("Printable space");
|
||||||
|
std::string tip = std::string("Shift G-code output so the object's bounding box minimum on machine ")
|
||||||
|
+ axis_label + " equals the offset value.";
|
||||||
|
def->tooltip = L(tip);
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
def = this->add(key_offset, coFloat);
|
||||||
|
def->label = L("Offset");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Target coordinate for the bounding box minimum on this machine axis.");
|
||||||
|
def->sidetext = L("mm");
|
||||||
|
def->min = -10000;
|
||||||
|
def->max = 10000;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(0));
|
||||||
|
};
|
||||||
|
add_belt_origin_snap("belt_origin_snap_x", "belt_origin_offset_x", "X");
|
||||||
|
add_belt_origin_snap("belt_origin_snap_y", "belt_origin_offset_y", "Y");
|
||||||
|
add_belt_origin_snap("belt_origin_snap_z", "belt_origin_offset_z", "Z");
|
||||||
|
|
||||||
// Belt support floor debug controls
|
// Belt support floor debug controls
|
||||||
def = this->add("belt_support_floor_offset", coFloat);
|
def = this->add("belt_support_floor_offset", coFloat);
|
||||||
|
|||||||
@@ -1487,9 +1487,19 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||||||
((ConfigOptionFloat, belt_scale_y_angle))
|
((ConfigOptionFloat, belt_scale_y_angle))
|
||||||
((ConfigOptionEnum<BeltScaleMode>, belt_scale_z))
|
((ConfigOptionEnum<BeltScaleMode>, belt_scale_z))
|
||||||
((ConfigOptionFloat, belt_scale_z_angle))
|
((ConfigOptionFloat, belt_scale_z_angle))
|
||||||
|
((ConfigOptionEnum<BeltRemapAxis>, belt_preslice_remap_x))
|
||||||
|
((ConfigOptionEnum<BeltRemapAxis>, belt_preslice_remap_y))
|
||||||
|
((ConfigOptionEnum<BeltRemapAxis>, belt_preslice_remap_z))
|
||||||
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_x))
|
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_x))
|
||||||
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_y))
|
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_y))
|
||||||
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_z))
|
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_z))
|
||||||
|
((ConfigOptionBool, belt_gcode_back_transform))
|
||||||
|
((ConfigOptionBool, belt_origin_snap_x))
|
||||||
|
((ConfigOptionFloat, belt_origin_offset_x))
|
||||||
|
((ConfigOptionBool, belt_origin_snap_y))
|
||||||
|
((ConfigOptionFloat, belt_origin_offset_y))
|
||||||
|
((ConfigOptionBool, belt_origin_snap_z))
|
||||||
|
((ConfigOptionFloat, belt_origin_offset_z))
|
||||||
((ConfigOptionFloat, belt_support_floor_offset))
|
((ConfigOptionFloat, belt_support_floor_offset))
|
||||||
((ConfigOptionEnum<BeltSupportFloorMode>, belt_support_floor_mode))
|
((ConfigOptionEnum<BeltSupportFloorMode>, belt_support_floor_mode))
|
||||||
((ConfigOptionEnum<BeltSupportZOffsetMode>, belt_support_z_offset_mode))
|
((ConfigOptionEnum<BeltSupportZOffsetMode>, belt_support_z_offset_mode))
|
||||||
|
|||||||
@@ -3402,9 +3402,16 @@ void PrintObject::update_slicing_parameters()
|
|||||||
double belt_floor_shear_factor_out = 0.0;
|
double belt_floor_shear_factor_out = 0.0;
|
||||||
int belt_floor_from_axis_out = 1;
|
int belt_floor_from_axis_out = 1;
|
||||||
double belt_floor_z_shift_out = 0.0;
|
double belt_floor_z_shift_out = 0.0;
|
||||||
// Belt shear/scale may change the effective Z height.
|
// Belt shear/scale/pre-remap may change the effective Z height.
|
||||||
const auto &pcfg = this->print()->config();
|
const auto &pcfg = this->print()->config();
|
||||||
if (pcfg.belt_printer.value) {
|
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)
|
||||||
|
object_height = bb.size().z();
|
||||||
|
|
||||||
bool has_z_shear = pcfg.belt_shear_z.value != BeltShearMode::None;
|
bool has_z_shear = pcfg.belt_shear_z.value != BeltShearMode::None;
|
||||||
bool has_z_scale = pcfg.belt_scale_z.value != BeltScaleMode::None;
|
bool has_z_scale = pcfg.belt_scale_z.value != BeltScaleMode::None;
|
||||||
if (has_z_shear || has_z_scale) {
|
if (has_z_shear || has_z_scale) {
|
||||||
@@ -3435,7 +3442,6 @@ void PrintObject::update_slicing_parameters()
|
|||||||
double scale_z = compute_scale_factor(pcfg.belt_scale_z.value, pcfg.belt_scale_z_angle.value);
|
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) {
|
if (has_z_shear && std::abs(shear_factor) > EPSILON) {
|
||||||
int from = int(pcfg.belt_shear_z_from.value);
|
int from = int(pcfg.belt_shear_z_from.value);
|
||||||
BoundingBoxf3 bb = this->model_object()->raw_bounding_box();
|
|
||||||
double min_rz = std::numeric_limits<double>::max();
|
double min_rz = std::numeric_limits<double>::max();
|
||||||
double max_rz = std::numeric_limits<double>::lowest();
|
double max_rz = std::numeric_limits<double>::lowest();
|
||||||
for (double vz : {bb.min.z(), bb.max.z()})
|
for (double vz : {bb.min.z(), bb.max.z()})
|
||||||
@@ -3507,8 +3513,15 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
|
|||||||
if (object_max_z <= 0.f) {
|
if (object_max_z <= 0.f) {
|
||||||
BoundingBoxf3 bb = model_object.raw_bounding_box();
|
BoundingBoxf3 bb = model_object.raw_bounding_box();
|
||||||
object_max_z = (float)bb.size().z();
|
object_max_z = (float)bb.size().z();
|
||||||
// Belt shear/scale may change the effective Z height.
|
// Belt pre-remap/shear/scale may change the effective Z height.
|
||||||
if (print_config.belt_printer.value) {
|
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)
|
||||||
|
object_max_z = (float)bb.size().z();
|
||||||
|
|
||||||
bool has_z_shear = print_config.belt_shear_z.value != BeltShearMode::None;
|
bool has_z_shear = print_config.belt_shear_z.value != BeltShearMode::None;
|
||||||
bool has_z_scale = print_config.belt_scale_z.value != BeltScaleMode::None;
|
bool has_z_scale = print_config.belt_scale_z.value != BeltScaleMode::None;
|
||||||
if (has_z_shear || has_z_scale) {
|
if (has_z_shear || has_z_scale) {
|
||||||
|
|||||||
@@ -143,6 +143,58 @@ static std::vector<VolumeSlices> slice_volumes_inner(
|
|||||||
params_base.extra_offset = 0;
|
params_base.extra_offset = 0;
|
||||||
params_base.trafo = object_trafo;
|
params_base.trafo = object_trafo;
|
||||||
if (print_config.belt_printer.value) {
|
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.
|
// Build per-axis shear matrix from 3 independent axis configs.
|
||||||
auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double {
|
auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double {
|
||||||
double angle_rad = Geometry::deg2rad(angle_deg);
|
double angle_rad = Geometry::deg2rad(angle_deg);
|
||||||
@@ -204,11 +256,12 @@ static std::vector<VolumeSlices> slice_volumes_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply: scale * shear * trafo (shear first, then scale).
|
// Apply: scale * shear * trafo (shear first, then scale).
|
||||||
if (has_shear || has_scale) {
|
if (has_shear || has_scale)
|
||||||
params_base.trafo = belt_scale * belt_shear * params_base.trafo;
|
params_base.trafo = belt_scale * belt_shear * params_base.trafo;
|
||||||
|
|
||||||
// After the shear/scale transform, the mesh may clip through the
|
// After pre-remap/shear/scale, the mesh may clip through the build
|
||||||
// build plate (Z < 0). Detect this and shift the mesh up.
|
// 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;
|
Transform3d combined = params_base.trafo;
|
||||||
double min_z = std::numeric_limits<double>::max();
|
double min_z = std::numeric_limits<double>::max();
|
||||||
for (const ModelVolume *mv : model_volumes) {
|
for (const ModelVolume *mv : model_volumes) {
|
||||||
@@ -893,17 +946,34 @@ void PrintObject::slice()
|
|||||||
this->slice_volumes();
|
this->slice_volumes();
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
|
|
||||||
// After slicing, m_belt_min_z holds the exact post-shear minimum Z
|
// Belt floor Z-shift: where is the belt surface in final slicer space?
|
||||||
// in trafo_centered space (which includes the ensure_on_bed Z offset).
|
//
|
||||||
// The belt surface is at Z=0 in trafo_centered space; after shear it
|
// The belt surface is at model_Y=0 (XZ belt plane). After the full
|
||||||
// becomes Z = sf*Y, and after the z-shift that keeps the mesh above
|
// pipeline (trafo_centered → pre_remap → shear → z_shift), the belt
|
||||||
// Z=0 it becomes Z = sf*Y + z_shift_val. So belt_floor_z_shift is
|
// surface equation in slicer space is:
|
||||||
// simply the z-shift applied, i.e. max(0, -m_belt_min_z).
|
// Z_belt = sf * from_axis + belt_surface_z_centered + z_shift_val
|
||||||
// NOTE: do NOT add raw_bounding_box().min.z() here — m_belt_min_z
|
//
|
||||||
// already includes the ensure_on_bed offset, unlike the min_rz used
|
// belt_surface_z_centered = remapped_bbox.min.z() (the Z position of
|
||||||
// in update_slicing_parameters() which needs that compensation.
|
// the belt surface in centered-pre-shear slicer space, which is 0
|
||||||
|
// without pre-remap but nonzero when e.g. Y↔Z swap shifts the belt
|
||||||
|
// surface away from Z=0 by the centering offset).
|
||||||
|
//
|
||||||
|
// z_shift_val = max(0, -m_belt_min_z) (lifts mesh above Z=0).
|
||||||
|
//
|
||||||
|
// So: belt_floor_z_shift = remapped_bb.min.z() + z_shift_val
|
||||||
if (std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON) {
|
if (std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON) {
|
||||||
m_slicing_params.belt_floor_z_shift = (m_belt_min_z < 0.) ? -m_belt_min_z : 0.;
|
double z_shift_val = (m_belt_min_z < 0.) ? -m_belt_min_z : 0.;
|
||||||
|
// With pre-remap, the belt surface (model_Y=0) may not be at Z=0 in
|
||||||
|
// centered slicer space — add the remapped bbox min Z to compensate.
|
||||||
|
// 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.;
|
||||||
|
m_slicing_params.belt_floor_z_shift = belt_surface_z + z_shift_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
int firstLayerReplacedBy = 0;
|
int firstLayerReplacedBy = 0;
|
||||||
@@ -989,14 +1059,23 @@ void PrintObject::slice()
|
|||||||
const auto &za = gaxes[2]; // Z row
|
const auto &za = gaxes[2]; // Z row
|
||||||
if (za.global && za.mode != BeltShearMode::None && za.from < 2) {
|
if (za.global && za.mode != BeltShearMode::None && za.from < 2) {
|
||||||
double factor = compute_shear_factor(za.mode, za.angle);
|
double factor = compute_shear_factor(za.mode, za.angle);
|
||||||
// The Z-shift brought the mesh's lowest sheared vertex to
|
// The global Z offset accounts for the instance's position-
|
||||||
// Z=0. That vertex's physical Y determines the belt contact
|
// dependent shear contribution. m_belt_min_z is the minimum Z
|
||||||
// point. With trafo_z preserved (ensure_on_bed offset),
|
// of the mesh after pre_remap + shear + trafo_centered, which
|
||||||
// min_z = Y_at_contact * factor for bottom-face vertices,
|
// includes the centering offset on the remapped Z axis.
|
||||||
// so: z_offset = center_Y * factor + min_z.
|
// 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 shear_min_z = m_belt_min_z - belt_surface_z;
|
||||||
Point phys = inst_shift; // already has center_offset subtracted
|
Point phys = inst_shift; // already has center_offset subtracted
|
||||||
double center_on_axis = (za.from == 0) ? unscale<double>(phys.x()) : unscale<double>(phys.y());
|
double center_on_axis = (za.from == 0) ? unscale<double>(phys.x()) : unscale<double>(phys.y());
|
||||||
global_z_offset += center_on_axis * factor + m_belt_min_z;
|
global_z_offset += center_on_axis * factor + shear_min_z;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "Belt global: z_offset=" << global_z_offset
|
BOOST_LOG_TRIVIAL(warning) << "Belt global: z_offset=" << global_z_offset
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ TreeModelVolumes::TreeModelVolumes(
|
|||||||
double belt_sf = sp2.belt_floor_shear_factor;
|
double belt_sf = sp2.belt_floor_shear_factor;
|
||||||
if (std::abs(belt_sf) > EPSILON && std::abs(print_object.belt_global_z_offset()) > EPSILON
|
if (std::abs(belt_sf) > EPSILON && std::abs(print_object.belt_global_z_offset()) > EPSILON
|
||||||
&& pcfg2.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
|
&& pcfg2.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
|
||||||
double bb_min_z = std::abs(print_object.model_object()->raw_bounding_box().min.z());
|
double bb_min_z = std::abs(belt_remapped_bbox(*print_object.model_object(), pcfg2).min.z());
|
||||||
double extra_depth = bb_min_z + 10.;
|
double extra_depth = bb_min_z + 10.;
|
||||||
int num_extra = std::max(0, (int)std::ceil(extra_depth / sp2.layer_height));
|
int num_extra = std::max(0, (int)std::ceil(extra_depth / sp2.layer_height));
|
||||||
if (num_extra > 0) {
|
if (num_extra > 0) {
|
||||||
|
|||||||
@@ -1733,6 +1733,99 @@ void TreeSupport::generate()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Belt floor: extend support below the object's first layer by creating
|
||||||
|
// additional support layers with geometry copied from the lowest content
|
||||||
|
// 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
|
||||||
|
&& 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;
|
||||||
|
// Find the lowest non-empty, non-brim support layer.
|
||||||
|
ExPolygons source_areas;
|
||||||
|
double source_z = 0;
|
||||||
|
int layers_with_content = 0;
|
||||||
|
for (size_t i = 0; i < m_object->support_layer_count(); ++i) {
|
||||||
|
SupportLayer *sl = m_object->get_support_layer(i);
|
||||||
|
if (sl && !sl->base_areas.empty()) {
|
||||||
|
layers_with_content++;
|
||||||
|
if (layers_with_content >= 2) {
|
||||||
|
source_areas = sl->base_areas;
|
||||||
|
source_z = sl->print_z;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to first content layer.
|
||||||
|
if (source_areas.empty()) {
|
||||||
|
for (size_t i = 0; i < m_object->support_layer_count(); ++i) {
|
||||||
|
SupportLayer *sl = m_object->get_support_layer(i);
|
||||||
|
if (sl && !sl->base_areas.empty()) {
|
||||||
|
source_areas = sl->base_areas;
|
||||||
|
source_z = sl->print_z;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 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
|
||||||
|
// distance) + 10mm safety margin. The 10mm is a bodge to avoid
|
||||||
|
// small cutoff artifacts — ideally computed exactly from belt geometry.
|
||||||
|
double extra_depth = std::min(from_extent + bb_min_z + 10., std::max(0., first_z));
|
||||||
|
int num_extra = std::max(0, (int)std::ceil(extra_depth / sp.layer_height));
|
||||||
|
ExPolygons prev_areas = source_areas;
|
||||||
|
// Build belt extension layers (lowest Z first).
|
||||||
|
SupportLayerPtrs belt_ext_layers;
|
||||||
|
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});
|
||||||
|
if (clipped.empty()) continue;
|
||||||
|
SupportLayer *sl = new SupportLayer(0, 0, m_object, sp.layer_height, print_z, -1);
|
||||||
|
sl->base_areas = clipped;
|
||||||
|
// Populate area_groups — generate_toolpaths() iterates these,
|
||||||
|
// not base_areas directly.
|
||||||
|
for (auto &expoly : sl->base_areas)
|
||||||
|
sl->area_groups.emplace_back(&expoly, SupportLayer::BaseType, 0);
|
||||||
|
sl->lslices = clipped;
|
||||||
|
sl->lslices_bboxes.reserve(clipped.size());
|
||||||
|
for (const ExPolygon &ep : clipped)
|
||||||
|
sl->lslices_bboxes.emplace_back(get_extents(ep));
|
||||||
|
belt_ext_layers.push_back(sl);
|
||||||
|
}
|
||||||
|
// Insert at the front of support_layers (they're already in Z order).
|
||||||
|
if (!belt_ext_layers.empty()) {
|
||||||
|
auto &sl_vec = m_object->support_layers();
|
||||||
|
sl_vec.insert(sl_vec.begin(), belt_ext_layers.begin(), belt_ext_layers.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
profiler.stage_start(STAGE_GENERATE_TOOLPATHS);
|
profiler.stage_start(STAGE_GENERATE_TOOLPATHS);
|
||||||
m_object->print()->set_status(70, _u8L("Generating support"));
|
m_object->print()->set_status(70, _u8L("Generating support"));
|
||||||
generate_toolpaths();
|
generate_toolpaths();
|
||||||
|
|||||||
@@ -3381,7 +3381,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
|
|||||||
// termination happen inside the belt region and get clipped.
|
// termination happen inside the belt region and get clipped.
|
||||||
// Use the distance from the pre-shear bbox min Z to the part's
|
// Use the distance from the pre-shear bbox min Z to the part's
|
||||||
// post-shear min Z, plus 10mm for base expansion headroom.
|
// post-shear min Z, plus 10mm for base expansion headroom.
|
||||||
double bb_min_z = std::abs(po.model_object()->raw_bounding_box().min.z());
|
double bb_min_z = std::abs(belt_remapped_bbox(*po.model_object(), pcfg).min.z());
|
||||||
double extra_depth = bb_min_z + 10.;
|
double extra_depth = bb_min_z + 10.;
|
||||||
int num_extra = std::max(0, (int)std::ceil(extra_depth / sp.layer_height));
|
int num_extra = std::max(0, (int)std::ceil(extra_depth / sp.layer_height));
|
||||||
if (num_extra > 0) {
|
if (num_extra > 0) {
|
||||||
|
|||||||
+37
-2
@@ -4432,12 +4432,41 @@ void TabPrinter::build_fff()
|
|||||||
optgroup->append_line(line);
|
optgroup->append_line(line);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
Line line = { L("G-code axis remap"), L("Remap slicing-frame axes to machine axes in G-code output") };
|
Line line = { L("Pre-slice axis remap"),
|
||||||
|
L("Remap model axes before slicing so the slicer's coordinate system matches "
|
||||||
|
"the physical bed orientation. For belt printers whose bed is NOT in the XY plane, "
|
||||||
|
"use this to swap axes so layers are stacked in the correct physical direction.") };
|
||||||
|
line.append_option(optgroup->get_option("belt_preslice_remap_x"));
|
||||||
|
line.append_option(optgroup->get_option("belt_preslice_remap_y"));
|
||||||
|
line.append_option(optgroup->get_option("belt_preslice_remap_z"));
|
||||||
|
optgroup->append_line(line);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Line line = { L("G-code axis remap (post-slice)"), L("Remap slicing-frame axes to machine axes in G-code output. Applied AFTER slicing, during G-code generation.") };
|
||||||
line.append_option(optgroup->get_option("belt_gcode_remap_x"));
|
line.append_option(optgroup->get_option("belt_gcode_remap_x"));
|
||||||
line.append_option(optgroup->get_option("belt_gcode_remap_y"));
|
line.append_option(optgroup->get_option("belt_gcode_remap_y"));
|
||||||
line.append_option(optgroup->get_option("belt_gcode_remap_z"));
|
line.append_option(optgroup->get_option("belt_gcode_remap_z"));
|
||||||
optgroup->append_line(line);
|
optgroup->append_line(line);
|
||||||
}
|
}
|
||||||
|
optgroup->append_single_option_line("belt_gcode_back_transform");
|
||||||
|
{
|
||||||
|
Line line = { L("Origin snap X"), L("Snap object bbox min X to offset in G-code output") };
|
||||||
|
line.append_option(optgroup->get_option("belt_origin_snap_x"));
|
||||||
|
line.append_option(optgroup->get_option("belt_origin_offset_x"));
|
||||||
|
optgroup->append_line(line);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Line line = { L("Origin snap Y"), L("Snap object bbox min Y to offset in G-code output") };
|
||||||
|
line.append_option(optgroup->get_option("belt_origin_snap_y"));
|
||||||
|
line.append_option(optgroup->get_option("belt_origin_offset_y"));
|
||||||
|
optgroup->append_line(line);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Line line = { L("Origin snap Z"), L("Snap object bbox min Z to offset in G-code output") };
|
||||||
|
line.append_option(optgroup->get_option("belt_origin_snap_z"));
|
||||||
|
line.append_option(optgroup->get_option("belt_origin_offset_z"));
|
||||||
|
optgroup->append_line(line);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
Line line = { L("Support floor"), L("Belt floor awareness for support generation and clipping") };
|
Line line = { L("Support floor"), L("Belt floor awareness for support generation and clipping") };
|
||||||
line.append_option(optgroup->get_option("belt_support_floor_mode"));
|
line.append_option(optgroup->get_option("belt_support_floor_mode"));
|
||||||
@@ -5292,7 +5321,9 @@ void TabPrinter::toggle_options()
|
|||||||
toggle_line("belt_printer_infinite_y", is_belt);
|
toggle_line("belt_printer_infinite_y", is_belt);
|
||||||
for (auto el : {"belt_shear_x", "belt_shear_y", "belt_shear_z",
|
for (auto el : {"belt_shear_x", "belt_shear_y", "belt_shear_z",
|
||||||
"belt_scale_x", "belt_scale_y", "belt_scale_z",
|
"belt_scale_x", "belt_scale_y", "belt_scale_z",
|
||||||
"belt_gcode_remap_x"})
|
"belt_preslice_remap_x",
|
||||||
|
"belt_gcode_remap_x", "belt_gcode_back_transform",
|
||||||
|
"belt_origin_snap_x", "belt_origin_snap_y", "belt_origin_snap_z"})
|
||||||
toggle_line(el, is_belt);
|
toggle_line(el, is_belt);
|
||||||
|
|
||||||
// Gray out angle/from sub-options when their parent shear/scale mode is None.
|
// Gray out angle/from sub-options when their parent shear/scale mode is None.
|
||||||
@@ -5320,6 +5351,10 @@ void TabPrinter::toggle_options()
|
|||||||
auto scz = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_z")->value;
|
auto scz = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_z")->value;
|
||||||
toggle_option("belt_scale_z_angle", is_belt && scz != BeltScaleMode::None);
|
toggle_option("belt_scale_z_angle", is_belt && scz != BeltScaleMode::None);
|
||||||
|
|
||||||
|
toggle_option("belt_origin_offset_x", is_belt && m_config->opt_bool("belt_origin_snap_x"));
|
||||||
|
toggle_option("belt_origin_offset_y", is_belt && m_config->opt_bool("belt_origin_snap_y"));
|
||||||
|
toggle_option("belt_origin_offset_z", is_belt && m_config->opt_bool("belt_origin_snap_z"));
|
||||||
|
|
||||||
toggle_line("belt_support_floor_mode", is_belt);
|
toggle_line("belt_support_floor_mode", is_belt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user