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

@@ -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;
}
}
}