mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-16 15:32:09 +00:00
feat(belt/calib): add Overhang temperature-tower model (selectable) (#48)
Belt printers can't slice a tall vertical temperature tower. This adds a belt-specific temperature-tower model — a row of discrete, individually engraved provini laid along the belt, each printed at one temperature via custom per-layer M104. Each provino is an inverted-L overhang that stresses print quality, so the operator reads the best temperature off overhang quality rather than a continuous ramp. It is offered as a "Test model" choice in the temperature calibration dialog (mirroring the Cornering test's selector), so users keep Joe's counter-rotated sectioned tower as "Standard" and can pick this one as "Overhang": - Calib_Params::test_model (existing field) carries the choice. - Temp_Calibration_Dlg gets a Standard/Overhang radio. - Plater::calib_temp belt branch: test_model 0 -> _calib_temp_belt_sectioned (unchanged Standard path), 1 -> the discrete-provini Overhang path. Assets: belt_temp_provino_unit.stl + belt_temp_tower_<start>_<end>.stl (6 ranges) + gen_belt_temp_tower.py (manifold engraving). Based on belt/generic-calibrations. The Overhang path is HW-validated on the IdeaFormer IR3 V2 (discrete M104 + engraved numbers); not re-validated since the rebase.
This commit is contained in:
committed by
harrierpigeon
parent
0da24cd38b
commit
85fd613cf7
@@ -13297,14 +13297,96 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
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.
|
||||
// Belt printers build along the conveyor, not vertically — a tall tower cannot
|
||||
// be sliced. Instead lay a row of DISCRETE provini (one per temperature) along
|
||||
// the belt and change temperature in discrete steps via custom per-layer G-code
|
||||
// (M104), injected in the empty gap just before each provino so the nozzle is
|
||||
// settled by the time that provino prints. We deliberately do NOT call
|
||||
// set_calib_params here: its per-layer interpolation (interpolate_value_across_
|
||||
// layers) would overwrite these discrete M104 events.
|
||||
{
|
||||
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));
|
||||
auto belt_printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
if (belt_printer_config->has("belt_printer") && belt_printer_config->opt_bool("belt_printer")) {
|
||||
// Belt temperature-tower model selector (Calib_Params::test_model):
|
||||
// 0 = "Standard" -> Joe's counter-rotated sectioned tower.
|
||||
// 1 = "Overhang" -> engraved inverted-L provini that stress overhang
|
||||
// print-quality at each discrete temperature (below).
|
||||
if (params.test_model < 1) {
|
||||
double belt_angle_rad = 0.; Vec3d belt_axis = Vec3d::UnitX();
|
||||
belt_calib_rotation_params(belt_angle_rad, belt_axis);
|
||||
_calib_temp_belt_sectioned(params, std::abs(belt_angle_rad));
|
||||
return;
|
||||
}
|
||||
constexpr int TEMP_STEP = 5; // matches Temp_Calibration_Dlg
|
||||
// Shared geometry contract with the offline asset generator
|
||||
// (resources/calib/temperature_tower/gen_belt_temp_tower.py): provini are
|
||||
// replicated along the belt (designed Y) at this pitch. The slicing plane
|
||||
// is oblique (belt_slice_rotation_angle), so the per-zone advance in the
|
||||
// layer print_z space the custom-gcode matcher uses is PITCH*cos(theta).
|
||||
constexpr double PITCH_Y = 74.718; // designed-Y pitch == gen PITCH
|
||||
const double angle = belt_printer_config->has("belt_slice_rotation_angle")
|
||||
? belt_printer_config->opt_float("belt_slice_rotation_angle") : 45.0;
|
||||
const double zone_topz = PITCH_Y * std::cos(angle * M_PI / 180.0);
|
||||
// The custom-gcode matcher attaches each event to a real sliced layer. The
|
||||
// empty inter-provino gap has NO layers, so an event placed there is silently
|
||||
// dropped. Fire it 70 layers ABOVE provino i's start instead — inside the
|
||||
// provino body, past the gap and the overlap with provino i-1's tail, so the
|
||||
// temperature change attaches and applies cleanly. (layer print_z steps by the
|
||||
// process layer height.)
|
||||
auto belt_print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
const double layer_h = belt_print_config->has("layer_height")
|
||||
? belt_print_config->opt_float("layer_height") : 0.2;
|
||||
const double into_provino = 70.0 * layer_h;
|
||||
|
||||
const int t_start = (int) lround(params.start);
|
||||
const int t_end = (int) lround(params.end);
|
||||
std::vector<int> temps;
|
||||
const int tstep = (t_start >= t_end) ? -TEMP_STEP : TEMP_STEP;
|
||||
for (int t = t_start; (tstep < 0) ? (t >= t_end) : (t <= t_end); t += tstep)
|
||||
temps.push_back(t);
|
||||
if (temps.empty()) temps.push_back(t_start);
|
||||
|
||||
const std::string calib_dir = Slic3r::resources_dir() + "/calib/temperature_tower/";
|
||||
std::string asset = calib_dir + "belt_temp_tower_" + std::to_string(t_start) + "_" + std::to_string(t_end) + ".stl";
|
||||
if (!boost::filesystem::exists(asset)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[belt_temp] no embossed provini for " << t_start << "->" << t_end
|
||||
<< ", falling back to 230_190 (embossed numbers will not match)";
|
||||
asset = calib_dir + "belt_temp_tower_230_190.stl";
|
||||
}
|
||||
add_model(false, asset);
|
||||
|
||||
// Place keel-first asset at the belt entry (designed Y = 0) so Z_gcode
|
||||
// starts at 0, centered laterally on the bed, resting on the conveyor.
|
||||
ModelObject* obj = model().objects[0];
|
||||
obj->ensure_on_bed();
|
||||
BoundingBoxf3 obb = obj->bounding_box_exact();
|
||||
auto bed_shape = belt_printer_config->option<ConfigOptionPoints>("printable_area")->values;
|
||||
BoundingBoxf bed_ext = get_extents(bed_shape);
|
||||
obj->translate_instances(Vec3d(bed_ext.center().x() - obb.center().x(), -obb.min.y(), 0.0));
|
||||
|
||||
auto belt_filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
|
||||
belt_filament_config->set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts(1, temps.front()));
|
||||
belt_filament_config->set_key_value("nozzle_temperature", new ConfigOptionInts(1, temps.front()));
|
||||
obj->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btNoBrim));
|
||||
obj->config.set_key_value("seam_slope_type", new ConfigOptionEnum<SeamScarfType>(SeamScarfType::None));
|
||||
obj->config.set_key_value("overhang_reverse", new ConfigOptionBool(false));
|
||||
belt_printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||
|
||||
const int plate_idx = get_partplate_list().get_curr_plate_index();
|
||||
model().curr_plate_index = plate_idx;
|
||||
CustomGCode::Info &cg_info = model().plates_custom_gcodes[plate_idx];
|
||||
cg_info.mode = CustomGCode::Mode::SingleExtruder;
|
||||
cg_info.gcodes.clear();
|
||||
for (size_t i = 1; i < temps.size(); ++i) {
|
||||
const double pz = double(i) * zone_topz + into_provino; // 70 layers into provino i
|
||||
cg_info.gcodes.push_back(CustomGCode::Item{
|
||||
pz, CustomGCode::Custom, 1, "",
|
||||
"M104 S" + std::to_string(temps[i]) + " ; belt temp zone " + std::to_string(temps[i]) });
|
||||
}
|
||||
|
||||
changed_objects({ 0 });
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_dirty();
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->reload_config();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,6 +395,15 @@ Temp_Calibration_Dlg::Temp_Calibration_Dlg(wxWindow* parent, wxWindowID id, Plat
|
||||
method_box->Add(m_rbFilamentType, 0, wxALL | wxEXPAND, FromDIP(4));
|
||||
v_sizer->Add(method_box, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10));
|
||||
|
||||
// Belt temperature-tower model: Standard (sectioned tower) vs Overhang (engraved
|
||||
// inverted-L provini that stress overhang quality per temperature). Only affects
|
||||
// belt printers; the upright tower ignores it.
|
||||
auto labeled_box_model = new LabeledStaticBox(this, _L("Test model"));
|
||||
auto model_box = new wxStaticBoxSizer(labeled_box_model, wxHORIZONTAL);
|
||||
m_rbModel = new RadioGroup(this, { _L("Standard"), _L("Overhang") }, wxVERTICAL);
|
||||
model_box->Add(m_rbModel, 0, wxALL | wxEXPAND, FromDIP(4));
|
||||
v_sizer->Add(model_box, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10));
|
||||
|
||||
// Settings
|
||||
wxString start_temp_str = _L("Start temp: ");
|
||||
wxString end_temp_str = _L("End temp: ");
|
||||
@@ -511,6 +520,7 @@ void Temp_Calibration_Dlg::on_start(wxCommandEvent& event) {
|
||||
m_params.start = start;
|
||||
m_params.end = end;
|
||||
m_params.mode = CalibMode::Calib_Temp_Tower;
|
||||
m_params.test_model = m_rbModel->GetSelection();
|
||||
m_plater->calib_temp(m_params);
|
||||
EndModal(wxID_OK);
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ protected:
|
||||
Calib_Params m_params;
|
||||
|
||||
RadioGroup* m_rbFilamentType;
|
||||
RadioGroup* m_rbModel;
|
||||
TextInput* m_tiStart;
|
||||
TextInput* m_tiEnd;
|
||||
TextInput* m_tiStep;
|
||||
|
||||
Reference in New Issue
Block a user