add global mesh transform option

This commit is contained in:
harrierpigeon
2026-04-09 23:48:43 -05:00
parent 1e9ee0c120
commit 0703728e56
11 changed files with 135 additions and 40 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "Custom Printer",
"version": "02.03.02.61",
"version": "02.03.02.62",
"force_update": "0",
"description": "My configurations",
"machine_model_list": [

View File

@@ -84,11 +84,13 @@
],
"preslice_remap_y": "pos_z",
"preslice_remap_z": "pos_y",
"preslice_remap_global": "1",
"printer_extruder_id": [
"1"
],
"belt_origin_snap_y": "1",
"belt_printer": "1",
"belt_preslice_global": "1",
"belt_shear_z": "pos_cot",
"belt_shear_z_angle": "30",
"build_plate_tilt_x": "45",

View File

@@ -56,10 +56,45 @@ void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print)
file.write_format("; preslice_remap_x = %s\n", full_cfg.opt_serialize("preslice_remap_x").c_str());
file.write_format("; preslice_remap_y = %s\n", full_cfg.opt_serialize("preslice_remap_y").c_str());
file.write_format("; preslice_remap_z = %s\n", full_cfg.opt_serialize("preslice_remap_z").c_str());
file.write_format("; preslice_remap_global = %d\n", print.config().preslice_remap_global.value ? 1 : 0);
file.write_format("; belt_preslice_global = %d\n", print.config().belt_preslice_global.value ? 1 : 0);
}
void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift)
{
// Global pre-slice mode: adjust origin using computed correction.
// Transform the origin through the belt pipeline so that
// back_transform(T * origin) = origin (correct machine position).
// This replaces the bbox-based axis snap with an exact formula.
//
// Two flags trigger this path:
// belt_preslice_global — full pipeline (scale * shear * remap) is global
// preslice_remap_global — only the pre-slice remap is global
// The XY origin adjustment uses the FULL forward transform either way,
// because the back_transform applied during G-code emission is always the
// inverse of the full pipeline. When only the remap is configured, both
// flags produce identical math (T == R).
bool use_global = m_config.belt_preslice_global.value
|| (m_config.preslice_remap_global.value
&& BeltTransformPipeline::has_preslice_remap(m_config));
if (use_global && m_config.belt_printer.value) {
auto *belt_writer = dynamic_cast<BeltGCodeWriter*>(m_writer.get());
if (belt_writer) {
// Clear snap — not needed with computed corrections
for (int a = 0; a < 3; ++a)
belt_writer->set_origin_snap(a, false, 0., 0.);
}
// Adjust origin: transform through belt forward pipeline so that
// the back-transform correctly recovers model-space positions.
Transform3d T = BeltTransformPipeline::build_forward_transform(m_config);
Vec2d cur_origin = this->origin();
Vec3d origin3d(cur_origin.x(), cur_origin.y(), 0.);
Vec3d adjusted = T.linear() * origin3d;
this->set_origin(Vec2d(adjusted.x(), adjusted.y()));
return;
}
if (!m_origin_snap[0] && !m_origin_snap[1] && !m_origin_snap[2])
return;

View File

@@ -15,7 +15,9 @@ bool BeltBackTransform::init_from_config(const PrintConfig &config)
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 && !BeltTransformPipeline::has_preslice_remap(config))
bool has_preslice_global = config.belt_preslice_global.value
|| config.preslice_remap_global.value;
if (!has_global_shear && !has_preslice_global && !BeltTransformPipeline::has_preslice_remap(config))
return false;
// Build the forward pipeline (scale * shear * pre_remap) and store its inverse.

View File

@@ -1014,8 +1014,9 @@ 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_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",
"preslice_remap_x", "preslice_remap_y", "preslice_remap_z",
"preslice_remap_x", "preslice_remap_y", "preslice_remap_z", "preslice_remap_global",
"gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "gcode_back_transform",
"belt_preslice_global",
"belt_origin_snap_x", "belt_origin_offset_x",
"belt_origin_snap_y", "belt_origin_offset_y",
"belt_origin_snap_z", "belt_origin_offset_z",

View File

@@ -585,9 +585,12 @@ private:
double m_belt_global_z_offset { 0.0 };
// Belt printer: min_z of mesh after belt shear (before Z-shift), for z_offset calc.
double m_belt_min_z { 0.0 };
// Belt printer: XY correction from global pre-slice mode, applied to G-code origin.
Vec2d m_belt_global_xy_correction { Vec2d::Zero() };
public:
double belt_global_z_offset() const { return m_belt_global_z_offset; }
double belt_min_z() const { return m_belt_min_z; }
Vec2d belt_global_xy_correction() const { return m_belt_global_xy_correction; }
private:

View File

@@ -1,6 +1,7 @@
#include "ClipperUtils.hpp"
#include "Model.hpp"
#include "Print.hpp"
#include "BeltTransform.hpp"
#include <boost/log/trivial.hpp>
#include <cfloat>
@@ -1520,8 +1521,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Orca: Updated for XYZ filament shrink compensation
// Belt global mode: force each instance into its own PrintObject
// so each gets independent layer Z values.
bool belt_force_separate = m_config.belt_printer.value && m_config.belt_shear_z_global.value
&& m_config.belt_shear_z.value != BeltShearMode::None;
bool belt_force_separate = m_config.belt_printer.value && (
(m_config.belt_shear_z_global.value && m_config.belt_shear_z.value != BeltShearMode::None)
|| m_config.belt_preslice_global.value
|| (m_config.preslice_remap_global.value && BeltTransformPipeline::has_preslice_remap(m_config)));
model_object_status.print_instances = print_objects_from_model_object(*model_object, this->shrinkage_compensation(), belt_force_separate);
std::vector<const PrintObjectStatus*> old;
old.reserve(print_object_status_db.count(*model_object));
@@ -1612,8 +1615,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// min_shift across all objects, so one move affects everyone).
if (belt_instances_shifted
&& m_config.belt_printer.value
&& m_config.belt_shear_z_global.value
&& m_config.belt_shear_z.value != BeltShearMode::None) {
&& ((m_config.belt_shear_z_global.value && m_config.belt_shear_z.value != BeltShearMode::None)
|| m_config.belt_preslice_global.value
|| (m_config.preslice_remap_global.value && BeltTransformPipeline::has_preslice_remap(m_config)))) {
for (PrintObject *object : m_objects)
update_apply_status(object->invalidate_step(posSlice));
}

View File

@@ -6156,6 +6156,16 @@ void PrintConfigDef::init_fff_params()
"Rev mode mirrors relative to the build volume maximum. Default +Z: no change.",
RemapAxis::PosZ);
def = this->add("preslice_remap_global", coBool);
def->label = L("Global");
def->category = L("Printable space");
def->tooltip = L("When enabled, the pre-slice axis remap accounts for each object's bed position. "
"Without this, the remap is applied locally around each object's center, so "
"objects at different positions don't get a position-dependent contribution. "
"Mirrors the per-axis 'Global' option on belt mesh shears, but for the remap.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
add_belt_remap("gcode_remap_x", "X", "Which slicing axis maps to machine X in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosX);
add_belt_remap("gcode_remap_y", "Y", "Which slicing axis maps to machine Y in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosY);
add_belt_remap("gcode_remap_z", "Z", "Which slicing axis maps to machine Z in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosZ);
@@ -6169,6 +6179,15 @@ void PrintConfigDef::init_fff_params()
def->mode = comSimple; // Visibility controlled by toggle_line in Tab.cpp
def->set_default_value(new ConfigOptionBool(false));
def = this->add("belt_preslice_global", coBool);
def->label = L("Global mesh transforms");
def->category = L("Printable space");
def->tooltip = L("When enabled, pre-slice belt transforms (remap, shear, scale) account for "
"each object's bed position, producing correct machine coordinates without "
"relying on origin snap. Each instance gets its own PrintObject.");
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);

View File

@@ -1490,10 +1490,12 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionEnum<RemapAxis>, preslice_remap_x))
((ConfigOptionEnum<RemapAxis>, preslice_remap_y))
((ConfigOptionEnum<RemapAxis>, preslice_remap_z))
((ConfigOptionBool, preslice_remap_global))
((ConfigOptionEnum<RemapAxis>, gcode_remap_x))
((ConfigOptionEnum<RemapAxis>, gcode_remap_y))
((ConfigOptionEnum<RemapAxis>, gcode_remap_z))
((ConfigOptionBool, gcode_back_transform))
((ConfigOptionBool, belt_preslice_global))
((ConfigOptionBool, belt_origin_snap_x))
((ConfigOptionFloat, belt_origin_offset_x))
((ConfigOptionBool, belt_origin_snap_y))

View File

@@ -906,6 +906,7 @@ void PrintObject::slice()
BOOST_LOG_TRIVIAL(warning) << "Belt global check: belt_printer=" << pcfg.belt_printer.value
<< " belt_shear_z=" << int(pcfg.belt_shear_z.value)
<< " belt_shear_z_global=" << pcfg.belt_shear_z_global.value
<< " belt_preslice_global=" << pcfg.belt_preslice_global.value
<< " object=" << this->model_object()->name;
if (pcfg.belt_printer.value) {
@@ -916,37 +917,53 @@ void PrintObject::slice()
<< " shift=(" << unscale<double>(inst_shift.x()) << ", " << unscale<double>(inst_shift.y()) << ")";
double global_z_offset = 0.;
struct GAxis { BeltShearMode mode; double angle; int from; bool global; };
GAxis gaxes[3] = {
{ pcfg.belt_shear_x.value, pcfg.belt_shear_x_angle.value, int(pcfg.belt_shear_x_from.value), pcfg.belt_shear_x_global.value },
{ pcfg.belt_shear_y.value, pcfg.belt_shear_y_angle.value, int(pcfg.belt_shear_y_from.value), pcfg.belt_shear_y_global.value },
{ pcfg.belt_shear_z.value, pcfg.belt_shear_z_angle.value, int(pcfg.belt_shear_z_from.value), pcfg.belt_shear_z_global.value },
};
if (pcfg.belt_preslice_global.value) {
// Global pre-slice mode: compute full correction c = (T.linear() - I) * d
// where T is the belt forward transform and d is the bed position.
Transform3d T = BeltTransformPipeline::build_forward_transform(pcfg);
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
Vec3d c = T.linear() * d - d;
// Only the Z-row shear contributes a Z offset from global mode.
// (X/Y row shears with global would offset X/Y, not Z — not useful here.)
// Offsets are RELATIVE: we subtract the minimum shift across all
// 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 = 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
// includes the centering offset on the remapped Z axis.
// 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).
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());
global_z_offset += center_on_axis * factor + shear_min_z;
global_z_offset = c.z();
m_belt_global_xy_correction = Vec2d(c.x(), c.y());
BOOST_LOG_TRIVIAL(warning) << "Belt preslice_global: correction=("
<< c.x() << ", " << c.y() << ", " << c.z() << ")";
} else {
struct GAxis { BeltShearMode mode; double angle; int from; bool global; };
GAxis gaxes[3] = {
{ pcfg.belt_shear_x.value, pcfg.belt_shear_x_angle.value, int(pcfg.belt_shear_x_from.value), pcfg.belt_shear_x_global.value },
{ pcfg.belt_shear_y.value, pcfg.belt_shear_y_angle.value, int(pcfg.belt_shear_y_from.value), pcfg.belt_shear_y_global.value },
{ pcfg.belt_shear_z.value, pcfg.belt_shear_z_angle.value, int(pcfg.belt_shear_z_from.value), pcfg.belt_shear_z_global.value },
};
// Only the Z-row shear contributes a Z offset from global mode.
// (X/Y row shears with global would offset X/Y, not Z — not useful here.)
const auto &za = gaxes[2]; // Z row
if (za.global && za.mode != BeltShearMode::None && za.from < 2) {
double factor = BeltTransformPipeline::compute_shear_factor(za.mode, za.angle);
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;
double center_on_axis = (za.from == 0) ? unscale<double>(phys.x()) : unscale<double>(phys.y());
global_z_offset += center_on_axis * factor + shear_min_z;
}
// Pre-slice remap global mode: when on, the remap accounts for the
// instance bed position. The Z component of the correction
// (R - I) * d shifts layer print_z so e.g. a Y↔Z swap with an
// object at Y=50 prints at Z=50.
if (pcfg.preslice_remap_global.value
&& BeltTransformPipeline::has_preslice_remap(pcfg)) {
Transform3d R = BeltTransformPipeline::build_preslice_remap(pcfg);
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
Vec3d remap_correction = R.linear() * d - d;
global_z_offset += remap_correction.z();
}
}
BOOST_LOG_TRIVIAL(warning) << "Belt global: z_offset=" << global_z_offset
<< " za.global=" << za.global << " za.mode=" << int(za.mode) << " za.from=" << za.from
<< " (relative to min across " << this->print()->objects().size() << " objects)";
m_belt_global_z_offset = global_z_offset;
if (std::abs(global_z_offset) > EPSILON) {

View File

@@ -4439,6 +4439,7 @@ void TabPrinter::build_fff()
line.append_option(optgroup->get_option("preslice_remap_x"));
line.append_option(optgroup->get_option("preslice_remap_y"));
line.append_option(optgroup->get_option("preslice_remap_z"));
line.append_option(optgroup->get_option("preslice_remap_global"));
optgroup->append_line(line);
}
{
@@ -4448,6 +4449,7 @@ void TabPrinter::build_fff()
line.append_option(optgroup->get_option("gcode_remap_z"));
optgroup->append_line(line);
}
optgroup->append_single_option_line("belt_preslice_global");
optgroup->append_single_option_line("gcode_back_transform");
{
Line line = { L("Origin snap X"), L("Snap object bbox min X to offset in G-code output") };
@@ -5328,22 +5330,29 @@ void TabPrinter::toggle_options()
bool show_remap = is_belt || (m_mode >= comDevelop);
for (auto el : {"preslice_remap_x", "gcode_remap_x", "gcode_back_transform"})
toggle_line(el, show_remap);
toggle_line("belt_preslice_global", show_remap);
bool belt_global = is_belt && m_config->opt_bool("belt_preslice_global");
// preslice_remap_global: superseded by belt_preslice_global
toggle_option("preslice_remap_global", show_remap && !belt_global);
// Gray out angle/from sub-options when their parent shear/scale mode is None.
// Per-axis globals are superseded when belt_preslice_global is on.
auto sx = m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_x")->value;
toggle_option("belt_shear_x_angle", is_belt && sx != BeltShearMode::None);
toggle_option("belt_shear_x_from", is_belt && sx != BeltShearMode::None);
toggle_option("belt_shear_x_global", is_belt && sx != BeltShearMode::None);
toggle_option("belt_shear_x_global", is_belt && sx != BeltShearMode::None && !belt_global);
auto sy = m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_y")->value;
toggle_option("belt_shear_y_angle", is_belt && sy != BeltShearMode::None);
toggle_option("belt_shear_y_from", is_belt && sy != BeltShearMode::None);
toggle_option("belt_shear_y_global", is_belt && sy != BeltShearMode::None);
toggle_option("belt_shear_y_global", is_belt && sy != BeltShearMode::None && !belt_global);
auto sz = m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_z")->value;
toggle_option("belt_shear_z_angle", is_belt && sz != BeltShearMode::None);
toggle_option("belt_shear_z_from", is_belt && sz != BeltShearMode::None);
toggle_option("belt_shear_z_global", is_belt && sz != BeltShearMode::None);
toggle_option("belt_shear_z_global", is_belt && sz != BeltShearMode::None && !belt_global);
auto scx = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_x")->value;
toggle_option("belt_scale_x_angle", is_belt && scx != BeltScaleMode::None);
@@ -5354,9 +5363,10 @@ void TabPrinter::toggle_options()
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_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"));
// Origin snap is superseded by belt_preslice_global
toggle_option("belt_origin_offset_x", is_belt && m_config->opt_bool("belt_origin_snap_x") && !belt_global);
toggle_option("belt_origin_offset_y", is_belt && m_config->opt_bool("belt_origin_snap_y") && !belt_global);
toggle_option("belt_origin_offset_z", is_belt && m_config->opt_bool("belt_origin_snap_z") && !belt_global);
toggle_line("belt_support_floor_mode", is_belt);
}