From 0703728e568d565476f43e4d9b00688a5dd5fdd1 Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Thu, 9 Apr 2026 23:48:43 -0500 Subject: [PATCH] add global mesh transform option --- resources/profiles/Custom.json | 2 +- .../Custom/machine/fdm_belt_common.json | 2 + src/libslic3r/BeltGCode.cpp | 35 +++++++++ src/libslic3r/GCode/BeltBackTransform.cpp | 4 +- src/libslic3r/Preset.cpp | 3 +- src/libslic3r/Print.hpp | 3 + src/libslic3r/PrintApply.cpp | 12 ++-- src/libslic3r/PrintConfig.cpp | 19 +++++ src/libslic3r/PrintConfig.hpp | 2 + src/libslic3r/PrintObjectSlice.cpp | 71 ++++++++++++------- src/slic3r/GUI/Tab.cpp | 22 ++++-- 11 files changed, 135 insertions(+), 40 deletions(-) diff --git a/resources/profiles/Custom.json b/resources/profiles/Custom.json index 4c64f0a205..2e697a7b54 100644 --- a/resources/profiles/Custom.json +++ b/resources/profiles/Custom.json @@ -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": [ diff --git a/resources/profiles/Custom/machine/fdm_belt_common.json b/resources/profiles/Custom/machine/fdm_belt_common.json index dd8b166805..46603f7faf 100644 --- a/resources/profiles/Custom/machine/fdm_belt_common.json +++ b/resources/profiles/Custom/machine/fdm_belt_common.json @@ -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", diff --git a/src/libslic3r/BeltGCode.cpp b/src/libslic3r/BeltGCode.cpp index 40dabf98dc..a34acf95bd 100644 --- a/src/libslic3r/BeltGCode.cpp +++ b/src/libslic3r/BeltGCode.cpp @@ -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(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; diff --git a/src/libslic3r/GCode/BeltBackTransform.cpp b/src/libslic3r/GCode/BeltBackTransform.cpp index b9cdac779f..893c46e52b 100644 --- a/src/libslic3r/GCode/BeltBackTransform.cpp +++ b/src/libslic3r/GCode/BeltBackTransform.cpp @@ -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. diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 399480075e..4d01757826 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1014,8 +1014,9 @@ static std::vector 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", diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9a5d000e1c..074ff52bf3 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -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: diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 7f3b41b0f1..a68c390477 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1,6 +1,7 @@ #include "ClipperUtils.hpp" #include "Model.hpp" #include "Print.hpp" +#include "BeltTransform.hpp" #include #include @@ -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 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)); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index a601a35a17..47d0b9c549 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -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); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5d9f977ed1..d17c46f0aa 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1490,10 +1490,12 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionEnum, preslice_remap_x)) ((ConfigOptionEnum, preslice_remap_y)) ((ConfigOptionEnum, preslice_remap_z)) + ((ConfigOptionBool, preslice_remap_global)) ((ConfigOptionEnum, gcode_remap_x)) ((ConfigOptionEnum, gcode_remap_y)) ((ConfigOptionEnum, 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)) diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 7279b83d14..7711610004 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -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(inst_shift.x()) << ", " << unscale(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(inst_shift.x()), unscale(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(phys.x()) : unscale(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(phys.x()) : unscale(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(inst_shift.x()), unscale(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) { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index e43e979955..986ff614f6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -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>("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>("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>("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>("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>("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); }