mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-16 07:22:10 +00:00
feat(engine): per-nozzle-variant machine limits in the time estimator
The time estimator's speed/acceleration limits were indexed by time mode only, reading slot 0 of the per-(extruder x volume-type) arrays the multi-extruder profiles already carry (H2C 0.4: 8 entries, H2D 0.4: 10). Every move was therefore modelled with the first machine slot's limits regardless of which nozzle variant was printing - estimation fidelity only, since emitted feedrates/accelerations are decided on the slicing side. Now the estimator resolves the machine slot of the nozzle currently mounted in the active extruder: the nozzle grouping context is handed to the processor BEFORE the streaming replay (new member + setter - deliberately separate from the post-stream result-field handover that gates the richer change-time model, whose timing is unchanged), the occupancy recorder is populated on every filament change (bookkeeping decoupled from the gated time model; recorder writes have no time effect), and get_machine_config_idx maps (volume type x extruder type x extruder) to the slot via the printer's variant layout, newly carried on the processor result. The feedrate/acceleration getters gain a slot parameter indexing [slot*2 + mode]; jerk and the print/travel/retract accelerations stay mode-only. Reloaded sliced projects re-estimate with the result's saved grouping context; imported bare g-code degrades to slot 0 - the historical read. M201/M203 write the parsed value into EVERY slot's mode entry (a firmware envelope change is global), which keeps per-slot reads in lockstep with the mode-only reads they replace: the fleet emits envelope lines before any motion, so estimates - hence the estimated time header, M73 lines, and every other byte - are unchanged (20/20 pinned-slice byte gate bit-identical, incl. the sequential repro sliced twice). Fidelity improves where envelope emission is off or a migrating per-layer plan moves filaments across variants. Tests: a stub-driven processor case proving the slot follows the active nozzle through the exact production path (T..H.. commands, fallback recorder bookkeeping, 4x time ratio on the slow variant), that emitted M201/M203 reach every slot, and that a missing context degrades to slot 0. Suites green (libslic3r 48998/169, fff_print 667/62).
This commit is contained in:
@@ -2453,11 +2453,16 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
|
||||
// free functions called by GCode::_do_export()
|
||||
namespace DoExport {
|
||||
static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled)
|
||||
static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled,
|
||||
const std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase>& nozzle_group_result = nullptr)
|
||||
{
|
||||
silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware)
|
||||
&& config.silent_mode;
|
||||
processor.reset();
|
||||
// Slot-resolution context for the streaming replay (reset() just cleared it). This is NOT
|
||||
// the post-stream result-field handover at the end of do_export, which gates the richer
|
||||
// change-time model and must stay after the stream.
|
||||
processor.initialize_from_context(nozzle_group_result);
|
||||
processor.initialize_result_moves();
|
||||
processor.apply_config(config);
|
||||
processor.enable_stealth_time_estimator(silent_time_estimator_enabled);
|
||||
@@ -2714,7 +2719,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
update_layer_related_config(0);
|
||||
|
||||
// modifies m_silent_time_estimator_enabled
|
||||
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled);
|
||||
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled,
|
||||
print.get_layered_nozzle_group_result());
|
||||
const bool is_bbl_printers = print.is_BBL_printer();
|
||||
const WipeTowerType wipe_tower_type = print.wipe_tower_type();
|
||||
m_calib_config.clear();
|
||||
|
||||
@@ -2548,6 +2548,9 @@ void GCodeProcessorResult::reset() {
|
||||
nozzle_group_result.reset();
|
||||
// per-extruder hotend types (pre-heat injector input); repopulated by apply_config.
|
||||
extruder_types.clear();
|
||||
// machine-slot layout of the per-variant printer arrays; repopulated by apply_config.
|
||||
printer_extruder_variant.clear();
|
||||
printer_extruder_id.clear();
|
||||
// SKIPPABLE per-type accumulated time.
|
||||
skippable_part_time.clear();
|
||||
|
||||
@@ -2945,6 +2948,8 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
||||
m_result.extruder_types.resize(config.extruder_type.values.size());
|
||||
for (size_t idx = 0; idx < config.extruder_type.values.size(); ++idx)
|
||||
m_result.extruder_types[idx] = static_cast<ExtruderType>(config.extruder_type.values[idx]);
|
||||
m_result.printer_extruder_variant = config.printer_extruder_variant.values;
|
||||
m_result.printer_extruder_id = config.printer_extruder_id.values;
|
||||
|
||||
m_extruder_offsets.resize(filament_count);
|
||||
m_extruder_colors.resize(filament_count);
|
||||
@@ -3130,6 +3135,11 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
|
||||
m_result.extruder_types[idx] = static_cast<ExtruderType>(extruder_type->values[idx]);
|
||||
}
|
||||
|
||||
if (const ConfigOptionStrings* pe_variant = config.option<ConfigOptionStrings>("printer_extruder_variant"))
|
||||
m_result.printer_extruder_variant = pe_variant->values;
|
||||
if (const ConfigOptionInts* pe_id = config.option<ConfigOptionInts>("printer_extruder_id"))
|
||||
m_result.printer_extruder_id = pe_id->values;
|
||||
|
||||
const ConfigOptionEnumsGenericNullable* nozzle_type = config.option<ConfigOptionEnumsGenericNullable>("nozzle_type");
|
||||
if (nozzle_type != nullptr) {
|
||||
m_result.nozzle_type.resize(nozzle_type->size());
|
||||
@@ -3497,6 +3507,9 @@ void GCodeProcessor::reset()
|
||||
// clear the multi-nozzle occupancy tracker between slices (the richer hotend-change model's only
|
||||
// mutable state). Inert for the single-nozzle fleet (never populated).
|
||||
m_nozzle_status_recorder = MultiNozzleUtils::NozzleStatusRecorder{};
|
||||
// drop the slot-resolution context and its cached slot; re-seeded per export.
|
||||
m_nozzle_group_result.reset();
|
||||
m_machine_config_idx = 0;
|
||||
m_extruder_colors.resize(MIN_EXTRUDERS_COUNT);
|
||||
for (size_t i = 0; i < MIN_EXTRUDERS_COUNT; ++i) {
|
||||
m_extruder_colors[i] = static_cast<unsigned char>(i);
|
||||
@@ -5056,7 +5069,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
|
||||
curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]);
|
||||
if (curr.abs_axis_feedrate[a] != 0.0f) {
|
||||
float axis_max_feedrate = get_axis_max_feedrate(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a));
|
||||
float axis_max_feedrate = get_axis_max_feedrate(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a), m_machine_config_idx);
|
||||
if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min<float>(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]);
|
||||
}
|
||||
}
|
||||
@@ -5080,7 +5093,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
|
||||
//BBS
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
float axis_max_acceleration = get_axis_max_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a));
|
||||
float axis_max_acceleration = get_axis_max_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a), m_machine_config_idx);
|
||||
if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration)
|
||||
acceleration = axis_max_acceleration / (std::abs(delta_pos[a]) * inv_distance);
|
||||
}
|
||||
@@ -5418,7 +5431,7 @@ void GCodeProcessor::process_VG1(const GCodeReader::GCodeLine& line)
|
||||
|
||||
curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]);
|
||||
if (curr.abs_axis_feedrate[a] != 0.0f) {
|
||||
float axis_max_feedrate = get_axis_max_feedrate(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a));
|
||||
float axis_max_feedrate = get_axis_max_feedrate(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a), m_machine_config_idx);
|
||||
if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min<float>(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]);
|
||||
}
|
||||
}
|
||||
@@ -5442,7 +5455,7 @@ void GCodeProcessor::process_VG1(const GCodeReader::GCodeLine& line)
|
||||
|
||||
//BBS
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
float axis_max_acceleration = get_axis_max_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a));
|
||||
float axis_max_acceleration = get_axis_max_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a), m_machine_config_idx);
|
||||
if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration)
|
||||
acceleration = axis_max_acceleration / (std::abs(delta_pos[a]) * inv_distance);
|
||||
}
|
||||
@@ -6181,16 +6194,23 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line)
|
||||
// see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration
|
||||
float factor = ((m_flavor != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
|
||||
|
||||
// Write to index i (0=Normal, 1=Stealth) — matches get_axis_max_acceleration's read pattern.
|
||||
// The arrays are slot-major ([slot*2 + mode]); a firmware M201 changes the machine's live
|
||||
// limits globally, so write the value into EVERY slot's mode entry. Orca: covering every slot
|
||||
// (not a partial range) keeps the per-slot reads in lockstep with the mode-only reads they
|
||||
// replaced. Per-mode gating unchanged: Stealth entries only once envelope processing is on.
|
||||
auto set_all_slots = [](ConfigOptionFloats &option, size_t mode, float value) {
|
||||
for (size_t slot_base = 0; slot_base < option.size(); slot_base += 2)
|
||||
set_option_value(option, slot_base + mode, value);
|
||||
};
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
|
||||
if (static_cast<PrintEstimatedStatistics::ETimeMode>(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) {
|
||||
if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor);
|
||||
if (line.has_x()) set_all_slots(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor);
|
||||
|
||||
if (line.has_y()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor);
|
||||
if (line.has_y()) set_all_slots(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor);
|
||||
|
||||
if (line.has_z()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor);
|
||||
if (line.has_z()) set_all_slots(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor);
|
||||
|
||||
if (line.has_e()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor);
|
||||
if (line.has_e()) set_all_slots(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6205,20 +6225,25 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line)
|
||||
// http://smoothieware.org/supported-g-codes
|
||||
float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfSmoothie || m_flavor == gcfKlipper) ? 1.0f : MMMIN_TO_MMSEC;
|
||||
|
||||
// Write to index i (0=Normal, 1=Stealth) — matches get_axis_max_feedrate's read pattern.
|
||||
// Slot-major arrays; a firmware M203 changes the live limits globally — write every slot's
|
||||
// mode entry (see process_M201). Per-mode gating unchanged.
|
||||
auto set_all_slots = [](ConfigOptionFloats &option, size_t mode, float value) {
|
||||
for (size_t slot_base = 0; slot_base < option.size(); slot_base += 2)
|
||||
set_option_value(option, slot_base + mode, value);
|
||||
};
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
|
||||
if (static_cast<PrintEstimatedStatistics::ETimeMode>(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) {
|
||||
if (line.has_x())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_speed_x, i, line.x() * factor);
|
||||
set_all_slots(m_time_processor.machine_limits.machine_max_speed_x, i, line.x() * factor);
|
||||
|
||||
if (line.has_y())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_speed_y, i, line.y() * factor);
|
||||
set_all_slots(m_time_processor.machine_limits.machine_max_speed_y, i, line.y() * factor);
|
||||
|
||||
if (line.has_z())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_speed_z, i, line.z() * factor);
|
||||
set_all_slots(m_time_processor.machine_limits.machine_max_speed_z, i, line.z() * factor);
|
||||
|
||||
if (line.has_e())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_speed_e, i, line.e() * factor);
|
||||
set_all_slots(m_time_processor.machine_limits.machine_max_speed_e, i, line.e() * factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6582,11 +6607,33 @@ bool GCodeProcessor::use_multi_nozzle_change_time_model() const
|
||||
// - Beyond total_(flush_)filament_changes, the richer counters (total_extruder_changes / load /
|
||||
// unload / tool_change time) are maintained in the matching branches so the multi-nozzle
|
||||
// GCodeViewer stats do not regress to zero. These are UI-only (not written to g-code).
|
||||
std::optional<MultiNozzleUtils::NozzleInfo> GCodeProcessor::resolve_target_nozzle(
|
||||
const MultiNozzleUtils::NozzleGroupResultBase &group, int id, int nozzle_id) const
|
||||
{
|
||||
std::optional<MultiNozzleUtils::NozzleInfo> info;
|
||||
if (nozzle_id != -1)
|
||||
info = group.get_nozzle_from_id(nozzle_id);
|
||||
if (!info) {
|
||||
auto used_nozzles = group.get_nozzles_for_filament(id);
|
||||
if (!used_nozzles.empty())
|
||||
info = used_nozzles.front();
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_filament_change(int id, int nozzle_id)
|
||||
{
|
||||
// Gate: outside the multi-nozzle context, or when the nozzle-grouping result is not available
|
||||
// (e.g. re-importing a bare g-code file), run the existing single-arg model byte-for-byte.
|
||||
if (!use_multi_nozzle_change_time_model() || !m_result.nozzle_group_result) {
|
||||
// Orca: occupancy bookkeeping is deliberately decoupled from the gated change-time model:
|
||||
// the per-slot machine-limit resolution needs the recorder during the streaming pass,
|
||||
// where the richer time model stays byte-frozen behind the result-field gate above.
|
||||
// Recorder writes have no time effect.
|
||||
if (m_nozzle_group_result) {
|
||||
if (auto info = resolve_target_nozzle(*m_nozzle_group_result, id, nozzle_id))
|
||||
m_nozzle_status_recorder.set_nozzle_status(info->group_id, id, info->extruder_id);
|
||||
}
|
||||
process_filament_change(id);
|
||||
return;
|
||||
}
|
||||
@@ -6604,15 +6651,7 @@ void GCodeProcessor::process_filament_change(int id, int nozzle_id)
|
||||
m_last_filament_id[prev_extruder_id] = static_cast<unsigned char>(prev_filament_id);
|
||||
|
||||
// Resolve the destination nozzle: by explicit H<nozzle> id first, else the filament's first nozzle.
|
||||
std::optional<MultiNozzleUtils::NozzleInfo> target_nozzle_info;
|
||||
if (nozzle_id != -1)
|
||||
target_nozzle_info = m_result.nozzle_group_result->get_nozzle_from_id(nozzle_id);
|
||||
if (!target_nozzle_info) {
|
||||
auto used_nozzles = m_result.nozzle_group_result->get_nozzles_for_filament(id);
|
||||
if (used_nozzles.empty())
|
||||
return;
|
||||
target_nozzle_info = used_nozzles.front();
|
||||
}
|
||||
std::optional<MultiNozzleUtils::NozzleInfo> target_nozzle_info = resolve_target_nozzle(*m_result.nozzle_group_result, id, nozzle_id);
|
||||
if (!target_nozzle_info)
|
||||
return;
|
||||
|
||||
@@ -6690,6 +6729,8 @@ void GCodeProcessor::process_filament_change(int id, int nozzle_id)
|
||||
}
|
||||
|
||||
simulate_st_synchronize(extra_time, EMoveType::Tool_change);
|
||||
|
||||
m_machine_config_idx = get_machine_config_idx();
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_filament_change(int id)
|
||||
@@ -6812,6 +6853,8 @@ void GCodeProcessor::process_filament_change(int id)
|
||||
}
|
||||
|
||||
simulate_st_synchronize(extra_time, EMoveType::Tool_change);
|
||||
|
||||
m_machine_config_idx = get_machine_config_idx();
|
||||
}
|
||||
|
||||
void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type, bool internal_only)
|
||||
@@ -6941,33 +6984,59 @@ float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMod
|
||||
return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast<size_t>(mode)));
|
||||
}
|
||||
|
||||
// Machine limit arrays hold 2 values: [0]=Normal, [1]=Stealth. Index by mode only.
|
||||
// Orca: per-(extruder x volume-type) machine limits are deliberately not resolved here even
|
||||
// though the slicing side now materializes filament_map_2 and the per-filament volume map
|
||||
// (an extruder_id*2+mode style offset would need filament_map_2 in the processor-side config
|
||||
// plus a re-audit of every get_option_value(..., mode) call). This only affects
|
||||
// time-estimation fidelity: limits are mode-indexed for ALL multi-extruder printers alike, so
|
||||
// a Hybrid extruder degrades no further than existing dual-extruder machines. Follow-up.
|
||||
// Machine slot of the nozzle currently mounted in the active extruder. Slot 0 (the historical
|
||||
// single-slot read) whenever there is no grouping context (bare g-code import), no active
|
||||
// extruder yet, or the nozzle/extruder is unknown to the recorder.
|
||||
int GCodeProcessor::get_machine_config_idx() const
|
||||
{
|
||||
const int extruder_id = get_extruder_id(false);
|
||||
if (!m_nozzle_group_result || extruder_id < 0)
|
||||
return 0;
|
||||
const int nozzle_id = m_nozzle_status_recorder.get_nozzle_in_extruder(extruder_id);
|
||||
auto nozzle_info = m_nozzle_group_result->get_nozzle_from_id(nozzle_id);
|
||||
// Orca: bounds guard — a stale grouping context after a printer swap must not index OOB.
|
||||
if (!nozzle_info || extruder_id >= (int) m_result.extruder_types.size())
|
||||
return 0;
|
||||
return std::max(0, get_config_index_base(nozzle_info->volume_type, m_result.extruder_types[extruder_id],
|
||||
extruder_id + 1, m_result.printer_extruder_variant,
|
||||
m_result.printer_extruder_id));
|
||||
}
|
||||
|
||||
// Speed/acceleration limit arrays are slot-major with two mode entries per machine slot:
|
||||
// [slot*2 + mode]. Single-variant printers have one slot, so the 2-arg forms (slot 0) read
|
||||
// exactly the historical [mode] entry.
|
||||
float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const
|
||||
{
|
||||
return get_axis_max_feedrate(mode, axis, 0);
|
||||
}
|
||||
|
||||
float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int machine_idx) const
|
||||
{
|
||||
const size_t pos = static_cast<size_t>(machine_idx) * 2 + static_cast<size_t>(mode);
|
||||
switch (axis)
|
||||
{
|
||||
case X: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_x, static_cast<size_t>(mode)); }
|
||||
case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_y, static_cast<size_t>(mode)); }
|
||||
case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_z, static_cast<size_t>(mode)); }
|
||||
case E: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_e, static_cast<size_t>(mode)); }
|
||||
case X: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_x, pos); }
|
||||
case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_y, pos); }
|
||||
case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_z, pos); }
|
||||
case E: { return get_option_value(m_time_processor.machine_limits.machine_max_speed_e, pos); }
|
||||
default: { return 0.0f; }
|
||||
}
|
||||
}
|
||||
|
||||
float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const
|
||||
{
|
||||
return get_axis_max_acceleration(mode, axis, 0);
|
||||
}
|
||||
|
||||
float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int machine_idx) const
|
||||
{
|
||||
const size_t pos = static_cast<size_t>(machine_idx) * 2 + static_cast<size_t>(mode);
|
||||
switch (axis)
|
||||
{
|
||||
case X: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, static_cast<size_t>(mode)); }
|
||||
case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, static_cast<size_t>(mode)); }
|
||||
case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, static_cast<size_t>(mode)); }
|
||||
case E: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, static_cast<size_t>(mode)); }
|
||||
case X: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, pos); }
|
||||
case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, pos); }
|
||||
case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, pos); }
|
||||
case E: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, pos); }
|
||||
default: { return 0.0f; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,6 +298,10 @@ class Print;
|
||||
// (mixed-type X2D workaround). Populated in apply_config; unused until the injector side-pass
|
||||
// consumes it.
|
||||
std::vector<ExtruderType> extruder_types;
|
||||
// Machine-slot layout of the per-variant printer arrays (one entry per (extruder x
|
||||
// volume-type) slot). Populated in apply_config; keys the per-slot machine-limit lookup.
|
||||
std::vector<std::string> printer_extruder_variant;
|
||||
std::vector<int> printer_extruder_id;
|
||||
// first key stores filaments, second keys stores the layer ranges(enclosed) that use the filaments
|
||||
std::unordered_map<std::vector<unsigned int>, std::vector<std::pair<int, int>>,FilamentSequenceHash> layer_filaments;
|
||||
std::vector<unsigned int> nozzle_change_sequence;
|
||||
@@ -349,6 +353,8 @@ class Print;
|
||||
nozzle_group_result = other.nozzle_group_result;
|
||||
// Keep the per-extruder hotend types on a copied result (injector input).
|
||||
extruder_types = other.extruder_types;
|
||||
printer_extruder_variant = other.printer_extruder_variant;
|
||||
printer_extruder_id = other.printer_extruder_id;
|
||||
layer_filaments = other.layer_filaments;
|
||||
filament_change_sequence = other.filament_change_sequence;
|
||||
nozzle_change_sequence = other.nozzle_change_sequence;
|
||||
@@ -1104,9 +1110,15 @@ class Print;
|
||||
unsigned int m_machine_start_gcode_end_line_id{ (unsigned int) (-1) };
|
||||
unsigned int m_machine_end_gcode_start_line_id{ (unsigned int) (-1) };
|
||||
// Tracks, during the stream, which filament sits in each physical nozzle and which nozzle each
|
||||
// extruder currently carries. Consumed ONLY by the richer two-arg process_filament_change
|
||||
// model, which single-nozzle printers (X1/P1/A1/H2S/A2L) never enter.
|
||||
// extruder currently carries. Written by both branches of the two-arg process_filament_change
|
||||
// (the fallback branch does occupancy bookkeeping only); read by the richer change-time model
|
||||
// and by the per-slot machine-limit resolution. Single-nozzle printers never populate it.
|
||||
MultiNozzleUtils::NozzleStatusRecorder m_nozzle_status_recorder;
|
||||
// Nozzle grouping context for slot resolution during the streaming pass. Set before the
|
||||
// replay begins (see initialize_from_context); deliberately separate from
|
||||
// m_result.nozzle_group_result, which is handed over only after the stream for the
|
||||
// pre-heat injector's second pass and gates the richer change-time model.
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> m_nozzle_group_result;
|
||||
bool m_manual_filament_change;
|
||||
|
||||
//BBS: x, y offset for gcode generated
|
||||
@@ -1131,6 +1143,9 @@ class Print;
|
||||
std::vector<unsigned char> m_last_filament_id;
|
||||
std::vector<unsigned char> m_filament_id;
|
||||
unsigned char m_extruder_id;
|
||||
// Cached get_machine_config_idx() value; its inputs (active extruder + recorder occupancy)
|
||||
// change only on filament-change events, where it is recomputed.
|
||||
int m_machine_config_idx{0};
|
||||
ExtruderColors m_extruder_colors;
|
||||
ExtruderTemps m_extruder_temps;
|
||||
bool m_is_XL_printer = false;
|
||||
@@ -1193,6 +1208,11 @@ class Print;
|
||||
const std::vector<std::set<int>>& unprintable_filament_types );
|
||||
void apply_config(const PrintConfig& config);
|
||||
void set_print(Print* print) { m_print = print; }
|
||||
// Hand the nozzle grouping context to the estimator BEFORE the streaming replay, so the
|
||||
// per-slot machine-limit resolution can follow the active nozzle. Null is fine (slot 0).
|
||||
void initialize_from_context(const std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase>& nozzle_group_result) {
|
||||
m_nozzle_group_result = nozzle_group_result;
|
||||
}
|
||||
|
||||
DynamicConfig export_config_for_render() const;
|
||||
|
||||
@@ -1412,6 +1432,15 @@ class Print;
|
||||
// filament-in-nozzle change. Self-gated: for single-nozzle printers it delegates to
|
||||
// process_filament_change(int) so their time estimate — hence exported g-code — is unchanged.
|
||||
void process_filament_change(int id, int nozzle_id);
|
||||
// Destination nozzle of a filament change: the explicit H<nozzle> id when given, else the
|
||||
// filament's first nozzle in the grouping. Shared by the change-time model and the
|
||||
// fallback-path occupancy bookkeeping.
|
||||
std::optional<MultiNozzleUtils::NozzleInfo> resolve_target_nozzle(
|
||||
const MultiNozzleUtils::NozzleGroupResultBase &group, int id, int nozzle_id) const;
|
||||
// Machine slot of the nozzle currently mounted in the active extruder (0 when no grouping
|
||||
// context / unknown extruder — the single-slot layout). Cached in m_machine_config_idx,
|
||||
// recomputed on filament-change events.
|
||||
int get_machine_config_idx() const;
|
||||
// True only for multi-nozzle-capable printers (H2C cluster, or a dual/multi-extruder machine
|
||||
// like H2D/X2D): the gate that admits the richer two-arg hotend-change time model. False for
|
||||
// every single-extruder single-nozzle printer (X1/P1/A1/H2S/A2L).
|
||||
@@ -1440,11 +1469,14 @@ class Print;
|
||||
|
||||
float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const;
|
||||
float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const;
|
||||
// Machine limit arrays are indexed by time mode only: [0]=Normal, [1]=Stealth.
|
||||
// Do NOT add an extruder_id parameter — OrcaSlicer does not use BambuStudio's
|
||||
// per-nozzle machine limits (filament_map_2 / get_config_idx_for_filament).
|
||||
// Speed/acceleration limit arrays are slot-major with two mode entries per machine slot:
|
||||
// [slot*2 + mode], slot from get_machine_config_idx() (0 = the only slot on single-variant
|
||||
// printers, whose arrays hold just [Normal, Stealth]). The 2-arg forms read slot 0 and stay
|
||||
// exactly the historical mode-only lookup; jerk and the accelerations below are mode-only.
|
||||
float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int machine_idx) const;
|
||||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int machine_idx) const;
|
||||
float get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis, float acceleration) const;
|
||||
float get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
|
||||
@@ -3453,8 +3453,8 @@ void Print::update_to_config_by_nozzle_group_result(const MultiNozzleUtils::Laye
|
||||
// Note: filament_map_2 keeps its apply-time (static) derivation here; the per-slot machine
|
||||
// indices below key the override merge instead, so nothing on this path reads it. Its other
|
||||
// consumers are the three-map write-back (which recomputes it) and the diagnostic copy in
|
||||
// the g-code header; per-(extruder x volume-type) machine limits in the g-code processor
|
||||
// remain a follow-up (see GCodeProcessor::get_axis_max_feedrate).
|
||||
// the g-code header; the time estimator resolves per-(extruder x volume-type) machine limits
|
||||
// from the live nozzle occupancy instead (see GCodeProcessor::get_machine_config_idx).
|
||||
m_full_print_config = m_ori_full_print_config;
|
||||
std::set<std::string> filament_keys = filament_options_with_variant;
|
||||
filament_keys.insert("filament_self_index");
|
||||
@@ -4257,6 +4257,10 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
|
||||
const Vec3d origin = this->get_plate_origin();
|
||||
processor.set_xy_offset(origin(0), origin(1));
|
||||
// Reloaded sliced projects re-estimate with the same nozzle-grouping slot context as the
|
||||
// original export; harmless when the result carries none (slot 0).
|
||||
if (result != nullptr && result->nozzle_group_result)
|
||||
processor.initialize_from_context(result->nozzle_group_result);
|
||||
//processor.enable_producers(true);
|
||||
processor.process_file(file);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user