Add first layer detection and fan control - prototype

This commit is contained in:
Joseph Robertson
2026-04-13 22:29:23 -05:00
committed by GitHub
parent c17ae25bbc
commit bc6d0ef0fb
14 changed files with 792 additions and 12 deletions
+65 -7
View File
@@ -24,6 +24,7 @@
#include "Time.hpp"
#include "GCode/ExtrusionProcessor.hpp"
#include <algorithm>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <chrono>
@@ -2438,6 +2439,17 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
}
}
// Build the FirstLayerPlane evaluator. When inactive (non-belt printers
// and belt printers without Z shear), all per-path call sites short-
// circuit to the legacy Layer::id() == 0 path so g-code stays bit-
// identical to the pre-feature behavior.
m_first_layer_plane = std::make_unique<FirstLayerPlane>(print.config());
if (auto *belt_writer = dynamic_cast<BeltGCodeWriter*>(m_writer.get())) {
belt_writer->set_first_layer_plane(
m_first_layer_plane.get(),
print.config().initial_layer_print_height.value);
}
// How many times will be change_layer() called?
// change_layer() in turn increments the progress bar status.
m_layer_count = 0;
@@ -4588,7 +4600,38 @@ LayerResult GCode::process_layer(
}
}
if (!first_layer && !m_second_layer_things_done) {
// First-layer plane: defer the temperature/PLR transition until the
// entire layer is past the first-layer band. When the evaluator is
// inactive (non-belt printers and belt printers without Z shear) we
// fall back to the legacy `!first_layer` predicate so behavior is
// bit-identical to the pre-feature path.
bool past_first_layer_band = !first_layer;
if (m_first_layer_plane && m_first_layer_plane->is_active()) {
past_first_layer_band = false;
if (object_layer != nullptr) {
// Conservatively walk the layer's lslice bboxes; if every bbox's
// most-belt-side corner is outside the first-layer band, the
// layer is fully past it.
const Layer *ol = object_layer;
int min_eff = INT_MAX;
for (const BoundingBox &bb : ol->lslices_bboxes) {
BoundingBoxf bbf(
Vec2d(unscale<double>(bb.min.x()), unscale<double>(bb.min.y())),
Vec2d(unscale<double>(bb.max.x()), unscale<double>(bb.max.y())));
int eff = m_first_layer_plane->min_effective_index_for_xy_bbox(
bbf, ol->print_z);
if (eff < min_eff) min_eff = eff;
if (min_eff <= 0) break;
}
past_first_layer_band = (min_eff > 0 && min_eff != INT_MAX);
} else if (support_layer != nullptr) {
// Support-only layers: gate on the support layer's bottom_z
// proximity to the plane. Conservative.
past_first_layer_band = !first_layer;
}
}
if (past_first_layer_band && !m_second_layer_things_done) {
// Orca: set power loss recovery
const auto plr_mode = print.config().enable_power_loss_recovery.value;
gcode += m_writer->enable_power_loss_recovery(plr_mode);
@@ -6204,6 +6247,18 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
if (is_bridge(path.role()))
description += " (bridge)";
// First-layer plane evaluation: compute the path's slicing-frame point
// once and reuse for every per-path call site below. When the plane
// evaluator is inactive (non-belt printers, or belt printers without
// a Z-axis shear) `path_on_first_layer` falls back to the legacy
// layer-id check, so behavior is bit-identical to the pre-feature path.
const Vec3d path_point_mm{
unscale<double>(path.first_point().x()),
unscale<double>(path.first_point().y()),
m_layer ? m_layer->print_z : 0.0
};
const bool path_on_first_layer = this->on_first_layer(path_point_mm);
const ExtrusionPathSloped* sloped = dynamic_cast<const ExtrusionPathSloped*>(&path);
const auto get_sloped_z = [&sloped, this](double z_ratio) {
@@ -6249,7 +6304,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// adjust acceleration
if (m_config.default_acceleration.value > 0) {
double acceleration;
if (this->on_first_layer() && m_config.initial_layer_acceleration.value > 0) {
if (path_on_first_layer && m_config.initial_layer_acceleration.value > 0) {
acceleration = m_config.initial_layer_acceleration.value;
#if 0
} else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) {
@@ -6275,7 +6330,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// adjust X Y jerk
if (m_config.default_jerk.value > 0) {
if (this->on_first_layer() && m_config.initial_layer_jerk.value > 0) {
if (path_on_first_layer && m_config.initial_layer_jerk.value > 0) {
jerk = m_config.initial_layer_jerk.value;
} else if (m_config.outer_wall_jerk.value > 0 && is_external_perimeter(path.role())) {
jerk = m_config.outer_wall_jerk.value;
@@ -6335,7 +6390,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
// Additionally, adjust the value if we are on the first layer (except for brims and skirts)
if (this->on_first_layer() && (path.role() != erBrim && path.role() != erSkirt)) {
if (path_on_first_layer && (path.role() != erBrim && path.role() != erSkirt)) {
_mm3_per_mm *= m_config.first_layer_flow_ratio;
}
}
@@ -6393,14 +6448,17 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
if (speed == 0)
speed = filament_max_volumetric_speed / _mm3_per_mm;
if (this->on_first_layer()) {
if (path_on_first_layer) {
//BBS: for solid infill of first layer, speed can be higher as long as
//wall lines have be attached
if (path.role() != erBottomSurface)
speed = m_config.get_abs_value("initial_layer_speed");
}
else if(m_config.slow_down_layers > 1){
const auto _layer = layer_id();
// Use the FirstLayerPlane-aware effective layer index when active so
// the speed fade tracks perpendicular distance from the plane on
// belt printers; otherwise this falls back to the slicing layer id.
const int _layer = this->effective_layer_index_for_point(path_point_mm);
if (_layer > 0 && _layer < m_config.slow_down_layers) {
const auto first_layer_speed =
is_perimeter(path.role())
@@ -6473,7 +6531,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
bool variable_speed = false;
std::vector<ProcessedPoint> new_points {};
if (m_config.enable_overhang_speed && !this->on_first_layer() &&
if (m_config.enable_overhang_speed && !path_on_first_layer &&
(is_bridge(path.role()) || is_perimeter(path.role()))) {
bool is_external = is_external_perimeter(path.role());
double ref_speed = is_external ? m_config.get_abs_value("outer_wall_speed") : m_config.get_abs_value("inner_wall_speed");