feat(gcode): resolve per-filament variant slots per layer

- the g-code writer tracks the current layer id and resolves
  FILAMENT_CONFIG/NOZZLE_CONFIG (plus every non-macro variant lookup,
  toolchange placeholder scalars, and the change-filament flush
  overrides) through Print's per-filament, per-layer config-index
  resolvers instead of the filament->extruder collapse
- update_layer_related_config refreshes the per-layer
  extruder/volume/nozzle maps in the writer config;
  update_placeholder_parser_with_variant_params remaps the
  filament-variant arrays into filament-id space for custom g-code
  (Orca's flush placeholder computation moves inside it)
- the engine's concrete per-filament volume assignment now merges into
  the config write-back (the temporary hold from the producer commit
  is lifted together with these consumers), and the background process
  reads the computed volume map back to the plate
- append_full_config dumps the resolved filament_map_2 slots
- update_used_filament_values gains a bounds guard
- tests: per-filament Hybrid slot resolution + null-result fallback

Result: on a Hybrid extruder, each filament's features slice with its
assigned sub-nozzle's variant values (speeds, volumetric limits,
retraction). Verified on a 4-filament H2C Hybrid project: outer walls
split into three feedrate populations (30/50/200 mm/s), toolpath
geometry byte-identical, deterministic across repeated slices. All 18
non-Hybrid reference fixtures stay byte-identical except the
filament_map_2 header value now showing the real slot. Auto grouping
ties (multiple zero-flush perfect matchings) may pick a different
filament-to-nozzle isolation than other slicers; verified co-optimal.
This commit is contained in:
SoftFever
2026-07-11 15:00:33 +08:00
parent 03a704df7c
commit 21b46044d0
8 changed files with 312 additions and 107 deletions

View File

@@ -331,6 +331,10 @@ namespace FilamentGroupUtils
{ {
std::vector<int> res = old_values; std::vector<int> res = old_values;
for (size_t i = 0; i < used_filaments.size(); ++i) { for (size_t i = 0; i < used_filaments.size(); ++i) {
// Orca: guard against filament ids beyond the map sizes (possible with
// mis-normalized per-filament arrays from CLI inputs); skip instead of UB.
if (used_filaments[i] >= res.size() || used_filaments[i] >= new_values.size())
continue;
res[used_filaments[i]] = new_values[used_filaments[i]]; res[used_filaments[i]] = new_values[used_filaments[i]];
} }
return res; return res;

View File

@@ -457,7 +457,9 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
if(retraction_length_remaining <=EPSILON) return {retractionBeforeWipe,0.f}; if(retraction_length_remaining <=EPSILON) return {retractionBeforeWipe,0.f};
// Calculate wipe speed // Calculate wipe speed
double wipe_speed = config.role_based_wipe_speed ? writer.get_current_speed() / 60.0 : config.get_abs_value("wipe_speed", gcodegen.config().travel_speed.get_at(gcodegen.cur_extruder_index())); // Orca: resolve the travel_speed slot via the Print-side per-layer resolver; the writer's
// per-layer synced config would yield the same index.
double wipe_speed = config.role_based_wipe_speed ? writer.get_current_speed() / 60.0 : config.get_abs_value("wipe_speed", gcodegen.config().travel_speed.get_at(gcodegen.get_nozzle_config_index(gcodegen.writer().filament()->id())));
wipe_speed = std::max(wipe_speed, 10.0); wipe_speed = std::max(wipe_speed, 10.0);
// Process wipe path & calculate wipe path length // Process wipe path & calculate wipe path length
@@ -549,7 +551,9 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
/* Reduce feedrate a bit; travel speed is often too high to move on existing material. /* Reduce feedrate a bit; travel speed is often too high to move on existing material.
Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */
double _wipe_speed = gcodegen.config().get_abs_value("wipe_speed", gcodegen.config().travel_speed.get_at(gcodegen.cur_extruder_index()));// gcodegen.writer().config.travel_speed.value * 0.8; // Orca: resolve the travel_speed slot via the Print-side per-layer resolver; the writer's
// per-layer synced config would yield the same index.
double _wipe_speed = gcodegen.config().get_abs_value("wipe_speed", gcodegen.config().travel_speed.get_at(gcodegen.get_nozzle_config_index(gcodegen.writer().filament()->id())));// gcodegen.writer().config.travel_speed.value * 0.8;
if(gcodegen.config().role_based_wipe_speed) if(gcodegen.config().role_based_wipe_speed)
_wipe_speed = gcodegen.writer().get_current_speed() / 60.0; _wipe_speed = gcodegen.writer().get_current_speed() / 60.0;
if(_wipe_speed < 10) if(_wipe_speed < 10)
@@ -1089,25 +1093,30 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x()))); config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x())));
config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y())));
auto flush_v_speed = m_print_config->filament_flush_volumetric_speed.values; {
// Fast purge mode uses filament_flush_temp_fast; Default is inert. size_t num_filaments = m_print_config->filament_type.values.size();
auto flush_temps = (m_print_config->prime_volume_mode == PrimeVolumeMode::pvmFast ? m_print_config->filament_flush_temp_fast : m_print_config->filament_flush_temp).values; // Fast purge mode uses filament_flush_temp_fast; Default is inert.
auto filament_cooling_before_tower = m_print_config->filament_cooling_before_tower.values; bool use_fast_flush = m_print_config->prime_volume_mode == PrimeVolumeMode::pvmFast;
for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) { std::vector<double> flush_v_speed(num_filaments);
if (flush_v_speed[idx] == 0) std::vector<int> flush_temps(num_filaments);
flush_v_speed[idx] = m_print_config->filament_max_volumetric_speed.get_at(idx); std::vector<double> filament_cooling_before_tower(num_filaments);
for (size_t idx = 0; idx < num_filaments; ++idx) {
size_t fi = gcodegen.get_filament_config_index(idx);
flush_v_speed[idx] = m_print_config->filament_flush_volumetric_speed.get_at(fi);
if (flush_v_speed[idx] == 0)
flush_v_speed[idx] = m_print_config->filament_max_volumetric_speed.get_at(fi);
flush_temps[idx] = use_fast_flush ? m_print_config->filament_flush_temp_fast.get_at(fi)
: m_print_config->filament_flush_temp.get_at(fi);
if (flush_temps[idx] == 0)
flush_temps[idx] = m_print_config->nozzle_temperature_range_high.get_at(idx);
filament_cooling_before_tower[idx] = m_print_config->filament_cooling_before_tower.get_at(fi);
}
if (tcr.is_contact || gcodegen.m_layer_index == 0)
std::fill(filament_cooling_before_tower.begin(), filament_cooling_before_tower.end(), 0);
config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps));
config.set_key_value("filament_cooling_before_tower", new ConfigOptionFloats(filament_cooling_before_tower));
} }
for (size_t idx = 0; idx < flush_temps.size(); ++idx) {
if (flush_temps[idx] == 0)
flush_temps[idx] = m_print_config->nozzle_temperature_range_high.get_at(idx);
}
if (filament_cooling_before_tower.size() < m_print_config->filament_type.values.size())
filament_cooling_before_tower.resize(m_print_config->filament_type.values.size(), m_print_config->filament_cooling_before_tower.get_at(0));
if (tcr.is_contact || gcodegen.m_layer_index == 0)
std::fill(filament_cooling_before_tower.begin(), filament_cooling_before_tower.end(), 0);
config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps));
config.set_key_value("filament_cooling_before_tower", new ConfigOptionFloats(filament_cooling_before_tower));
config.set_key_value("flush_length", new ConfigOptionFloat(purge_length)); config.set_key_value("flush_length", new ConfigOptionFloat(purge_length));
config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(is_used_travel_avoid_perimeter)); config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(is_used_travel_avoid_perimeter));
config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x));
@@ -1238,10 +1247,13 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_unretract_str); check_add_eol(toolchange_unretract_str);
gcodegen.placeholder_parser().set("current_extruder", new_filament_id); gcodegen.placeholder_parser().set("current_extruder", new_filament_id);
gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(new_filament_id)); {
gcodegen.placeholder_parser().set("long_retraction_when_cut", gcodegen.m_config.long_retractions_when_cut.get_at(new_filament_id)); size_t fi = gcodegen.get_filament_config_index(new_filament_id);
gcodegen.placeholder_parser().set("retraction_distance_when_ec", gcodegen.m_config.retraction_distances_when_ec.get_at(new_filament_id)); gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(fi));
gcodegen.placeholder_parser().set("long_retraction_when_ec", gcodegen.m_config.long_retractions_when_ec.get_at(new_filament_id)); gcodegen.placeholder_parser().set("long_retraction_when_cut", gcodegen.m_config.long_retractions_when_cut.get_at(fi));
gcodegen.placeholder_parser().set("retraction_distance_when_ec", gcodegen.m_config.retraction_distances_when_ec.get_at(fi));
gcodegen.placeholder_parser().set("long_retraction_when_ec", gcodegen.m_config.long_retractions_when_ec.get_at(fi));
}
// Process the start filament gcode. // Process the start filament gcode.
std::string start_filament_gcode_str; std::string start_filament_gcode_str;
@@ -1791,8 +1803,8 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
const std::vector<std::string> ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" }; const std::vector<std::string> ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" };
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.filament()->extruder_id()) #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.filament()->extruder_id())
#define FILAMENT_CONFIG(OPT) m_config.OPT.get_at(m_writer.filament()->id()) #define FILAMENT_CONFIG(OPT) m_config.OPT.get_at(get_filament_config_index(m_writer.filament()->id()))
#define NOZZLE_CONFIG(OPT) m_config.OPT.get_at(cur_extruder_index()) #define NOZZLE_CONFIG(OPT) m_config.OPT.get_at(get_nozzle_config_index(m_writer.filament()->id()))
void GCode::PlaceholderParserIntegration::reset() void GCode::PlaceholderParserIntegration::reset()
{ {
@@ -2676,6 +2688,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_print = &print; m_print = &print;
m_timelapse_pos_picker.init(&print,m_writer.get_xy_offset().cast<coord_t>()); m_timelapse_pos_picker.init(&print,m_writer.get_xy_offset().cast<coord_t>());
// init as filament map
update_layer_related_config(0);
// modifies m_silent_time_estimator_enabled // 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);
@@ -3086,15 +3100,19 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
this->placeholder_parser().set("initial_nozzle_id", first_nozzle_id_for_gcode_placeholder(group_result, (int) initial_extruder_id, extruder_id)); this->placeholder_parser().set("initial_nozzle_id", first_nozzle_id_for_gcode_placeholder(group_result, (int) initial_extruder_id, extruder_id));
this->placeholder_parser().set("nozzle_diameter_at_nozzle_id", new ConfigOptionFloats(get_nozzle_diameters_by_nozzle_id(group_result.get()))); this->placeholder_parser().set("nozzle_diameter_at_nozzle_id", new ConfigOptionFloats(get_nozzle_diameters_by_nozzle_id(group_result.get())));
this->placeholder_parser().set("nozzle_volume_types", new ConfigOptionStrings(get_nozzle_volume_types_by_nozzle_id(group_result.get()))); this->placeholder_parser().set("nozzle_volume_types", new ConfigOptionStrings(get_nozzle_volume_types_by_nozzle_id(group_result.get())));
//Orca: set the key for compatibilty //Orca: set the key for compatibilty, scalar values for the initial extruder (variant-aware)
this->placeholder_parser().set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(initial_extruder_id)); {
this->placeholder_parser().set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(initial_extruder_id)); size_t fi = get_filament_config_index(initial_extruder_id);
this->placeholder_parser().set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(initial_extruder_id)); this->placeholder_parser().set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(fi));
this->placeholder_parser().set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(initial_extruder_id)); this->placeholder_parser().set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(fi));
this->placeholder_parser().set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(fi));
this->placeholder_parser().set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(fi));
}
this->placeholder_parser().set("temperature", new ConfigOptionInts(print.config().nozzle_temperature)); this->placeholder_parser().set("temperature", new ConfigOptionInts(print.config().nozzle_temperature));
// retraction_distances_when_cut (array), flush_volumetric_speeds, flush_temperatures and
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(m_config.retraction_distances_when_cut)); // filament_cooling_before_tower are set by update_placeholder_parser_with_variant_params()
// with variant-aware remapping.
this->placeholder_parser().set("long_retractions_when_cut",new ConfigOptionBools(m_config.long_retractions_when_cut)); this->placeholder_parser().set("long_retractions_when_cut",new ConfigOptionBools(m_config.long_retractions_when_cut));
this->placeholder_parser().set("retraction_distances_when_ec", new ConfigOptionFloatsNullable(m_config.retraction_distances_when_ec)); this->placeholder_parser().set("retraction_distances_when_ec", new ConfigOptionFloatsNullable(m_config.retraction_distances_when_ec));
this->placeholder_parser().set("long_retractions_when_ec",new ConfigOptionBoolsNullable(m_config.long_retractions_when_ec)); this->placeholder_parser().set("long_retractions_when_ec",new ConfigOptionBoolsNullable(m_config.long_retractions_when_ec));
@@ -3104,20 +3122,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
this->placeholder_parser().set("close_additional_fan_first_x_layers", new ConfigOptionInts(m_config.close_additional_fan_first_x_layers)); this->placeholder_parser().set("close_additional_fan_first_x_layers", new ConfigOptionInts(m_config.close_additional_fan_first_x_layers));
this->placeholder_parser().set("additional_fan_full_speed_layer", new ConfigOptionInts(m_config.additional_fan_full_speed_layer)); this->placeholder_parser().set("additional_fan_full_speed_layer", new ConfigOptionInts(m_config.additional_fan_full_speed_layer));
auto flush_v_speed = m_config.filament_flush_volumetric_speed.values;
// Fast purge mode uses filament_flush_temp_fast; Default is inert.
auto flush_temps = (m_config.prime_volume_mode == PrimeVolumeMode::pvmFast ? m_config.filament_flush_temp_fast : m_config.filament_flush_temp).values;
for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) {
if (flush_v_speed[idx] == 0)
flush_v_speed[idx] = m_config.filament_max_volumetric_speed.get_at(idx);
}
for (size_t idx = 0; idx < flush_temps.size(); ++idx) {
if (flush_temps[idx] == 0)
flush_temps[idx] = m_config.nozzle_temperature_range_high.get_at(idx);
}
this->placeholder_parser().set("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
this->placeholder_parser().set("flush_temperatures", new ConfigOptionInts(flush_temps));
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloatsNullable(m_config.filament_cooling_before_tower));
//Set variable for total layer count so it can be used in custom gcode. //Set variable for total layer count so it can be used in custom gcode.
this->placeholder_parser().set("total_layer_count", m_layer_count); this->placeholder_parser().set("total_layer_count", m_layer_count);
// Useful for sequential prints. // Useful for sequential prints.
@@ -3369,6 +3373,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
this->placeholder_parser().set("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder))); this->placeholder_parser().set("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
this->placeholder_parser().set("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder))); this->placeholder_parser().set("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
// Sync variant-mapped params into placeholder_parser before processing start gcode
update_placeholder_parser_with_variant_params();
std::string machine_start_gcode = this->placeholder_parser_process("machine_start_gcode", print.config().machine_start_gcode.value, initial_extruder_id); std::string machine_start_gcode = this->placeholder_parser_process("machine_start_gcode", print.config().machine_start_gcode.value, initial_extruder_id);
if (print.config().gcode_flavor != gcfKlipper) { if (print.config().gcode_flavor != gcfKlipper) {
// Set bed temperature if the start G-code does not contain any bed temp control G-codes. // Set bed temperature if the start G-code does not contain any bed temp control G-codes.
@@ -3963,6 +3970,25 @@ size_t GCode::get_extruder_id(unsigned int filament_id) const
return 0; return 0;
} }
size_t GCode::get_filament_config_index(int filament_id) const
{
if (m_print) {
return m_print->get_filament_config_indx(filament_id, m_cur_layer_idx);
}
// Orca: without a Print the filament-indexed arrays are unexpanded, so the
// filament id itself is the only meaningful column.
return filament_id;
}
size_t GCode::get_nozzle_config_index(int filament_id) const
{
if (m_print) {
return m_print->get_nozzle_config_index(filament_id, m_cur_layer_idx);
}
// Orca: same reasoning; degenerate to the filament's extruder column.
return get_extruder_id(filament_id);
}
// Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Process all layers of all objects (non-sequential mode) with a parallel pipeline:
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
// and export G-code into file. // and export G-code into file.
@@ -5193,6 +5219,7 @@ LayerResult GCode::process_layer(
else if (support_layer != nullptr) else if (support_layer != nullptr)
layer_ptr = support_layer; layer_ptr = support_layer;
const Layer& layer = *layer_ptr; const Layer& layer = *layer_ptr;
m_cur_layer_idx = layer.id();
LayerResult result { {}, layer.id(), false, last_layer }; LayerResult result { {}, layer.id(), false, last_layer };
if (layer_tools.extruders.empty()) if (layer_tools.extruders.empty())
// Nothing to extrude. // Nothing to extrude.
@@ -5288,6 +5315,8 @@ LayerResult GCode::process_layer(
// BBS: don't use lazy_raise when enable spiral vase // BBS: don't use lazy_raise when enable spiral vase
gcode += this->change_layer(print_z); // this will increase m_layer_index gcode += this->change_layer(print_z); // this will increase m_layer_index
update_layer_related_config(m_layer_index);
update_placeholder_parser_with_variant_params();
m_layer = &layer; m_layer = &layer;
m_object_layer_over_raft = false; m_object_layer_over_raft = false;
@@ -5501,7 +5530,7 @@ LayerResult GCode::process_layer(
if (layer_to_print.object_layer) { if (layer_to_print.object_layer) {
const auto& regions = layer_to_print.object_layer->regions(); const auto& regions = layer_to_print.object_layer->regions();
const bool enable_overhang_speed = std::any_of(regions.begin(), regions.end(), [this](const LayerRegion* r) { const bool enable_overhang_speed = std::any_of(regions.begin(), regions.end(), [this](const LayerRegion* r) {
return r->has_extrusions() && r->region().config().enable_overhang_speed.get_at(cur_extruder_index()); return r->has_extrusions() && r->region().config().enable_overhang_speed.get_at(get_nozzle_config_index(m_writer.filament()->id()));
}); });
if (enable_overhang_speed) { if (enable_overhang_speed) {
m_extrusion_quality_estimator.prepare_for_new_layer(layer_to_print.original_object, m_extrusion_quality_estimator.prepare_for_new_layer(layer_to_print.original_object,
@@ -6269,6 +6298,11 @@ void GCode::append_full_config(const Print &print, std::string &str)
throw Slic3r::SlicingError(_(L("Flush volumes matrix do not match to the correct size!"))); throw Slic3r::SlicingError(_(L("Flush volumes matrix do not match to the correct size!")));
} }
} }
// filament_map_2 (the per-filament (extruder x volume-type) slot map) is computed during apply /
// write-back and lives in the applied print config only; the pre-expansion snapshot this dump is
// built from still carries the registered default. Copy the real values so the header line is
// diagnostic (nothing parses it back).
cfg.option<ConfigOptionInts>("filament_map_2", true)->values = print.config().filament_map_2.values;
// Sorted list of config keys, which shall not be stored into the G-code. Initializer list. // Sorted list of config keys, which shall not be stored into the G-code. Initializer list.
static const std::set<std::string_view> banned_keys( { static const std::set<std::string_view> banned_keys( {
// filament_extruder_compatibility is a device-side (blacklist) compatibility hint read from the // filament_extruder_compatibility is a device-side (blacklist) compatibility hint read from the
@@ -7116,12 +7150,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) { } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) {
acceleration = m_config.first_layer_acceleration_over_raft.value; acceleration = m_config.first_layer_acceleration_over_raft.value;
#endif #endif
} else if (m_config.get_abs_value_at("bridge_acceleration", cur_extruder_index()) > 0 && is_bridge(path.role())) { } else if (m_config.get_abs_value_at("bridge_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && is_bridge(path.role())) {
acceleration = m_config.get_abs_value_at("bridge_acceleration", cur_extruder_index()); acceleration = m_config.get_abs_value_at("bridge_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
} else if (m_config.get_abs_value_at("sparse_infill_acceleration", cur_extruder_index()) > 0 && (path.role() == erInternalInfill)) { } else if (m_config.get_abs_value_at("sparse_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && (path.role() == erInternalInfill)) {
acceleration = m_config.get_abs_value_at("sparse_infill_acceleration", cur_extruder_index()); acceleration = m_config.get_abs_value_at("sparse_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
} else if (m_config.get_abs_value_at("internal_solid_infill_acceleration", cur_extruder_index()) > 0 && (path.role() == erSolidInfill)) { } else if (m_config.get_abs_value_at("internal_solid_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && (path.role() == erSolidInfill)) {
acceleration = m_config.get_abs_value_at("internal_solid_infill_acceleration", cur_extruder_index()); acceleration = m_config.get_abs_value_at("internal_solid_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
} else if (NOZZLE_CONFIG(outer_wall_acceleration) > 0 && is_external_perimeter(path.role())) { } else if (NOZZLE_CONFIG(outer_wall_acceleration) > 0 && is_external_perimeter(path.role())) {
acceleration = NOZZLE_CONFIG(outer_wall_acceleration); acceleration = NOZZLE_CONFIG(outer_wall_acceleration);
} else if (NOZZLE_CONFIG(inner_wall_acceleration) > 0 && is_internal_perimeter(path.role())) { } else if (NOZZLE_CONFIG(inner_wall_acceleration) > 0 && is_internal_perimeter(path.role())) {
@@ -7222,7 +7256,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
} }
else if(path.role() == erInternalBridgeInfill) { else if(path.role() == erInternalBridgeInfill) {
speed = m_config.get_abs_value_at("internal_bridge_speed", cur_extruder_index()); speed = m_config.get_abs_value_at("internal_bridge_speed", get_nozzle_config_index(m_writer.filament()->id()));
} else if (path.role() == erOverhangPerimeter || path.role() == erSupportTransition || path.role() == erBridgeInfill) { } else if (path.role() == erOverhangPerimeter || path.role() == erSupportTransition || path.role() == erBridgeInfill) {
speed = NOZZLE_CONFIG(bridge_speed); speed = NOZZLE_CONFIG(bridge_speed);
} else if (path.role() == erInternalInfill) { } else if (path.role() == erInternalInfill) {
@@ -8098,8 +8132,8 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string
unsigned int acceleration_to_set = 0; unsigned int acceleration_to_set = 0;
if (this->on_first_layer()) { if (this->on_first_layer()) {
unsigned int initial_layer_travel_acceleration = m_config.get_abs_value_at("initial_layer_travel_acceleration", cur_extruder_index()); unsigned int initial_layer_travel_acceleration = m_config.get_abs_value_at("initial_layer_travel_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
double initial_layer_travel_jerk = m_config.get_abs_value_at("initial_layer_travel_jerk", cur_extruder_index()); double initial_layer_travel_jerk = m_config.get_abs_value_at("initial_layer_travel_jerk", get_nozzle_config_index(m_writer.filament()->id()));
if (NOZZLE_CONFIG(default_acceleration) > 0 && initial_layer_travel_acceleration > 0) { if (NOZZLE_CONFIG(default_acceleration) > 0 && initial_layer_travel_acceleration > 0) {
acceleration_to_set = (unsigned int) floor(initial_layer_travel_acceleration + 0.5); acceleration_to_set = (unsigned int) floor(initial_layer_travel_acceleration + 0.5);
@@ -8112,7 +8146,7 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string
if (NOZZLE_CONFIG(default_acceleration) > 0) { if (NOZZLE_CONFIG(default_acceleration) > 0) {
if (role == erOverhangPerimeter && is_short_travel) { if (role == erOverhangPerimeter && is_short_travel) {
const double bridge_acceleration = m_config.get_abs_value_at("bridge_acceleration", cur_extruder_index()); const double bridge_acceleration = m_config.get_abs_value_at("bridge_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
if (bridge_acceleration > 0) if (bridge_acceleration > 0)
acceleration_to_set = (unsigned int) floor(bridge_acceleration + 0.5); acceleration_to_set = (unsigned int) floor(bridge_acceleration + 0.5);
@@ -8437,6 +8471,87 @@ std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType li
return gcode; return gcode;
} }
void GCode::update_layer_related_config(int layer_id){
auto group_result = m_print->get_layered_nozzle_group_result();
// Orca: sequential (by-object) prints never publish a Print-level layered group result on
// this branch; the statically applied config maps stay authoritative there.
if(!group_result)
return;
auto extruder_map = group_result->get_extruder_map(false,layer_id);
auto volume_map = group_result->get_volume_map(layer_id);
auto nozzle_map = group_result->get_nozzle_map(layer_id);
m_config.filament_map.values = extruder_map;
m_config.filament_volume_map.values = volume_map;
m_config.filament_nozzle_map.values = nozzle_map;
m_writer.config.filament_map.values = extruder_map;
m_writer.config.filament_volume_map.values = volume_map;
m_writer.config.filament_nozzle_map.values = nozzle_map;
}
void GCode::update_placeholder_parser_with_variant_params()
{
if (!m_print)
return;
size_t num_filaments = m_config.filament_type.values.size();
if (num_filaments == 0)
return;
// Helpers: remap config arrays from variant index space to filament_id index space.
// After remapping, gcode templates can use param[filament_id] directly.
auto remap_floats_by_filament = [&](const auto &src) {
std::vector<double> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i));
return dst;
};
auto remap_ints_by_filament = [&](const auto &src) {
std::vector<int> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i));
return dst;
};
// --- filament_options_with_variant: gcode indexes by filament_id ---
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_max_volumetric_speed)));
this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature)));
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature_nc)));
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_cooling_before_tower)));
this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature)));
// first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
// --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed ---
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_floats_by_filament(m_config.retraction_distances_when_cut)));
// hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed
// --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser ---
this->placeholder_parser().set("filament_map", new ConfigOptionInts(m_config.filament_map));
// --- flush_volumetric_speeds / flush_temperatures: derived with fallback ---
{
// Fast purge mode uses filament_flush_temp_fast; Default is inert.
bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast;
auto flush_v_speed = remap_floats_by_filament(m_config.filament_flush_volumetric_speed);
auto filament_max_v = remap_floats_by_filament(m_config.filament_max_volumetric_speed);
auto flush_temps = remap_ints_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
: m_config.filament_flush_temp);
for (size_t i = 0; i < num_filaments; ++i) {
if (flush_v_speed[i] == 0)
flush_v_speed[i] = filament_max_v[i];
if (flush_temps[i] == 0)
flush_temps[i] = m_config.nozzle_temperature_range_high.get_at(i);
}
this->placeholder_parser().set("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
this->placeholder_parser().set("flush_temperatures", new ConfigOptionInts(flush_temps));
}
}
std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object, int toolchange_temp_override) std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object, int toolchange_temp_override)
{ {
int new_extruder_id = get_extruder_id(new_filament_id); int new_extruder_id = get_extruder_id(new_filament_id);
@@ -8446,8 +8561,11 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
// if we are running a single-extruder setup, just set the extruder and return nothing // if we are running a single-extruder setup, just set the extruder and return nothing
if (!m_writer.multiple_extruders) { if (!m_writer.multiple_extruders) {
this->placeholder_parser().set("current_extruder", new_filament_id); this->placeholder_parser().set("current_extruder", new_filament_id);
this->placeholder_parser().set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(new_filament_id)); {
this->placeholder_parser().set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(new_filament_id)); size_t fi = get_filament_config_index(new_filament_id);
this->placeholder_parser().set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(fi));
this->placeholder_parser().set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(fi));
}
std::string gcode; std::string gcode;
// Append the filament start G-code. // Append the filament start G-code.
@@ -8459,9 +8577,12 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position().z() - m_config.z_offset.value)); config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position().z() - m_config.z_offset.value));
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(new_filament_id))); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(new_filament_id)));
config.set_key_value("retraction_distance_when_cut", {
new ConfigOptionFloat(m_config.retraction_distances_when_cut.get_at(new_filament_id))); size_t fi = get_filament_config_index(new_filament_id);
config.set_key_value("long_retraction_when_cut", new ConfigOptionBool(m_config.long_retractions_when_cut.get_at(new_filament_id))); config.set_key_value("retraction_distance_when_cut",
new ConfigOptionFloat(m_config.retraction_distances_when_cut.get_at(fi)));
config.set_key_value("long_retraction_when_cut", new ConfigOptionBool(m_config.long_retractions_when_cut.get_at(fi)));
}
gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
check_add_eol(gcode); check_add_eol(gcode);
@@ -8664,24 +8785,29 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
dyn_config.set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts(first_layer_temps)); dyn_config.set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts(first_layer_temps));
} }
auto flush_v_speed = m_print->config().filament_flush_volumetric_speed.values; {
// Fast purge mode uses filament_flush_temp_fast; Default is inert. size_t num_filaments = m_print->config().filament_type.values.size();
auto flush_temps = (m_print->config().prime_volume_mode == PrimeVolumeMode::pvmFast ? m_print->config().filament_flush_temp_fast : m_print->config().filament_flush_temp).values; // Fast purge mode uses filament_flush_temp_fast; Default is inert.
auto filament_cooling_before_tower = m_print->config().filament_cooling_before_tower.values; bool use_fast_flush = m_print->config().prime_volume_mode == PrimeVolumeMode::pvmFast;
for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) { std::vector<double> flush_v_speed(num_filaments);
if (flush_v_speed[idx] == 0) std::vector<int> flush_temps(num_filaments);
flush_v_speed[idx] = m_print->config().filament_max_volumetric_speed.get_at(idx); std::vector<double> filament_cooling_before_tower(num_filaments);
for (size_t idx = 0; idx < num_filaments; ++idx) {
size_t fi = get_filament_config_index(idx);
flush_v_speed[idx] = m_print->config().filament_flush_volumetric_speed.get_at(fi);
if (flush_v_speed[idx] == 0)
flush_v_speed[idx] = m_print->config().filament_max_volumetric_speed.get_at(fi);
flush_temps[idx] = use_fast_flush ? m_print->config().filament_flush_temp_fast.get_at(fi)
: m_print->config().filament_flush_temp.get_at(fi);
if (flush_temps[idx] == 0)
flush_temps[idx] = m_print->config().nozzle_temperature_range_high.get_at(idx);
filament_cooling_before_tower[idx] = m_print->config().filament_cooling_before_tower.get_at(fi);
}
std::fill(filament_cooling_before_tower.begin(), filament_cooling_before_tower.end(), 0);
dyn_config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
dyn_config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps));
dyn_config.set_key_value("filament_cooling_before_tower", new ConfigOptionFloats(filament_cooling_before_tower));
} }
for (size_t idx = 0; idx < flush_temps.size(); ++idx) {
if (flush_temps[idx] == 0)
flush_temps[idx] = m_print->config().nozzle_temperature_range_high.get_at(idx);
}
if (filament_cooling_before_tower.size() < m_print->config().filament_type.values.size())
filament_cooling_before_tower.resize(m_print->config().filament_type.values.size(), m_print->config().filament_cooling_before_tower.get_at(0));
std::fill(filament_cooling_before_tower.begin(), filament_cooling_before_tower.end(), 0);
dyn_config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
dyn_config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps));
dyn_config.set_key_value("filament_cooling_before_tower", new ConfigOptionFloats(filament_cooling_before_tower));
dyn_config.set_key_value("flush_length", new ConfigOptionFloat(wipe_length)); dyn_config.set_key_value("flush_length", new ConfigOptionFloat(wipe_length));
int flush_count = std::min(g_max_flush_count, (int)std::round(wipe_volume / g_purge_volume_one_time)); int flush_count = std::min(g_max_flush_count, (int)std::round(wipe_volume / g_purge_volume_one_time));
@@ -8756,10 +8882,13 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
this->placeholder_parser().set("current_extruder", new_filament_id); this->placeholder_parser().set("current_extruder", new_filament_id);
this->placeholder_parser().set("current_hotend", this->placeholder_parser().set("current_hotend",
hotend_id_for_gcode_placeholder(m_config, group_result, (int) new_filament_id, new_extruder_id, m_layer_index)); hotend_id_for_gcode_placeholder(m_config, group_result, (int) new_filament_id, new_extruder_id, m_layer_index));
this->placeholder_parser().set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(new_filament_id)); {
this->placeholder_parser().set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(new_filament_id)); size_t fi = get_filament_config_index(new_filament_id);
this->placeholder_parser().set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(new_filament_id)); this->placeholder_parser().set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(fi));
this->placeholder_parser().set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(new_filament_id)); this->placeholder_parser().set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(fi));
this->placeholder_parser().set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(fi));
this->placeholder_parser().set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(fi));
}
// Append the filament start G-code. // Append the filament start G-code.

View File

@@ -395,6 +395,12 @@ private:
void check_placeholder_parser_failed(); void check_placeholder_parser_failed();
size_t cur_extruder_index() const; size_t cur_extruder_index() const;
size_t get_extruder_id(unsigned int filament_id) const; size_t get_extruder_id(unsigned int filament_id) const;
// Per-filament config-slot resolvers for the current layer (m_cur_layer_idx): the filament
// resolver keys filament-indexed arrays, the nozzle resolver keys (extruder x volume-type)
// slot arrays. Both degenerate to filament_id / extruder index on single-volume printers.
size_t get_filament_config_index(int filament_id) const;
size_t get_nozzle_config_index(int filament_id) const;
void update_placeholder_parser_with_variant_params();
void set_last_pos(const Point &pos) { m_last_pos = Point3(pos, 0); m_last_pos_defined = true; } void set_last_pos(const Point &pos) { m_last_pos = Point3(pos, 0); m_last_pos_defined = true; }
void set_last_pos(const Point3 &pos) { m_last_pos = pos; m_last_pos_defined = true; } void set_last_pos(const Point3 &pos) { m_last_pos = pos; m_last_pos_defined = true; }
@@ -701,12 +707,18 @@ private:
int m_start_gcode_filament = -1; int m_start_gcode_filament = -1;
std::string m_filament_instances_code; std::string m_filament_instances_code;
// Object layer id of the layer being generated; keys the per-filament config-slot
// resolvers. Distinct from m_layer_index (an export progress counter starting at -1).
size_t m_cur_layer_idx{0};
std::set<unsigned int> m_initial_layer_extruders; std::set<unsigned int> m_initial_layer_extruders;
std::vector<std::vector<unsigned int>> m_sorted_layer_filaments; std::vector<std::vector<unsigned int>> m_sorted_layer_filaments;
// BBS // BBS
int get_bed_temperature(const int extruder_id, const bool is_first_layer, const BedType bed_type) const; int get_bed_temperature(const int extruder_id, const bool is_first_layer, const BedType bed_type) const;
int get_highest_bed_temperature(const bool is_first_layer,const Print &print) const; int get_highest_bed_temperature(const bool is_first_layer,const Print &print) const;
void update_layer_related_config(int layer_id);
double calc_max_volumetric_speed(const double layer_height, const double line_width, const std::string co_str); double calc_max_volumetric_speed(const double layer_height, const double line_width, const std::string co_str);
std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1); std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1);
bool _needSAFC(const ExtrusionPath &path); bool _needSAFC(const ExtrusionPath &path);

View File

@@ -6940,10 +6940,12 @@ float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMod
} }
// Machine limit arrays hold 2 values: [0]=Normal, [1]=Stealth. Index by mode only. // Machine limit arrays hold 2 values: [0]=Normal, [1]=Stealth. Index by mode only.
// BambuStudio used extruder_id*2+mode to support per-nozzle limits, but OrcaSlicer // Orca: per-(extruder x volume-type) machine limits are deliberately not resolved here even
// never ported that system (filament_map_2 / get_config_idx_for_filament), so the // though the slicing side now materializes filament_map_2 and the per-filament volume map
// extruder_id offset was always wrong: uninitialized extruder (255) or extruder > 0 // (an extruder_id*2+mode style offset would need filament_map_2 in the processor-side config
// would overshoot the array and fall back to values.back() (stealth limits). // 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.
float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const
{ {
switch (axis) switch (axis)

View File

@@ -2093,15 +2093,8 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
std::vector<int> base_volume_map = print_config->filament_volume_map.values; std::vector<int> base_volume_map = print_config->filament_volume_map.values;
if (base_volume_map.size() != derived_maps.size()) if (base_volume_map.size() != derived_maps.size())
base_volume_map.assign(derived_maps.size(), (int)nvtStandard); base_volume_map.assign(derived_maps.size(), (int)nvtStandard);
// Orca: the engine's concrete per-filament volume assignment (get_volume_map()) is
// deliberately NOT merged into the write-back yet. The write-back re-expands the
// per-filament filament_* arrays from these maps, and those arrays are already
// consumed by filament id, so materializing a High Flow assignment here would alter
// motion g-code before the layer-aware g-code resolvers consume the assignment.
// Until those consumers land, the config keeps its own (GUI-injected or project)
// volume map, size-corrected above.
m_print->update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments), m_print->update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments),
base_volume_map, FilamentGroupUtils::update_used_filament_values(base_volume_map, grouping_result.get_volume_map(), used_filaments),
grouping_result.get_nozzle_map()); grouping_result.get_nozzle_map());
} }
std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; }); std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; });

View File

@@ -2535,12 +2535,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
std::vector<int> base_volume_map = m_config.filament_volume_map.values; std::vector<int> base_volume_map = m_config.filament_volume_map.values;
if (base_volume_map.size() != derived_maps.size()) if (base_volume_map.size() != derived_maps.size())
base_volume_map.assign(derived_maps.size(), (int)nvtStandard); base_volume_map.assign(derived_maps.size(), (int)nvtStandard);
// Orca: as on the non-sequential path, the engine's concrete volume
// assignment (get_volume_map()) is deliberately not merged in until the
// layer-aware g-code resolvers consume it; the config keeps its own
// (GUI-injected or project) volume map, size-corrected above.
update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments), update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments),
base_volume_map, FilamentGroupUtils::update_used_filament_values(base_volume_map, grouping_result.get_volume_map(), used_filaments),
grouping_result.get_nozzle_map()); grouping_result.get_nozzle_map());
} }
} }

View File

@@ -231,12 +231,12 @@ void BackgroundSlicingProcess::process_fff()
if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) < FilamentMapMode::fmmManual) { if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) < FilamentMapMode::fmmManual) {
std::vector<int> f_maps = m_fff_print->get_filament_maps(); std::vector<int> f_maps = m_fff_print->get_filament_maps();
m_current_plate->set_filament_maps(f_maps); m_current_plate->set_filament_maps(f_maps);
// Orca: the volume map is not read back to the plate yet. The engine's concrete // The engine's concrete per-filament volume assignment is merged into the print
// volume assignment is not written into the print config until the layer-aware // config by the ToolOrdering write-back; reading it back keeps the plate config the
// g-code resolvers consume it (see the ToolOrdering write-back), so the print-side // next apply overlays equal to the written-back state (no diff, no re-invalidation)
// value here is just the injected plate-or-default map echoed back; persisting it // and persists the auto result (always concrete Std/HF, never the Hybrid seed).
// would freeze the per-extruder defaults (including the transient Hybrid seed) into std::vector<int> f_volume_maps = m_fff_print->get_filament_volume_maps();
// the plate and the saved project. Restore together with the write-back merge. m_current_plate->set_filament_volume_maps(f_volume_maps);
} }
if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) != FilamentMapMode::fmmNozzleManual) { if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) != FilamentMapMode::fmmNozzleManual) {
// The engine-resolved nozzle map is read back for every non-nozzle-manual mode so the // The engine-resolved nozzle map is read back for every non-nozzle-manual mode so the

View File

@@ -4,6 +4,9 @@
#include "libslic3r/MultiNozzleUtils.hpp" #include "libslic3r/MultiNozzleUtils.hpp"
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
#include "libslic3r/GCode/ToolOrdering.hpp" #include "libslic3r/GCode/ToolOrdering.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include <algorithm> #include <algorithm>
#include <map> #include <map>
@@ -391,3 +394,69 @@ TEST_CASE("update_used_filament_values merges only used filaments", "[ToolOrderi
// No used filaments => the config baseline is returned untouched. // No used filaments => the config baseline is returned untouched.
REQUIRE(FilamentGroupUtils::update_used_filament_values(old_values, new_values, {}) == old_values); REQUIRE(FilamentGroupUtils::update_used_filament_values(old_values, new_values, {}) == old_values);
} }
TEST_CASE("Print config-index resolvers pick per-filament Hybrid slots", "[Print][H2C]")
{
// A 2-extruder printer whose second extruder is Hybrid (Standard + High Flow nozzles).
// The preset-style variant columns carry one column per (extruder x volume type); apply()
// expands them to the 3-slot layout [e1-Std, e2-Std, e2-HF].
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.option<ConfigOptionFloats>("nozzle_diameter", true)->values = {0.4, 0.4};
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"Standard#1", "Standard#1|High Flow#2"};
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtHybrid};
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow",
"Direct Drive Standard,Direct Drive High Flow"};
config.option<ConfigOptionInts>("print_extruder_id", true)->values = {1, 1, 2, 2};
config.option<ConfigOptionStrings>("print_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
"Direct Drive Standard", "Direct Drive High Flow"};
config.option<ConfigOptionFloats>("outer_wall_speed", true)->values = {30., 200., 50., 500.};
// Three filaments: 0 -> extruder 1 (Std), 1 -> extruder 2 (Std), 2 -> extruder 2 (High Flow).
config.option<ConfigOptionFloats>("filament_diameter", true)->values = {1.75, 1.75, 1.75};
config.option<ConfigOptionStrings>("filament_colour", true)->values = {"#FF0000", "#00FF00", "#0000FF"};
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 2, 2};
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {(int) nvtStandard, (int) nvtStandard, (int) nvtHighFlow};
Model model;
model.add_object("cube", "", make_cube(20, 20, 20))->add_instance();
Print print;
print.apply(model, config);
// Stub grouping result mirroring the maps above: one nozzle per (extruder, volume type).
std::vector<NozzleInfo> nozzle_list;
{
NozzleInfo n;
n.diameter = "0.4";
n.volume_type = nvtStandard; n.extruder_id = 0; n.group_id = 0; nozzle_list.push_back(n);
n.volume_type = nvtStandard; n.extruder_id = 1; n.group_id = 1; nozzle_list.push_back(n);
n.volume_type = nvtHighFlow; n.extruder_id = 1; n.group_id = 2; nozzle_list.push_back(n);
}
std::vector<unsigned int> used_filaments = {0, 1, 2};
auto group = LayeredNozzleGroupResult::create(std::vector<int>{0, 1, 2}, nozzle_list, used_filaments);
REQUIRE(group.has_value());
print.set_nozzle_group_result(std::make_shared<LayeredNozzleGroupResult>(*group));
// The write-back re-expands the config and refreshes the resolver caches.
print.update_filament_maps_to_config({1, 2, 2}, {(int) nvtStandard, (int) nvtStandard, (int) nvtHighFlow}, {0, 1, 2});
// The expansion must have produced the 3-slot layout the resolvers index into.
const auto &region_config = print.default_region_config();
REQUIRE(region_config.print_extruder_variant.values ==
std::vector<std::string>({"Direct Drive Standard", "Direct Drive Standard", "Direct Drive High Flow"}));
REQUIRE(region_config.print_extruder_id.values == std::vector<int>({1, 2, 2}));
SECTION("each filament resolves to its own (extruder x volume type) slot") {
REQUIRE(print.get_nozzle_config_index(0, 0) == 0); // extruder 1, Standard
REQUIRE(print.get_nozzle_config_index(1, 0) == 1); // extruder 2, Standard
REQUIRE(print.get_nozzle_config_index(2, 0) == 2); // extruder 2, High Flow
}
SECTION("without a group result the resolver falls back to the filament's extruder slot") {
print.set_nozzle_group_result(nullptr);
REQUIRE(print.get_nozzle_config_index(0, 0) == 0);
REQUIRE(print.get_nozzle_config_index(1, 0) == 1);
REQUIRE(print.get_nozzle_config_index(2, 0) == 1); // extruder slot, not the High Flow slot
}
}