Belt/Standard calibrations (#54)

Enables supported printing of standard Orcaslicer calibration profiles.

* Build 2 Checkpoint

* fix support generation wedge, ghost layers

* flip cornering tests 180 deg to waste less supports

* fix row spacing on the flow ratio calibrations

* more testing, this didn't fix anything

* switched rotation tools, same issue

* fixed Z-offset issues

* add rest of PA features, may look a bit weird on a belt

* make temp towers work

* re-enable spiral on calibrations that want it

* Final cleanup pre-PR and community testing
This commit is contained in:
Joseph Robertson
2026-06-12 03:14:12 -05:00
committed by GitHub
parent d7b75540d0
commit 0da24cd38b
10 changed files with 554 additions and 59 deletions

View File

@@ -38,7 +38,11 @@ void BeltGCodeWriter::set_machine_frame_transform(const PrintConfig &config)
Vec3d BeltGCodeWriter::to_machine_coords(const Vec3d &pos) const
{
// Step 1+2: To Cartesian (back_transform + axis_remap).
Vec3d after_back = m_belt_back_transform.apply(pos);
// In world-coordinates mode (PA line / PA pattern calibration) the input
// already describes a point relative to the belt surface, so the
// slicer->world back-transform is skipped and only the machine kinematics
// (axis remap + frame shear/scale) are applied.
Vec3d after_back = m_world_coordinates ? pos : m_belt_back_transform.apply(pos);
Vec3d result = apply_axis_remap(after_back);
Vec3d after_remap = result;
// Step 3: Machine-frame transform (belt frame tilt) applied LAST so it acts

View File

@@ -23,6 +23,14 @@ public:
void set_machine_frame_transform(const PrintConfig &config);
Vec3d to_machine_coords(const Vec3d &pos) const;
// World-coordinates mode: incoming coordinates are treated as points
// relative to the physical belt surface (X across, Y along the belt,
// Z height above it) instead of slicing-frame coordinates — the
// slicer->world back-transform is skipped. Used by the PA line / PA
// pattern calibration generators, whose logical bed coordinates describe
// first-layer drawings on the build surface.
void set_world_coordinates(bool enable) { m_world_coordinates = enable; }
// First-layer plane: when set to a non-null active evaluator, travel
// speed selection consults the plane per-move and uses
// initial_layer_travel_speed for points within first_layer_height_mm
@@ -47,6 +55,7 @@ protected:
private:
BeltBackTransform m_belt_back_transform;
MachineFrameTransform m_machine_frame_transform;
bool m_world_coordinates = false;
// Borrowed pointer; lifetime owned by GCode. null = inactive.
const FirstLayerPlane *m_first_layer_plane = nullptr;
double m_first_layer_thickness_mm = 0.;

View File

@@ -1834,6 +1834,12 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
last_extrusion_layer = &layers_to_print.back();
}
// ORCA-Belt: objects print at their position along the belt, so the first
// extrusions legitimately start far above Z=0. Drop the spurious
// "empty layers from the bed" range while keeping genuine mid-print gaps.
if (skip_empty_first_layer && !warning_ranges.empty() && warning_ranges.front().first == 0.)
warning_ranges.erase(warning_ranges.begin());
if (! warning_ranges.empty()) {
std::string warning;
size_t i = 0;
@@ -3307,7 +3313,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
pa_test.set_speed(fast_speed, slow_speed);
pa_test.draw_numbers() = print.calib_params().print_numbers;
// ORCA-Belt: the PA line test draws directly on the build surface in
// logical bed coordinates — on a belt printer that surface is the
// belt plane, not the slicing plane.
BeltGCodeWriter* belt_writer = dynamic_cast<BeltGCodeWriter*>(m_writer.get());
if (belt_writer != nullptr)
belt_writer->set_world_coordinates(true);
gcode += pa_test.generate_test(params.start, params.step, std::llround(std::ceil((params.end - params.start) / params.step)) + 1);
if (belt_writer != nullptr)
belt_writer->set_world_coordinates(false);
file.write(gcode);
} else {
@@ -4653,31 +4668,57 @@ LayerResult GCode::process_layer(
gcode += ";_SET_FAN_SPEED_CHANGING_LAYER\n";
//Calibration Layer-specific GCode
// ORCA-Belt: on belt printers the calibration object is counter-rotated to
// stand upright in slicing space on top of a support wedge, so its first
// layer starts above Z=0 (at its position along the belt) with support-only
// layers below it. Reference the per-height calibration bands to the bottom
// of the object so they keep their designed meaning; on regular printers
// the object base is at Z=0 and calib_z == print_z.
double calib_z = print_z;
if (m_config.belt_printer.value && print.calib_mode() != CalibMode::Calib_None) {
// Skip empty ghost layers the grid may produce below the object.
for (const Layer* l : layer.object()->layers())
if (!l->lslices.empty()) {
calib_z = print_z - (l->print_z - l->height);
break;
}
}
switch (print.calib_mode()) {
case CalibMode::Calib_PA_Tower: {
gcode += writer().set_pressure_advance(print.calib_params().start + static_cast<int>(print_z) * print.calib_params().step);
gcode += writer().set_pressure_advance(print.calib_params().start + static_cast<int>(std::max(0.0, calib_z)) * print.calib_params().step);
break;
}
case CalibMode::Calib_Temp_Tower: {
gcode += writer().set_temperature(this->interpolate_value_across_layers(static_cast<float>(print.calib_params().start), static_cast<float>(print.calib_params().end), 5.0f));
// ORCA-Belt: the sectioned variant prints each temperature as its
// own object in native belt orientation, with the temperature
// encoded in the object name ("temp_230") — step per object
// instead of ramping per layer band.
int sectioned_temp = 0;
if (m_config.belt_printer.value &&
sscanf(layer.object()->model_object()->name.c_str(), "temp_%d", &sectioned_temp) == 1 &&
sectioned_temp > 0) {
gcode += writer().set_temperature(static_cast<unsigned int>(sectioned_temp));
} else {
gcode += writer().set_temperature(this->interpolate_value_across_layers(static_cast<float>(print.calib_params().start), static_cast<float>(print.calib_params().end), 5.0f));
}
break;
}
case CalibMode::Calib_VFA_Tower: {
auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step;
auto _speed = print.calib_params().start + std::floor(std::max(0.0, calib_z) / 5.0) * print.calib_params().step;
m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed)));
break;
}
case CalibMode::Calib_Vol_speed_Tower: {
auto _speed = print.calib_params().start + print_z * print.calib_params().step;
auto _speed = print.calib_params().start + std::max(0.0, calib_z) * print.calib_params().step;
m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed)));
break;
}
case CalibMode::Calib_Retraction_tower: {
auto _length = print.calib_params().start + std::floor(std::max(0.0,print_z-0.4)) * print.calib_params().step;
auto _length = print.calib_params().start + std::floor(std::max(0.0,calib_z-0.4)) * print.calib_params().step;
DynamicConfig _cfg;
_cfg.set_key_value("retraction_length", new ConfigOptionFloats{_length});
writer().config.apply(_cfg);
sprintf(buf, "; Calib_Retraction_tower: Z_HEIGHT: %g, length:%g\n", print_z, _length);
sprintf(buf, "; Calib_Retraction_tower: Z_HEIGHT: %g, length:%g\n", calib_z, _length);
gcode += buf;
break;
}
@@ -7415,25 +7456,39 @@ std::string GCode::extrusion_role_to_string_for_parser(const ExtrusionRole & rol
// Step = 0 means gradual interpolation finishing at last value.
float GCode::interpolate_value_across_layers(float start_value, float end_value, float step) const
{
if (m_layer_index <= 1) {
float ratio;
// ORCA-Belt: counter-rotated calibration objects stand on a support wedge,
// so support-only layers below the object would stretch a layer-index
// interpolation. Use the object's own Z span instead, so the value ramps
// across the test geometry only.
if (m_config.belt_printer.value && m_layer != nullptr && !m_layer->object()->layers().empty()) {
const auto& layers = m_layer->object()->layers();
// Skip empty ghost layers the grid may produce below the object.
double z_min = layers.front()->print_z;
for (const Layer* l : layers)
if (!l->lslices.empty()) { z_min = l->print_z; break; }
const double z_max = layers.back()->print_z;
if (m_layer->print_z <= z_min + EPSILON || z_max - z_min <= EPSILON)
return start_value;
ratio = float(std::min(1.0, (m_layer->print_z - z_min) / (z_max - z_min)));
} else if (m_layer_index <= 1) {
return start_value;
} else {
ratio = m_layer_index / (m_layer_count - 1.f);
}
else {
bool use_steps = step > 0.f;
if (use_steps) {
if (start_value > end_value) {
start_value += step;
} else {
end_value += step;
}
bool use_steps = step > 0.f;
if (use_steps) {
if (start_value > end_value) {
start_value += step;
} else {
end_value += step;
}
float ratio = m_layer_index / (m_layer_count - 1.f);
float value = start_value + ratio * (end_value - start_value);
if (use_steps) {
value = trunc(value / step) * step;
}
return value;
}
float value = start_value + ratio * (end_value - start_value);
if (use_steps) {
value = trunc(value / step) * step;
}
return value;
}
std::string encodeBase64(uint64_t value)

View File

@@ -966,7 +966,14 @@ void PrintObject::slice()
// correct machine-frame coordinates whether or not a global mode is active.
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
double belt_z_shift = m_belt_min_z - belt_surface_z;
// The compensation must mirror the Z-shift actually applied, which
// is max(0, -m_belt_min_z): when the transformed mesh starts ABOVE
// slicer Z=0 (m_belt_min_z > 0 — possible for counter-rotated or
// asymmetric geometry whose centered-frame minimum lands positive)
// no lift was applied, and an unclamped m_belt_min_z here would
// leak straight into the layer Z values, floating the whole object
// off the belt by exactly that amount.
double belt_z_shift = std::min(m_belt_min_z, 0.) - belt_surface_z;
double global_z_offset = belt_z_shift;
// Centering correction: trafo_centered pretranslates by

View File

@@ -1801,8 +1801,7 @@ void TreeSupport::generate()
{
BeltFloorContext ctx;
if (ctx.init(m_slicing_params, *m_print_config)
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly
&& m_object->support_layer_count() > 0) {
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
const auto &sp = m_slicing_params;
// Find the lowest non-empty, non-brim support layer.
ExPolygons source_areas;
@@ -1830,16 +1829,61 @@ void TreeSupport::generate()
}
}
}
// ORCA-Belt calibration: a counter-rotated calibration object
// stands on a support wedge that lies entirely below the object's
// first layer, where the tree pipeline has no layers at all — so
// no support content can exist yet. Seed the extension directly
// from the floating portion of the first layer (anything more
// than one layer height above the belt floor). For objects whose
// first layer rests on the belt the floating region is empty and
// behavior is unchanged.
double first_z = m_object->support_layer_count() > 0 ? m_object->get_support_layer(0)->print_z : 0.;
bool seeded = false;
if (source_areas.empty() && m_object_config->enable_support.value && !m_object->layers().empty()) {
// The layer grid may start with an empty ghost layer just below
// the object (grid rounding against the belt global Z offset) —
// anchor the seed to the first layer that has geometry. Object
// layer print_z and the floor plane are both in the globally
// offset frame here (belt_floor_z_shift was adjusted alongside
// the layer Z values in PrintObject::slice()).
const Layer *first_layer = nullptr;
for (const Layer *l : m_object->layers())
if (!l->lslices_extrudable.empty()) { first_layer = l; break; }
if (first_layer != nullptr) {
ExPolygons floating = diff_ex(first_layer->lslices_extrudable,
ctx.surface_polygon(first_layer->bottom_z() - first_layer->height));
BOOST_LOG_TRIVIAL(debug) << "[BELT-CALIB] wedge seed: obj=" << m_object->model_object()->name
<< " bottom_z=" << first_layer->bottom_z() << " floating=" << floating.size();
if (!floating.empty()) {
source_areas = std::move(floating);
first_z = first_layer->bottom_z();
seeded = true;
}
}
}
if (!source_areas.empty()) {
BoundingBoxf3 bb = belt_remapped_bbox(*m_object->model_object(), m_object->print()->config());
double from_extent = std::abs(bb.min(ctx.from_axis()));
double bb_min_z = std::abs(bb.min.z());
double first_z = m_object->get_support_layer(0)->print_z;
// Depth = from-axis extent + pre-shear bbox Z offset (ensure_on_bed
// 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));
if (seeded) {
// Seeded wedge: the depth is known exactly — down to the lowest
// belt-floor point under the floating footprint. The bbox
// heuristic above under-estimates it for meshes centered
// around their origin (every object loaded through the GUI).
double min_floor = first_z;
for (const ExPolygon &ep : source_areas)
for (const Point &pt : ep.contour.points)
min_floor = std::min(min_floor, ctx.floor_print_z(pt));
extra_depth = std::min(std::max(0., first_z), first_z - min_floor + 2.);
}
int num_extra = std::max(0, (int)std::ceil(extra_depth / sp.layer_height));
// Seeded wedge: top layers become a dense support interface so the
// object's floating first layer bridges a roof, not sparse infill.
const int interface_layers = seeded ? std::max(0, m_object_config->support_interface_top_layers.value) : 0;
ExPolygons prev_areas = source_areas;
// Build belt extension layers (lowest Z first).
SupportLayerPtrs belt_ext_layers;
@@ -1853,8 +1897,15 @@ void TreeSupport::generate()
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);
// Note: base areas only get infill when support_base_pattern
// is explicitly set (with the default pattern tree bases are
// walls-only) — the calibration flow sets rectilinear.
const bool roof = i <= interface_layers;
for (auto &expoly : sl->base_areas) {
sl->area_groups.emplace_back(&expoly, roof ? SupportLayer::RoofType : SupportLayer::BaseType, 0);
if (roof)
sl->area_groups.back().interface_id = i & 1;
}
sl->lslices = clipped;
sl->lslices_bboxes.reserve(clipped.size());
for (const ExPolygon &ep : clipped)
@@ -1865,6 +1916,9 @@ void TreeSupport::generate()
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());
BOOST_LOG_TRIVIAL(debug) << "[BELT-CALIB] wedge ext layers=" << belt_ext_layers.size()
<< " z=" << belt_ext_layers.front()->print_z << ".." << belt_ext_layers.back()->print_z
<< " seeded=" << seeded;
}
}
}

View File

@@ -1,4 +1,5 @@
#include "calib.hpp"
#include "BeltGCodeWriter.hpp"
#include "BoundingBox.hpp"
#include "Config.hpp"
#include "Model.hpp"
@@ -587,21 +588,21 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
refresh_setup(config, is_bbl_machine, object, origin);
gcode << move_to(Vec2d(m_starting_point.x(), m_starting_point.y()), m_writer, "Move to start XY position");
gcode << m_writer.travel_to_z(height_first_layer() + height_z_offset(), "Move to start Z position");
gcode << m_writer.set_pressure_advance(m_params.start);
gcode << move_to(Vec2d(m_starting_point.x(), m_starting_point.y()), *m_writer, "Move to start XY position");
gcode << m_writer->travel_to_z(height_first_layer() + height_z_offset(), "Move to start Z position");
gcode << m_writer->set_pressure_advance(m_params.start);
const DrawBoxOptArgs default_box_opt_args(wall_count(), height_first_layer(), line_width_first_layer(),
speed_adjust(speed_first_layer()));
// create anchoring frame
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y(), print_size_x(), frame_size_y(), default_box_opt_args);
gcode << draw_box(*m_writer, m_starting_point.x(), m_starting_point.y(), print_size_x(), frame_size_y(), default_box_opt_args);
// create tab for numbers
DrawBoxOptArgs draw_box_opt_args = default_box_opt_args;
draw_box_opt_args.is_filled = true;
draw_box_opt_args.num_perimeters = wall_count();
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y() + frame_size_y() + line_spacing_first_layer(),
gcode << draw_box(*m_writer, m_starting_point.x(), m_starting_point.y() + frame_size_y() + line_spacing_first_layer(),
print_size_x(),
max_numbering_height() + line_spacing_first_layer() + m_glyph_padding_vertical * 2, draw_box_opt_args);
@@ -627,15 +628,15 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
gcode = std::stringstream(); // reset for next layer contents
gcode << "; start pressure advance pattern for layer\n";
gcode << m_writer.travel_to_z(layer_height, "Move to layer height");
gcode << m_writer.reset_e();
gcode << m_writer->travel_to_z(layer_height, "Move to layer height");
gcode << m_writer->reset_e();
}
// line numbering
if (i == 1) {
m_number_len = max_numbering_length();
gcode << m_writer.set_pressure_advance(m_params.start);
gcode << m_writer->set_pressure_advance(m_params.start);
double number_e_per_mm = e_per_mm(line_width(), height_layer(),
m_config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0),
@@ -646,20 +647,20 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
for (int j = 0; j < num_patterns; j += 2) {
gcode << draw_number(glyph_start_x(j), m_starting_point.y() + frame_size_y() + m_glyph_padding_vertical + line_width(),
m_params.start + (j * m_params.step), m_draw_digit_mode, line_width(), number_e_per_mm,
speed_first_layer(), m_writer);
speed_first_layer(), *m_writer);
}
// flow value
int line_num = num_patterns + 2;
gcode << draw_number(glyph_start_x(line_num), m_starting_point.y() + frame_size_y() + m_glyph_padding_vertical + line_width(),
flow_val(), m_draw_digit_mode, line_width(), number_e_per_mm,
speed_first_layer(), m_writer);
speed_first_layer(), *m_writer);
// acceleration
line_num = num_patterns + 4;
gcode << draw_number(glyph_start_x(line_num), m_starting_point.y() + frame_size_y() + m_glyph_padding_vertical + line_width(),
accel, m_draw_digit_mode, line_width(), number_e_per_mm,
speed_first_layer(), m_writer);
speed_first_layer(), *m_writer);
}
@@ -678,20 +679,20 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
/* Draw a line at slightly slower accel and speed in order to trick gcode writer to force update acceleration and speed.
* We do this since several tests may be generated by their own gcode writers which are
* not aware about their neighbours updating acceleration/speed */
gcode << m_writer.set_print_acceleration(std::max<int>(1, accel - 1));
gcode << move_to(Vec2d(m_starting_point.x(), m_starting_point.y()), m_writer, "Move to starting point", zhop_height, layer_height);
gcode << draw_line(m_writer, Vec2d(m_starting_point.x(), m_starting_point.y() + frame_size_y()), line_width(), height_layer(), speed_adjust(std::max<int>(1, speed_perimeter() - 1)), "Accel/flow trick line");
gcode << m_writer.set_print_acceleration(accel);
gcode << m_writer->set_print_acceleration(std::max<int>(1, accel - 1));
gcode << move_to(Vec2d(m_starting_point.x(), m_starting_point.y()), *m_writer, "Move to starting point", zhop_height, layer_height);
gcode << draw_line(*m_writer, Vec2d(m_starting_point.x(), m_starting_point.y() + frame_size_y()), line_width(), height_layer(), speed_adjust(std::max<int>(1, speed_perimeter() - 1)), "Accel/flow trick line");
gcode << m_writer->set_print_acceleration(accel);
}
double initial_x = to_x;
double initial_y = to_y;
gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to pattern start",zhop_height,layer_height);
gcode << move_to(Vec2d(to_x, to_y), *m_writer, "Move to pattern start",zhop_height,layer_height);
for (int j = 0; j < num_patterns; ++j) {
// increment pressure advance
gcode << m_writer.set_pressure_advance(m_params.start + (j * m_params.step));
gcode << m_writer->set_pressure_advance(m_params.start + (j * m_params.step));
for (int k = 0; k < wall_count(); ++k) {
to_x += std::cos(to_radians(m_corner_angle) / 2) * side_length;
@@ -701,27 +702,27 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
auto draw_line_arg_line_width = line_width(); // don't use line_width_first_layer so results are consistent across all layers
auto draw_line_arg_speed = i == 0 ? speed_adjust(speed_first_layer()) : speed_adjust(speed_perimeter());
auto draw_line_arg_comment = "Print pattern wall";
gcode << draw_line(m_writer, Vec2d(to_x, to_y), draw_line_arg_line_width, draw_line_arg_height, draw_line_arg_speed, draw_line_arg_comment);
gcode << draw_line(*m_writer, Vec2d(to_x, to_y), draw_line_arg_line_width, draw_line_arg_height, draw_line_arg_speed, draw_line_arg_comment);
to_x -= std::cos(to_radians(m_corner_angle) / 2) * side_length;
to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length;
gcode << draw_line(m_writer, Vec2d(to_x, to_y), draw_line_arg_line_width, draw_line_arg_height, draw_line_arg_speed, draw_line_arg_comment);
gcode << draw_line(*m_writer, Vec2d(to_x, to_y), draw_line_arg_line_width, draw_line_arg_height, draw_line_arg_speed, draw_line_arg_comment);
to_y = initial_y;
if (k != wall_count() - 1) {
// perimeters not done yet. move to next perimeter
to_x += line_spacing_angle();
gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to start next pattern wall", zhop_height, layer_height); // Call move to command with XY as well as z hop and layer height to invoke and undo z lift
gcode << move_to(Vec2d(to_x, to_y), *m_writer, "Move to start next pattern wall", zhop_height, layer_height); // Call move to command with XY as well as z hop and layer height to invoke and undo z lift
} else if (j != num_patterns - 1) {
// patterns not done yet. move to next pattern
to_x += m_pattern_spacing + line_width();
gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to next pattern", zhop_height, layer_height); // Call move to command with XY as well as z hop and layer height to invoke and undo z lift
gcode << move_to(Vec2d(to_x, to_y), *m_writer, "Move to next pattern", zhop_height, layer_height); // Call move to command with XY as well as z hop and layer height to invoke and undo z lift
} else if (i != m_num_layers - 1) {
// layers not done yet. move back to start
to_x = initial_x;
gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move back to start position", zhop_height, layer_height); // Call move to command with XY as well as z hop and layer height to invoke and undo z lift
gcode << m_writer.reset_e(); // reset extruder before printing placeholder cube to avoid over extrusion
gcode << move_to(Vec2d(to_x, to_y), *m_writer, "Move back to start position", zhop_height, layer_height); // Call move to command with XY as well as z hop and layer height to invoke and undo z lift
gcode << m_writer->reset_e(); // reset extruder before printing placeholder cube to avoid over extrusion
} else {
// everything done
}
@@ -729,7 +730,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
}
}
gcode << m_writer.set_pressure_advance(m_params.start);
gcode << m_writer->set_pressure_advance(m_params.start);
gcode << "; end pressure advance pattern for layer\n";
CustomGCode::Item item;
@@ -796,13 +797,36 @@ void CalibPressureAdvancePattern::_refresh_writer(bool is_bbl_machine, const Mod
PrintConfig print_config;
print_config.apply(m_config, true);
m_writer.apply_print_config(print_config);
m_writer.set_xy_offset(origin(0), origin(1));
m_writer.set_is_bbl_machine(is_bbl_machine);
// ORCA-Belt: the pattern is drawn in logical bed coordinates directly on
// the build surface — on a belt printer that means the belt plane, which
// needs the machine kinematics (axis remap + frame shear/scale) with the
// coordinates interpreted as world points (see set_world_coordinates).
if (print_config.belt_printer.value) {
auto belt_writer = std::make_shared<BeltGCodeWriter>();
belt_writer->set_belt_back_transform(print_config);
belt_writer->set_machine_frame_transform(print_config);
belt_writer->set_world_coordinates(true);
const int rx = int(print_config.gcode_remap_x.value);
const int ry = int(print_config.gcode_remap_y.value);
const int rz = int(print_config.gcode_remap_z.value);
if (rx != 0 || ry != 1 || rz != 2) {
belt_writer->set_axis_remap(rx, ry, rz);
BoundingBoxf bbox_bed(print_config.printable_area.values);
belt_writer->set_build_volume_max(Vec3d(bbox_bed.max.x(), bbox_bed.max.y(),
print_config.printable_height.value));
}
m_writer = std::move(belt_writer);
} else if (dynamic_cast<BeltGCodeWriter*>(m_writer.get()) != nullptr) {
m_writer = std::make_shared<GCodeWriter>();
}
m_writer->apply_print_config(print_config);
m_writer->set_xy_offset(origin(0), origin(1));
m_writer->set_is_bbl_machine(is_bbl_machine);
const unsigned int extruder_id = object.volumes.front()->extruder_id();
m_writer.set_extruders({extruder_id});
m_writer.set_extruder(extruder_id);
m_writer->set_extruders({extruder_id});
m_writer->set_extruder(extruder_id);
}
double CalibPressureAdvancePattern::object_size_x() const

View File

@@ -358,7 +358,10 @@ private:
const Calib_Params &m_params;
GCodeWriter m_writer;
// Polymorphic so belt printers get a BeltGCodeWriter in world-coordinates
// mode (_refresh_writer); shared_ptr keeps the class copyable — the writer
// is rebuilt by refresh_setup() before every use anyway.
std::shared_ptr<GCodeWriter> m_writer{std::make_shared<GCodeWriter>()};
Vec3d m_starting_point;
bool m_is_start_point_fixed = false;

View File

@@ -12594,8 +12594,181 @@ void Plater::add_model(bool imperial_units, std::string fname)
}
}
// ORCA-Belt: belt-printer handling for the desktop calibration tests.
//
// Belt slicing applies a global pre-slice rotation R(angle, axis) to every
// mesh (see BeltTransform.hpp) so the slicing planes match the tilted gantry.
// Calibration models are designed for upright slicing: their per-height test
// bands and XY-plane quality features assume slicer Z is the model's own Z
// axis. Counter-rotating each calibration object by the inverse rotation in
// world space cancels the global rotation, so in slicing space the object
// stands upright exactly as on a flat-bed printer and every test keeps its
// designed meaning. Physically the object then leans over the belt with its
// bottom face overhanging, so per-object supports fill the wedge between the
// bottom face and the belt. The wedge prints entirely below the object's base
// plane and leaves the test geometry untouched. Manual tree support is used
// so the deliberate bridge/overhang features of the test models stay
// unsupported; the wedge under the floating bottom face is built by the
// belt-floor extension in TreeSupport::generate(), which stacks the floating
// first-layer footprint down to the belt surface.
static bool belt_calib_rotation_params(double& angle_rad, Vec3d& axis)
{
const auto& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
const auto* belt_opt = printer_config.option<ConfigOptionBool>("belt_printer");
if (belt_opt == nullptr || !belt_opt->value)
return false;
const auto* axis_opt = printer_config.option<ConfigOptionEnum<BeltRotationAxis>>("belt_slice_rotation");
const auto* angle_opt = printer_config.option<ConfigOptionFloat>("belt_slice_rotation_angle");
if (axis_opt == nullptr || angle_opt == nullptr)
return false;
switch (axis_opt->value) {
case BeltRotationAxis::X: axis = Vec3d::UnitX(); break;
case BeltRotationAxis::Y: axis = Vec3d::UnitY(); break;
// Z rotation is an in-plane spin and None means no tilt; objects already
// slice upright in those cases and need no special handling.
default: return false;
}
angle_rad = -Geometry::deg2rad(angle_opt->value);
return std::abs(angle_rad) > EPSILON;
}
// ORCA-Belt: flip the ringing tower 180° about Z before the belt
// counter-rotation — its sloped face then leans over the belt and the
// support wedge gets much smaller.
static void belt_calib_flip_ringing_tower(Model &model)
{
double angle_rad = 0.;
Vec3d axis = Vec3d::UnitX();
if (belt_calib_rotation_params(angle_rad, axis) && !model.objects.empty())
model.objects.front()->rotate(M_PI, Vec3d::UnitZ());
}
void Plater::_calib_apply_belt_mode()
{
double angle_rad = 0.;
Vec3d axis = Vec3d::UnitX();
if (!belt_calib_rotation_params(angle_rad, axis))
return;
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
// Spiral vase stays enabled where the tests request it: the support wedge
// lies strictly below the object, support layers never spiralize
// (spiral_vase_enable requires an object layer), and the spiral/support
// exclusivity check only applies to globally enabled supports — the wedge
// uses per-object supports.
// A skirt would be drawn in the first slicing plane, which lies mostly
// above the belt surface.
print_config->set_key_value("skirt_loops", new ConfigOptionInt(0));
const Matrix3d cancel_rotation = Eigen::AngleAxisd(angle_rad, axis).toRotationMatrix();
std::vector<size_t> obj_idxs;
for (size_t i = 0; i < model().objects.size(); ++i) {
ModelObject* obj = model().objects[i];
if (obj->instances.size() != 1)
continue;
obj_idxs.emplace_back(i);
// Manual tree support: only the floating bottom face gets a support
// wedge (via the belt-floor extension in TreeSupport::generate()),
// leaving the test features untouched. The style is pinned to hybrid
// because the default style resolves to organic, which bypasses the
// non-organic generator that hosts the belt-floor extension.
obj->config.set_key_value("enable_support", new ConfigOptionBool(true));
obj->config.set_key_value("support_type", new ConfigOptionEnum<SupportType>(stTree));
obj->config.set_key_value("support_style", new ConfigOptionEnum<SupportMaterialStyle>(smsTreeHybrid));
obj->config.set_key_value("support_on_build_plate_only", new ConfigOptionBool(false));
// With the default base pattern, tree support base areas print as
// hollow outlines (no infill) — the wedge needs a real pattern.
obj->config.set_key_value("support_base_pattern", new ConfigOptionEnum<SupportMaterialPattern>(smpRectilinear));
// Counter-rotate exactly the way the rotate gizmo would: rotation on
// the instance, then a plain drop to the bed. This leaves the object
// in the same state shape as any manually rotated object, which the
// belt pipeline is known to handle.
ModelInstance* inst = obj->instances.front();
inst->rotate(cancel_rotation);
obj->invalidate_bounding_box();
obj->ensure_on_bed();
{
const BoundingBoxf3 rb = obj->raw_bounding_box();
const Vec3d io = inst->get_offset();
const Vec3d ir = inst->get_rotation();
BOOST_LOG_TRIVIAL(debug) << "[BELT-CALIB] helper exit: obj=" << obj->name
<< " inst_offset=(" << io.x() << "," << io.y() << "," << io.z() << ")"
<< " inst_rot=(" << ir.x() << "," << ir.y() << "," << ir.z() << ")"
<< " vol0_offset=(" << obj->volumes.front()->get_offset().x() << ","
<< obj->volumes.front()->get_offset().y() << "," << obj->volumes.front()->get_offset().z() << ")"
<< " raw_bbox=(" << rb.min.x() << "," << rb.min.y() << "," << rb.min.z()
<< ")..(" << rb.max.x() << "," << rb.max.y() << "," << rb.max.z() << ")"
<< " min_z=" << obj->min_z();
}
}
// Each object's support wedge extends upstream of it by roughly its own
// depth (at 45°), so the tight flat-bed layouts of the multi-part tests
// leave wedges intersecting the neighbouring parts. Keep the grid rows of
// the test layouts together and open up the space between rows just
// enough for the wedge shadow.
if (obj_idxs.size() > 1) {
std::vector<ModelObject*> sorted_objs;
sorted_objs.reserve(obj_idxs.size());
for (size_t i : obj_idxs)
sorted_objs.emplace_back(model().objects[i]);
std::sort(sorted_objs.begin(), sorted_objs.end(), [](const ModelObject* a, const ModelObject* b) {
return a->instances.front()->get_offset(Y) < b->instances.front()->get_offset(Y);
});
std::vector<std::vector<ModelObject*>> rows;
double row_y = std::numeric_limits<double>::quiet_NaN();
for (ModelObject* o : sorted_objs) {
const double oy = o->instances.front()->get_offset(Y);
if (rows.empty() || oy - row_y > 1.)
rows.emplace_back();
rows.back().emplace_back(o);
row_y = oy;
}
const double wedge_factor = std::abs(std::tan(angle_rad));
double cursor = std::numeric_limits<double>::quiet_NaN();
for (std::vector<ModelObject*>& row : rows) {
double rmin = std::numeric_limits<double>::max();
double rmax = std::numeric_limits<double>::lowest();
for (ModelObject* o : row) {
const BoundingBoxf3 bb = o->instance_bounding_box(0);
rmin = std::min(rmin, bb.min.y());
rmax = std::max(rmax, bb.max.y());
}
if (std::isnan(cursor))
cursor = rmin; // the first row anchors the layout
const double shift = cursor - rmin;
for (ModelObject* o : row) {
ModelInstance* inst = o->instances.front();
inst->set_offset(Y, inst->get_offset(Y) + shift);
o->invalidate_bounding_box();
}
cursor += (rmax - rmin) * (1. + wedge_factor) + 5.;
}
}
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_dirty();
wxGetApp().get_tab(Preset::TYPE_PRINT)->reload_config();
changed_objects(obj_idxs);
}
void Plater::calib_pa(const Calib_Params& params)
{
// ORCA-Belt: PA Line / PA Pattern have the belt plumbing in place
// (BeltGCodeWriter::set_world_coordinates draws them on the belt surface)
// but are not validated yet — keep them gated to the PA Tower for now.
{
double angle_rad = 0.;
Vec3d axis = Vec3d::UnitX();
if (belt_calib_rotation_params(angle_rad, axis) && params.mode != CalibMode::Calib_PA_Tower) {
MessageDialog msg_dlg(nullptr, _L("PA Line and PA Pattern tests are not enabled yet on belt printers.\nPlease use the PA Tower method instead."),
wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
}
const auto calib_pa_name = wxString::Format(L"Pressure Advance Test");
new_project(false, false, calib_pa_name);
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
@@ -12927,6 +13100,7 @@ void Plater::_calib_pa_tower(const Calib_Params& params) {
cut_horizontal(0, 0, new_height, ModelObjectCutAttribute::KeepLower);
}
_calib_apply_belt_mode();
_calib_pa_select_added_objects();
}
@@ -13104,6 +13278,8 @@ void Plater::calib_flowrate(bool is_linear, int pass, InfillPattern pattern) {
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
_calib_apply_belt_mode();
// Refresh object after scaling
const std::vector<size_t> object_idx(boost::counting_iterator<size_t>(0), boost::counting_iterator<size_t>(model().objects.size()));
changed_objects(object_idx);
@@ -13120,7 +13296,19 @@ void Plater::calib_temp(const Calib_Params& params) {
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
if (params.mode != CalibMode::Calib_Temp_Tower)
return;
// ORCA-Belt: a counter-rotated tower would cantilever every block off a
// support wedge — print the temperature blocks as separate objects in
// native belt orientation instead.
{
double belt_angle_rad = 0.;
Vec3d belt_axis = Vec3d::UnitX();
if (belt_calib_rotation_params(belt_angle_rad, belt_axis)) {
_calib_temp_belt_sectioned(params, std::abs(belt_angle_rad));
return;
}
}
add_model(false, Slic3r::resources_dir() + "/calib/temperature_tower/temperature_tower.drc");
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
@@ -13190,6 +13378,107 @@ void Plater::calib_temp(const Calib_Params& params) {
p->background_process.fff_print()->set_calib_params(params);
}
// ORCA-Belt: sectioned temperature test. Each temperature gets its own block
// cut out of the temperature tower model, printed in native belt orientation
// (no counter-rotation, no support wedge) and spaced along the belt so the
// blocks' layer ranges are disjoint — they print strictly one after another,
// starting with the start temperature closest to the gantry. The temperature
// is encoded in the object name ("temp_230") and applied per object by the
// Calib_Temp_Tower handler at G-code time, replacing the per-layer-band ramp
// that only makes sense for a monolithic upright tower.
void Plater::_calib_temp_belt_sectioned(const Calib_Params& params, double belt_angle_rad)
{
constexpr double base_temp_tower_nozzle_diameter = 0.4;
constexpr double base_temp_tower_block_height = 10.0;
constexpr int base_temp_tower_temp_step = 5;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
const long start_temp = lround(params.start);
const long end_temp = lround(params.end);
const int n_blocks = std::max(1, int((start_temp - end_temp) / base_temp_tower_temp_step) + 1);
const ConfigOptionFloats* nozzle_diameter_config = printer_config->option<ConfigOptionFloats>("nozzle_diameter");
size_t nozzle_id = static_cast<size_t>(std::max(params.extruder_id, 0));
double nozzle_diameter = base_temp_tower_nozzle_diameter;
if (nozzle_diameter_config && !nozzle_diameter_config->values.empty()) {
nozzle_id = std::min(nozzle_id, nozzle_diameter_config->values.size() - 1);
nozzle_diameter = nozzle_diameter_config->values[nozzle_id];
}
if (nozzle_diameter <= 0.0)
nozzle_diameter = base_temp_tower_nozzle_diameter;
const double nozzle_scale = nozzle_diameter / base_temp_tower_nozzle_diameter;
std::vector<size_t> obj_idxs;
for (int i = 0; i < n_blocks; ++i) {
const long temp = start_temp - long(i) * base_temp_tower_temp_step;
const size_t count_before = model().objects.size();
add_model(false, Slic3r::resources_dir() + "/calib/temperature_tower/temperature_tower.drc");
if (model().objects.size() <= count_before)
break; // model failed to load — don't index into an empty list
// The cut replaces the object at the END of the list, so re-acquire
// the index after every operation.
size_t obj_idx = model().objects.size() - 1;
// Isolate this temperature's block (full-tower coordinates, the same
// 500-down-to-temp indexing the monolithic flow cuts with).
const double block_bottom = double(lround(double(500 - temp) / base_temp_tower_temp_step)) * base_temp_tower_block_height;
auto obj_bb = model().objects[obj_idx]->bounding_box_exact();
if (block_bottom + base_temp_tower_block_height < obj_bb.size().z()) {
cut_horizontal(obj_idx, 0, block_bottom + base_temp_tower_block_height - EPSILON, ModelObjectCutAttribute::KeepLower);
obj_idx = model().objects.size() - 1;
}
if (block_bottom > 0) {
cut_horizontal(obj_idx, 0, block_bottom + EPSILON, ModelObjectCutAttribute::KeepUpper);
obj_idx = model().objects.size() - 1;
}
ModelObject* obj = model().objects[obj_idx];
if (std::abs(nozzle_scale - 1.0) > EPSILON)
obj->scale(nozzle_scale, nozzle_scale, nozzle_scale);
obj->name = std::string("temp_") + std::to_string(temp);
obj->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter / 2));
obj->config.set_key_value("alternate_extra_wall", new ConfigOptionBool(false));
obj->config.set_key_value("seam_slope_type", new ConfigOptionEnum<SeamScarfType>(SeamScarfType::None));
obj->config.set_key_value("overhang_reverse", new ConfigOptionBool(false));
obj->config.set_key_value("precise_z_height", new ConfigOptionBool(false));
obj->ensure_on_bed();
obj_idxs.emplace_back(obj_idx);
}
// Space the blocks along the belt with strictly increasing layer ranges:
// each block must start past the previous block's highest slicing plane,
// which trails its far edge by height / tan(angle). Anchor the row near
// the gantry so the whole test stays in the plate area.
const double cot_a = 1. / std::max(0.1, std::tan(belt_angle_rad));
double cursor = 20.;
for (size_t idx : obj_idxs) {
ModelObject* obj = model().objects[idx];
ModelInstance* inst = obj->instances.front();
const BoundingBoxf3 bb = obj->instance_bounding_box(0);
inst->set_offset(Y, inst->get_offset(Y) + (cursor - bb.min.y()));
obj->invalidate_bounding_box();
cursor += bb.size().y() + bb.size().z() * cot_a + 5.;
}
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
filament_config->set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts(1, (int)start_temp));
filament_config->set_key_value("nozzle_temperature", new ConfigOptionInts(1, (int)start_temp));
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter / 2));
print_config->set_key_value("skirt_loops", new ConfigOptionInt(0));
changed_objects(obj_idxs);
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_dirty();
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_dirty();
wxGetApp().get_tab(Preset::TYPE_PRINT)->reload_config();
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->reload_config();
p->background_process.fff_print()->set_calib_params(params);
}
void Plater::calib_max_vol_speed(const Calib_Params& params)
{
const auto calib_vol_speed_name = wxString::Format(L"Max volumetric speed test");
@@ -13256,6 +13545,8 @@ void Plater::calib_max_vol_speed(const Calib_Params& params)
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
}
_calib_apply_belt_mode();
auto new_params = params;
auto mm3_per_mm = Flow(line_width, layer_height, nozzle_diameter).mm3_per_mm() * filament_config->option<ConfigOptionFloatsNullable>("filament_flow_ratio")->get_at(0);
new_params.end = params.end / mm3_per_mm;
@@ -13320,6 +13611,7 @@ void Plater::calib_retraction(const Calib_Params& params)
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
}
_calib_apply_belt_mode();
p->background_process.fff_print()->set_calib_params(params);
}
@@ -13365,6 +13657,7 @@ void Plater::calib_VFA(const Calib_Params& params)
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
}
_calib_apply_belt_mode();
p->background_process.fff_print()->set_calib_params(params);
}
@@ -13377,6 +13670,8 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
return;
add_model(false, Slic3r::resources_dir() + (params.test_model < 1 ? "/calib/input_shaping/ringing_tower.drc" : "/calib/input_shaping/fast_tower_test.drc"));
if (params.test_model < 1)
belt_calib_flip_ringing_tower(model());
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
@@ -13428,6 +13723,7 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_ui_from_settings();
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_ui_from_settings();
_calib_apply_belt_mode();
p->background_process.fff_print()->set_calib_params(params);
}
@@ -13440,6 +13736,8 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
return;
add_model(false, Slic3r::resources_dir() + (params.test_model < 1 ? "/calib/input_shaping/ringing_tower.drc" : "/calib/input_shaping/fast_tower_test.drc"));
if (params.test_model < 1)
belt_calib_flip_ringing_tower(model());
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
@@ -13490,6 +13788,7 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_ui_from_settings();
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_ui_from_settings();
_calib_apply_belt_mode();
p->background_process.fff_print()->set_calib_params(params);
}
@@ -13505,6 +13804,8 @@ void Plater::Calib_Cornering(const Calib_Params& params)
? "/calib/input_shaping/ringing_tower.drc"
: (params.test_model == 1 ? "/calib/input_shaping/fast_tower_test.drc" : "/calib/cornering/SCV-V2.drc");
add_model(false, Slic3r::resources_dir() + cornering_model_path);
if (params.test_model == 0)
belt_calib_flip_ringing_tower(model());
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
@@ -13555,6 +13856,7 @@ void Plater::Calib_Cornering(const Calib_Params& params)
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_ui_from_settings();
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_ui_from_settings();
_calib_apply_belt_mode();
p->background_process.fff_print()->set_calib_params(params);
}

View File

@@ -951,6 +951,8 @@ private:
void _calib_pa_pattern_gen_gcode();
void _calib_pa_tower(const Calib_Params& params);
void _calib_pa_select_added_objects();
void _calib_apply_belt_mode();
void _calib_temp_belt_sectioned(const Calib_Params& params, double belt_angle_rad);
void cut_horizontal(size_t obj_idx, size_t instance_idx, double z, ModelObjectCutAttributes attributes);

View File

@@ -78,6 +78,19 @@ std::vector<wxString> make_shaper_type_labels()
return labels;
}
// ORCA-Belt: PA Line / PA Pattern have belt plumbing in place (drawn on the
// belt surface via BeltGCodeWriter world-coordinates mode) but are not
// validated yet — belt printers are restricted to the PA Tower for now.
bool is_belt_printer_selected()
{
if (auto* preset_bundle = wxGetApp().preset_bundle) {
const auto& cfg = preset_bundle->printers.get_edited_preset().config;
const auto* opt = cfg.option<ConfigOptionBool>("belt_printer");
return opt != nullptr && opt->value;
}
return false;
}
}
PA_Calibration_Dlg::PA_Calibration_Dlg(wxWindow* parent, wxWindowID id, Plater* plater)
@@ -288,6 +301,14 @@ void PA_Calibration_Dlg::on_start(wxCommandEvent& event) {
m_params.mode = CalibMode::Calib_PA_Tower;
}
// ORCA-Belt: backstop in case the selection slipped past the UI guards.
if (is_belt_printer_selected() && m_params.mode != CalibMode::Calib_PA_Tower) {
MessageDialog msg_dlg(nullptr, _L("PA Line and PA Pattern tests are not enabled yet on belt printers.\nPlease use the PA Tower method instead."),
wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
m_params.print_numbers = m_cbPrintNum->GetValue();
ParseStringValues(m_tiBMAccels->GetTextCtrl()->GetValue().ToStdString(), m_params.accelerations);
ParseStringValues(m_tiBMSpeeds->GetTextCtrl()->GetValue().ToStdString(), m_params.speeds);
@@ -314,6 +335,9 @@ void PA_Calibration_Dlg::on_extruder_type_changed(wxCommandEvent& event) {
event.Skip();
}
void PA_Calibration_Dlg::on_method_changed(wxCommandEvent& event) {
// ORCA-Belt: only the PA Tower method is enabled on belt printers so far.
if (is_belt_printer_selected() && m_rbMethod->GetSelection() != 0)
m_rbMethod->SetSelection(0, true);
PA_Calibration_Dlg::reset_params();
event.Skip();
}
@@ -324,6 +348,17 @@ void PA_Calibration_Dlg::on_dpi_changed(const wxRect& suggested_rect) {
}
void PA_Calibration_Dlg::on_show(wxShowEvent& event) {
// ORCA-Belt: the dialog is cached across printer switches, so refresh the
// belt restriction on every show.
if (is_belt_printer_selected()) {
m_rbMethod->SetSelection(0);
const wxString tip = _L("Not enabled yet on belt printers — use the PA Tower method instead.");
m_rbMethod->SetRadioTooltip(1, tip);
m_rbMethod->SetRadioTooltip(2, tip);
} else {
m_rbMethod->SetRadioTooltip(1, wxEmptyString);
m_rbMethod->SetRadioTooltip(2, wxEmptyString);
}
PA_Calibration_Dlg::reset_params();
}