diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index f6632c57ef..bb3a10ff48 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -13,11 +13,20 @@ Extruder::Extruder(unsigned int id, GCodeConfig *config, bool share_extruder) : { reset(); + m_config_index = int(m_id); // cache values that are going to be called often m_e_per_mm3 = this->filament_flow_ratio(); m_e_per_mm3 /= this->filament_crossection(); } +void Extruder::set_config_index(int idx) +{ + m_config_index = idx < 0 ? int(m_id) : idx; + // keep the cached flow term reading the same column as the getters + m_e_per_mm3 = this->filament_flow_ratio(); + m_e_per_mm3 /= this->filament_crossection(); +} + unsigned int Extruder::extruder_id() const { assert(m_config); @@ -162,28 +171,28 @@ double Extruder::filament_cost() const double Extruder::filament_flow_ratio() const { - return m_config->filament_flow_ratio.get_at(m_id); + return m_config->filament_flow_ratio.get_at(m_config_index); } // Return a "retract_before_wipe" percentage as a factor clamped to <0, 1> double Extruder::retract_before_wipe() const { - return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_id) * 0.01)); + return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_config_index) * 0.01)); } double Extruder::retraction_length() const { - return m_config->retraction_length.get_at(m_id); + return m_config->retraction_length.get_at(m_config_index); } double Extruder::retract_lift() const { - return m_config->z_hop.get_at(m_id); + return m_config->z_hop.get_at(m_config_index); } int Extruder::retract_speed() const { - return int(floor(m_config->retraction_speed.get_at(m_id)+0.5)); + return int(floor(m_config->retraction_speed.get_at(m_config_index)+0.5)); } bool Extruder::use_firmware_retraction() const @@ -193,13 +202,13 @@ bool Extruder::use_firmware_retraction() const int Extruder::deretract_speed() const { - int speed = int(floor(m_config->deretraction_speed.get_at(m_id)+0.5)); + int speed = int(floor(m_config->deretraction_speed.get_at(m_config_index)+0.5)); return (speed > 0) ? speed : this->retract_speed(); } double Extruder::retract_restart_extra() const { - return m_config->retract_restart_extra.get_at(m_id); + return m_config->retract_restart_extra.get_at(m_config_index); } double Extruder::retract_length_toolchange() const @@ -214,6 +223,8 @@ double Extruder::retract_restart_extra_toolchange() const double Extruder::travel_slope() const { + // Orca: deliberately keyed by the physical extruder, not the filament column — this read + // predates the per-variant merge and switching it would change existing multi-extruder output. return m_config->travel_slope.get_at(extruder_id()) * PI / 180; } diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp index a0b679add6..95bbae49ad 100644 --- a/src/libslic3r/Extruder.hpp +++ b/src/libslic3r/Extruder.hpp @@ -29,6 +29,13 @@ public: unsigned int id() const { return m_id; } + // Column of the per-variant filament/override arrays the getters read. Defaults to the + // filament id (one column per filament); the g-code generator refreshes it on layer changes + // and toolchanges when a per-layer nozzle grouping gives a filament several variant columns. + int config_index() const { return m_config_index; } + // idx < 0 resets to the filament id. Re-syncs the cached e_per_mm3 flow term. + void set_config_index(int idx); + unsigned int extruder_id() const; double extrude(double dE); double retract(double length, double restart_extra); @@ -86,6 +93,8 @@ private: GCodeConfig *m_config; // Print-wide global ID of this extruder. unsigned int m_id; + // Column into the per-variant filament/override arrays; equals m_id unless refreshed. + int m_config_index{0}; // Current state of the extruder axis, may be resetted if use_relative_e_distances. double m_E; // Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics. diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 858ccd670b..0eacc024c8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -419,12 +419,14 @@ static std::vector get_path_of_change_filament(const Print& print) int OozePrevention::_get_temp(const GCode &gcodegen) const { + // Resolve the filament's per-variant config column (equals its id on static prints). + size_t fi = gcodegen.get_filament_config_index((int)gcodegen.writer().filament()->id()); // First layer temperature should be used when on the first layer (obviously) and when // "other layers" is set to zero (which means it should not be used). return (gcodegen.layer() == nullptr || gcodegen.layer()->id() == 0 - || gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().filament()->id()) == 0) - ? gcodegen.config().nozzle_temperature_initial_layer.get_at(gcodegen.writer().filament()->id()) - : gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().filament()->id()); + || gcodegen.config().nozzle_temperature.get_at(fi) == 0) + ? gcodegen.config().nozzle_temperature_initial_layer.get_at(fi) + : gcodegen.config().nozzle_temperature.get_at(fi); } // Orca: @@ -567,7 +569,7 @@ static std::vector get_path_of_change_filament(const Print& print) amount of retraction. In other words, how far do we move in XY at wipe_speed for the time needed to consume retraction_length at retraction_speed? */ // BBS - double wipe_dist = scale_(gcodegen.config().wipe_distance.get_at(gcodegen.writer().filament()->id())); + double wipe_dist = scale_(gcodegen.config().wipe_distance.get_at(gcodegen.get_filament_config_index((int)gcodegen.writer().filament()->id()))); /* Take the stored wipe path and replace first point with the current actual position (they might be different, for example, in case of loop clipping). */ @@ -629,9 +631,11 @@ static std::vector get_path_of_change_filament(const Print& print) } // set volumetric speed of outer wall ,ignore per obejct & region ,just use default setting - static float get_outer_wall_volumetric_speed(const FullPrintConfig& config, const Print& print, int filament_id, int extruder_id) { + // filament_variant_idx selects the per-variant column of filament_max_volumetric_speed + // (equals filament_id unless a per-layer nozzle grouping expanded the filament arrays). + static float get_outer_wall_volumetric_speed(const FullPrintConfig& config, const Print& print, int filament_id, int filament_variant_idx, int extruder_id) { float outer_wall_volumetric_speed = 0; - float filament_max_volumetric_speed = config.filament_max_volumetric_speed.get_at(filament_id); + float filament_max_volumetric_speed = config.filament_max_volumetric_speed.get_at(filament_variant_idx); const double filament_diameter = config.filament_diameter.get_at(filament_id); float outer_wall_line_width = print.default_region_config().get_abs_value("outer_wall_line_width", filament_diameter); if (outer_wall_line_width == 0.0) { @@ -928,7 +932,7 @@ static std::vector get_path_of_change_filament(const Print& print) std::string toolchange_gcode_str; - ZHopType z_hope_type = ZHopType(gcodegen.config().z_hop_types.get_at(gcodegen.writer().filament()->id())); + ZHopType z_hope_type = ZHopType(gcodegen.config().z_hop_types.get_at(gcodegen.get_filament_config_index((int)gcodegen.writer().filament()->id()))); LiftType auto_lift_type = LiftType::NormalLift; if (z_hope_type == ZHopType::zhtAuto || z_hope_type == ZHopType::zhtSpiral || z_hope_type == ZHopType::zhtSlope) auto_lift_type = LiftType::SpiralLift; @@ -1001,25 +1005,32 @@ static std::vector get_path_of_change_filament(const Print& print) GCodeWriter& gcode_writer = gcodegen.m_writer; FullPrintConfig& full_config = gcodegen.m_config; + // Per-variant filament arrays can hold one column per variant a filament uses + // under a per-layer nozzle grouping; resolve the column instead of the raw id. + // The old filament resolves at the current layer, which is safe because the + // per-layer maps are gap-filled carry-forward. + size_t old_fi = (old_filament_id != -1) ? gcodegen.get_filament_config_index(old_filament_id) : 0; + size_t new_fi = gcodegen.get_filament_config_index(new_filament_id); + // set volumetric speed of outer wall ,ignore per obejct,just use default setting - float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(full_config, *gcodegen.m_print, new_filament_id, gcodegen.get_extruder_id(new_filament_id)); + float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(full_config, *gcodegen.m_print, new_filament_id, (int)new_fi, gcodegen.get_extruder_id(new_filament_id)); config.set_key_value("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); - float old_retract_length = (old_filament_id != -1) ? full_config.retraction_length.get_at(old_filament_id) : 0; - float new_retract_length = full_config.retraction_length.get_at(new_filament_id); + float old_retract_length = (old_filament_id != -1) ? full_config.retraction_length.get_at(old_fi) : 0; + float new_retract_length = full_config.retraction_length.get_at(new_fi); float old_retract_length_toolchange = (old_filament_id != -1) ? full_config.retract_length_toolchange.get_at(old_filament_id) : 0; float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_filament_id); - int old_filament_temp = (old_filament_id != -1) ? (gcodegen.on_first_layer()? full_config.nozzle_temperature_initial_layer.get_at(old_filament_id) : full_config.nozzle_temperature.get_at(old_filament_id)) : 210; - int new_filament_temp = gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : full_config.nozzle_temperature.get_at(new_filament_id); + int old_filament_temp = (old_filament_id != -1) ? (gcodegen.on_first_layer()? full_config.nozzle_temperature_initial_layer.get_at(old_fi) : full_config.nozzle_temperature.get_at(old_fi)) : 210; + int new_filament_temp = gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(new_fi) : full_config.nozzle_temperature.get_at(new_fi); Vec3d nozzle_pos = gcode_writer.get_position(); float purge_volume = tcr.purge_volume < EPSILON ? 0 : std::max(tcr.purge_volume, g_min_purge_volume); float filament_area = float((M_PI / 4.f) * pow(full_config.filament_diameter.get_at(new_filament_id), 2)); float purge_length = purge_volume / filament_area; - int old_filament_e_feedrate = (old_filament_id != -1) ? (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(old_filament_id) / filament_area) : 200; + int old_filament_e_feedrate = (old_filament_id != -1) ? (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(old_fi) / filament_area) : 200; old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; - int new_filament_e_feedrate = (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(new_filament_id) / filament_area); + int new_filament_e_feedrate = (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(new_fi) / filament_area); new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; float wipe_avoid_pos_x = 0.f; { @@ -1055,7 +1066,7 @@ static std::vector get_path_of_change_filament(const Print& print) config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); // Expose the old filament's nozzle-change retract length (filament_retract_length_nc; nil/-1 -> 0). config.set_key_value("filament_retract_length_nc", new ConfigOptionFloat( - (old_filament_id != -1) ? (float) full_config.filament_retract_length_nc.get_at(old_filament_id) : 0.f)); + (old_filament_id != -1) ? (float) full_config.filament_retract_length_nc.get_at(old_fi) : 0.f)); config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); // Current parked-retract length of the incoming filament's extruder. @@ -1069,12 +1080,19 @@ static std::vector get_path_of_change_filament(const Print& print) new_filament_temp = interface_temp; config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); if (full_config.enable_tower_interface_features && tcr.is_contact) { - auto temps = full_config.nozzle_temperature.values; + // Rebuild in filament order: the config arrays may carry per-variant columns, + // while these placeholder vectors are consumed indexed by filament id. + size_t num_filaments = full_config.filament_type.values.size(); + std::vector temps(num_filaments); + std::vector first_layer_temps(num_filaments); + for (size_t i = 0; i < num_filaments; ++i) { + size_t fi_i = gcodegen.get_filament_config_index((int)i); + temps[i] = full_config.nozzle_temperature.get_at(fi_i); + first_layer_temps[i] = full_config.nozzle_temperature_initial_layer.get_at(fi_i); + } if (new_filament_id >= 0 && new_filament_id < (int)temps.size()) temps[new_filament_id] = interface_temp; config.set_key_value("temperature", new ConfigOptionInts(temps)); - - auto first_layer_temps = full_config.nozzle_temperature_initial_layer.values; if (new_filament_id >= 0 && new_filament_id < (int)first_layer_temps.size()) first_layer_temps[new_filament_id] = interface_temp; config.set_key_value("first_layer_temperature", new ConfigOptionInts(first_layer_temps)); @@ -1415,10 +1433,12 @@ static std::vector get_path_of_change_filament(const Print& print) } if (toolchange_temp_override > 0) { - int base_temp = gcodegen.on_first_layer() ? gcodegen.config().nozzle_temperature_initial_layer.get_at(new_extruder_id) - : gcodegen.config().nozzle_temperature.get_at(new_extruder_id); + // new_extruder_id is the incoming filament id; resolve its per-variant config column. + size_t new_fi = gcodegen.get_filament_config_index(new_extruder_id); + int base_temp = gcodegen.on_first_layer() ? gcodegen.config().nozzle_temperature_initial_layer.get_at(new_fi) + : gcodegen.config().nozzle_temperature.get_at(new_fi); if (std::abs(tcr.print_z) < EPSILON) - base_temp = gcodegen.config().nozzle_temperature_initial_layer.get_at(new_extruder_id); + base_temp = gcodegen.config().nozzle_temperature_initial_layer.get_at(new_fi); const std::string t_token = " T" + std::to_string(new_extruder_id); std::string out; out.reserve(toolchange_gcode_str.size()); @@ -2344,11 +2364,13 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu m_processor.result().support_traditional_timelapse = m_support_traditional_timelapse; bool activate_long_retraction_when_cut = false; - for (const auto& filament : m_writer.extruders()) + for (const auto& filament : m_writer.extruders()) { + size_t fi = get_filament_config_index((int)filament.id()); activate_long_retraction_when_cut |= ( - m_config.long_retractions_when_cut.get_at(filament.id()) - && m_config.retraction_distances_when_cut.get_at(filament.id()) > 0 + m_config.long_retractions_when_cut.get_at(fi) + && m_config.retraction_distances_when_cut.get_at(fi) > 0 ); + } m_processor.result().long_retraction_when_cut = activate_long_retraction_when_cut; @@ -3330,7 +3352,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato this->placeholder_parser().set("during_print_exhaust_fan_speed_num", new ConfigOptionInts(during_print_exhaust_fan_speed_num)); //BBS: calculate the volumetric speed of outer wall. Ignore pre-object setting and multi-filament, and just use the default setting - float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, print, initial_non_support_extruder_id, get_extruder_id(initial_non_support_extruder_id)); + float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, print, initial_non_support_extruder_id, + (int) get_filament_config_index((int) initial_non_support_extruder_id), + get_extruder_id(initial_non_support_extruder_id)); this->placeholder_parser().set("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); auto first_layer_filaments = print.get_slice_used_filaments(true); @@ -3479,10 +3503,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Orca: when activate_air_filtration is set on any extruder, find and set the highest during_print_exhaust_fan_speed for (const auto &extruder : m_writer.extruders()) { - if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_during_print.get_at(extruder.id())) { + size_t fi = get_filament_config_index((int)extruder.id()); + if (m_config.activate_air_filtration.get_at(fi) && m_config.activate_air_filtration_during_print.get_at(fi)) { activate_air_filtration_during_print = true; during_print_exhaust_fan_speed = std::max(during_print_exhaust_fan_speed, - m_config.during_print_exhaust_fan_speed.get_at(extruder.id())); + m_config.during_print_exhaust_fan_speed.get_at(fi)); } } @@ -3619,7 +3644,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } else { file.write(this->retract()); } - file.write(m_writer.travel_to_z(m_max_layer_z + m_writer.config.z_hop.get_at(initial_extruder_id))); + file.write(m_writer.travel_to_z(m_max_layer_z + m_writer.config.z_hop.get_at(get_filament_config_index((int)initial_extruder_id)))); file.write(this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. @@ -3820,9 +3845,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Orca: when activate_air_filtration is set on any extruder, find and set the highest complete_print_exhaust_fan_speed for (const auto& extruder : m_writer.extruders()) { - if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_on_completion.get_at(extruder.id())) { + size_t fi = get_filament_config_index((int)extruder.id()); + if (m_config.activate_air_filtration.get_at(fi) && m_config.activate_air_filtration_on_completion.get_at(fi)) { activate_air_filtration_on_completion = true; - complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id())); + complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(fi)); } } @@ -4439,7 +4465,7 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr bool include_g10 = print.config().gcode_flavor == gcfRepRapFirmware; if (custom_gcode_sets_temperature(gcode, 104, 109, include_g10, temp_by_gcode)) { // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code. - int temp = print.config().nozzle_temperature_initial_layer.get_at(first_printing_extruder_id); + int temp = print.config().nozzle_temperature_initial_layer.get_at(get_filament_config_index((int)first_printing_extruder_id)); if (temp_by_gcode >= 0 && temp_by_gcode < 1000) temp = temp_by_gcode; m_writer.set_temperature(temp, wait, first_printing_extruder_id); @@ -4447,13 +4473,13 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr // Custom G-code does not set the extruder temperature. Do it now. if (print.config().single_extruder_multi_material.value) { // Set temperature of the first printing extruder only. - int temp = print.config().nozzle_temperature_initial_layer.get_at(first_printing_extruder_id); + int temp = print.config().nozzle_temperature_initial_layer.get_at(get_filament_config_index((int)first_printing_extruder_id)); if (temp > 0) file.write(m_writer.set_temperature(temp, wait, first_printing_extruder_id)); } else { // Set temperatures of all the printing extruders. for (unsigned int tool_id : print.extruders()) { - int temp = print.config().nozzle_temperature_initial_layer.get_at(tool_id); + int temp = print.config().nozzle_temperature_initial_layer.get_at(get_filament_config_index((int)tool_id)); if (m_ooze_prevention.enable && tool_id != first_printing_extruder_id) { if (print.config().idle_temperature.get_at(tool_id) == 0) temp += print.config().standby_temperature_delta.value; @@ -5237,6 +5263,10 @@ LayerResult GCode::process_layer( layer_ptr = support_layer; const Layer& layer = *layer_ptr; m_cur_layer_idx = layer.id(); + // A per-layer nozzle grouping can move the active filament to another variant column on a + // layer boundary without a toolchange, so re-resolve the writer's config column here. + if (Extruder *cur_filament = m_writer.filament()) + cur_filament->set_config_index((int)get_filament_config_index((int)cur_filament->id())); LayerResult result { {}, layer.id(), false, last_layer }; if (layer_tools.extruders.empty()) // Nothing to extrude. @@ -5501,8 +5531,9 @@ LayerResult GCode::process_layer( extruder.id() != m_writer.filament()->id()) // In single extruder multi material mode, set the temperature for the current extruder only. continue; - int temperature = print.config().nozzle_temperature.get_at(extruder.id()); - if (temperature > 0 && temperature != print.config().nozzle_temperature_initial_layer.get_at(extruder.id())) + size_t fi = get_filament_config_index((int)extruder.id()); + int temperature = print.config().nozzle_temperature.get_at(fi); + if (temperature > 0 && temperature != print.config().nozzle_temperature_initial_layer.get_at(fi)) gcode += m_writer.set_temperature(temperature, false, extruder.id()); } @@ -8612,6 +8643,8 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo } gcode += m_writer.toolchange(new_filament_id); + if (Extruder *fil = m_writer.filament()) + fil->set_config_index((int)get_filament_config_index((int)fil->id())); return gcode; } @@ -8657,12 +8690,15 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo gcode += m_ooze_prevention.pre_toolchange(*this); // BBS - float new_retract_length = m_config.retraction_length.get_at(new_filament_id); + // Per-variant filament arrays can hold one column per variant a filament uses under a + // per-layer nozzle grouping; resolve the column instead of indexing by the filament id. + size_t new_fi = get_filament_config_index((int)new_filament_id); + float new_retract_length = m_config.retraction_length.get_at(new_fi); float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(new_filament_id); - int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : m_config.nozzle_temperature.get_at(new_filament_id); + int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(new_fi) : m_config.nozzle_temperature.get_at(new_fi); // BBS: if print_z == 0 use first layer temperature if (abs(print_z) < EPSILON) - new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(new_filament_id); + new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(new_fi); if (toolchange_temp_override > 0) new_filament_temp = toolchange_temp_override; @@ -8685,9 +8721,12 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo old_filament_id = m_writer.filament() != nullptr ? m_writer.filament()->id() : m_start_gcode_filament; old_extruder_id = m_writer.filament() != nullptr ? m_writer.filament()->extruder_id() : get_extruder_id(m_start_gcode_filament); - old_retract_length = m_config.retraction_length.get_at(old_filament_id); + // Resolving the old filament at the current layer is safe: the per-layer nozzle maps are + // gap-filled carry-forward, so its current-layer column matches the nozzle it occupies. + size_t old_fi = get_filament_config_index(old_filament_id); + old_retract_length = m_config.retraction_length.get_at(old_fi); old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(old_filament_id); - old_filament_temp = this->on_first_layer()? m_config.nozzle_temperature_initial_layer.get_at(old_filament_id) : m_config.nozzle_temperature.get_at(old_filament_id); + old_filament_temp = this->on_first_layer()? m_config.nozzle_temperature_initial_layer.get_at(old_fi) : m_config.nozzle_temperature.get_at(old_fi); //During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length. float grab_purge_volume = m_config.grab_length.get_at(new_extruder_id) * 2.4; @@ -8707,7 +8746,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo } wipe_volume = std::max(0.f, wipe_volume-grab_purge_volume); - old_filament_e_feedrate = (int) (60.0 * m_config.filament_max_volumetric_speed.get_at(old_filament_id) / filament_area); + old_filament_e_feedrate = (int) (60.0 * m_config.filament_max_volumetric_speed.get_at(old_fi) / filament_area); old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; //BBS: must clean m_start_gcode_filament m_start_gcode_filament = -1; @@ -8719,11 +8758,11 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo old_filament_e_feedrate = 200; } float wipe_length = wipe_volume / filament_area; - int new_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(new_filament_id) / filament_area); + int new_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(new_fi) / filament_area); new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; // set volumetric speed of outer wall ,ignore per obejct,just use default setting - float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, *m_print, new_filament_id, get_extruder_id(new_filament_id)); + float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, *m_print, new_filament_id, (int)new_fi, get_extruder_id(new_filament_id)); float wipe_avoid_pos_x = 110.f; // Logical nozzle grouping (null on paths that don't populate it) + null-safe nozzle ids. auto group_result = m_print->get_layered_nozzle_group_result(); @@ -8747,7 +8786,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo dyn_config.set_key_value("nozzle_volume_types", new ConfigOptionStrings(get_nozzle_volume_types_by_nozzle_id(group_result.get()))); // Old filament's nozzle-change retract length (filament_retract_length_nc; nil/-1 -> 0). dyn_config.set_key_value("filament_retract_length_nc", new ConfigOptionFloat( - (old_filament_id != -1) ? (float) m_config.filament_retract_length_nc.get_at(old_filament_id) : 0.f)); + (old_filament_id != -1) ? (float) m_config.filament_retract_length_nc.get_at(get_filament_config_index(old_filament_id)) : 0.f)); // Current parked-retract length of the incoming filament's extruder. dyn_config.set_key_value("new_extruder_retracted_length", new ConfigOptionFloat(m_writer.get_extruder_retracted_length((int) new_filament_id))); @@ -8789,13 +8828,20 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo dyn_config.set_key_value("filament_tower_interface_print_temp", new ConfigOptionInt(interface_temp)); } if (toolchange_temp_override > 0) { - auto temps = m_config.nozzle_temperature.values; + // Rebuild in filament order: the config arrays may carry per-variant columns, while + // these placeholder vectors are consumed indexed by filament id. + size_t num_filaments = m_print->config().filament_type.values.size(); + std::vector temps(num_filaments); + std::vector first_layer_temps(num_filaments); + for (size_t i = 0; i < num_filaments; ++i) { + size_t fi_i = get_filament_config_index((int)i); + temps[i] = m_config.nozzle_temperature.get_at(fi_i); + first_layer_temps[i] = m_config.nozzle_temperature_initial_layer.get_at(fi_i); + } if (new_filament_id < temps.size()) temps[new_filament_id] = toolchange_temp_override; dyn_config.set_key_value("temperature", new ConfigOptionInts(temps)); dyn_config.set_key_value("nozzle_temperature", new ConfigOptionInts(temps)); - - auto first_layer_temps = m_config.nozzle_temperature_initial_layer.values; if (new_filament_id < first_layer_temps.size()) first_layer_temps[new_filament_id] = toolchange_temp_override; dyn_config.set_key_value("first_layer_temperature", new ConfigOptionInts(first_layer_temps)); @@ -8882,6 +8928,8 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo //BBS: don't add T[next extruder] if there is no T cmd on filament change //We inform the writer about what is happening, but we may not use the resulting gcode. std::string toolchange_command = m_writer.toolchange(new_filament_id); + if (Extruder *fil = m_writer.filament()) + fil->set_config_index((int)get_filament_config_index((int)fil->id())); if (!custom_gcode_changes_tool(toolchange_gcode_parsed, m_writer.toolchange_prefix(), new_filament_id)) gcode += toolchange_command; else { @@ -8890,8 +8938,9 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM) if (m_config.single_extruder_multi_material && !m_config.enable_prime_tower) { - int temp = (m_layer_index <= 0 ? m_config.nozzle_temperature_initial_layer.get_at(new_filament_id) : - m_config.nozzle_temperature.get_at(new_filament_id)); + size_t new_fi = get_filament_config_index(new_filament_id); + int temp = (m_layer_index <= 0 ? m_config.nozzle_temperature_initial_layer.get_at(new_fi) : + m_config.nozzle_temperature.get_at(new_fi)); gcode += m_writer.set_temperature(temp, false); } @@ -8918,13 +8967,20 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo 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))); if (toolchange_temp_override > 0) { - auto temps = m_config.nozzle_temperature.values; + // Rebuild in filament order: the config arrays may carry per-variant columns, while + // these placeholder vectors are consumed indexed by filament id. + size_t num_filaments = m_print->config().filament_type.values.size(); + std::vector temps(num_filaments); + std::vector first_layer_temps(num_filaments); + for (size_t i = 0; i < num_filaments; ++i) { + size_t fi_i = get_filament_config_index((int)i); + temps[i] = m_config.nozzle_temperature.get_at(fi_i); + first_layer_temps[i] = m_config.nozzle_temperature_initial_layer.get_at(fi_i); + } if (new_filament_id < temps.size()) temps[new_filament_id] = toolchange_temp_override; config.set_key_value("temperature", new ConfigOptionInts(temps)); config.set_key_value("nozzle_temperature", new ConfigOptionInts(temps)); - - auto first_layer_temps = m_config.nozzle_temperature_initial_layer.values; if (new_filament_id < first_layer_temps.size()) first_layer_temps[new_filament_id] = toolchange_temp_override; config.set_key_value("first_layer_temperature", new ConfigOptionInts(first_layer_temps)); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 0129e61fac..7b862f2cde 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -264,6 +264,12 @@ public: // append full config to the given string static void append_full_config(const Print& print, std::string& str); + // 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; + // Object and support extrusions of the same PrintObject at the same print_z. // public, so that it could be accessed by free helper functions from GCode.cpp struct LayerToPrint @@ -395,11 +401,6 @@ private: void check_placeholder_parser_failed(); size_t cur_extruder_index() 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; } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7358e0852b..262be94e59 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2969,6 +2969,8 @@ void GCodeProcessor::apply_config(const PrintConfig& config) for (size_t i = 0; i < filament_count; ++ i) { m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(filament_map[i] - 1).cast().eval(), 0.f); m_extruder_colors[i] = static_cast(i); + // Orca: pre-heat bookkeeping reads the filament's first per-variant column by design; + // it feeds estimation-side heat-up modelling only, never the emitted commands. m_filament_nozzle_temp_first_layer[i] = static_cast(config.nozzle_temperature_initial_layer.get_at(i)); m_filament_nozzle_temp[i] = static_cast(config.nozzle_temperature.get_at(i)); if (m_filament_nozzle_temp[i] == 0) { diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 38b7b56176..58df527da5 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1559,6 +1559,10 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector m_filpar.push_back(FilamentParameters()); + // Orca: one row per filament, indexed by the raw filament id. Under a per-layer nozzle + // grouping the per-variant arrays may hold several columns per filament; the tower has no + // layer dimension here, so it keeps the filament's first column (tower x per-layer + // grouping is a documented follow-up). m_filpar[idx].material = config.filament_type.get_at(idx); m_filpar[idx].is_soluble = config.wipe_tower_filament == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_filament - 1)); // BBS diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp index 7950034bb1..34b385d4be 100644 --- a/src/libslic3r/GCode/WipeTower2.cpp +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -1333,6 +1333,10 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config) //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector m_filpar.push_back(FilamentParameters()); + // Orca: one row per filament, indexed by the raw filament id. Under a per-layer nozzle + // grouping the per-variant arrays may hold several columns per filament; the tower has no + // layer dimension here, so it keeps the filament's first column (tower x per-layer + // grouping is a documented follow-up). m_filpar[idx].material = config.filament_type.get_at(idx); if (m_wipe_tower_filament > 0) m_filpar[idx].is_soluble = (idx != size_t(m_wipe_tower_filament - 1)); diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index adb104a25b..df8cfb33f1 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -404,3 +404,55 @@ TEST_CASE("EXTRUDER_LIMIT per-extruder clamping and max fallback", "[GCodeWriter } } } + +SCENARIO("Extruder reads the injected config column", "[GCodeWriter][H2C]") { + GIVEN("A writer whose per-variant arrays hold three columns for two filaments") { + GCodeWriter writer; + // Column layout after a migrating regroup: filament 0 -> column 0, filament 1 -> + // columns 1 (its first variant) and 2 (its second variant). + writer.config.retraction_length.values = {0.8, 0.5, 1.2}; + writer.config.z_hop.values = {0.4, 0.6, 0.9}; + writer.config.retraction_speed.values = {30., 40., 50.}; + writer.config.filament_flow_ratio.values = {0.98, 1.0, 1.02}; + // Filament-indexed arrays keep one entry per filament. + writer.config.filament_diameter.values = {1.75, 1.75}; + writer.set_extruders({0, 1}); + writer.toolchange(1); + Extruder *fil = writer.filament(); + REQUIRE(fil != nullptr); + REQUIRE(fil->id() == 1); + const double crossection = 1.75 * 1.75 * 0.25 * PI; + + WHEN("no column has been injected") { + THEN("the getters read the filament id's column") { + REQUIRE(fil->config_index() == 1); + REQUIRE_THAT(fil->retraction_length(), Catch::Matchers::WithinAbs(0.5, 1e-9)); + REQUIRE_THAT(fil->retract_lift(), Catch::Matchers::WithinAbs(0.6, 1e-9)); + REQUIRE(fil->retract_speed() == 40); + REQUIRE_THAT(fil->e_per_mm3(), Catch::Matchers::WithinRel(1.0 / crossection, 1e-9)); + } + } + WHEN("the second variant column is injected") { + fil->set_config_index(2); + THEN("the getters follow the column and the flow cache is rescaled") { + REQUIRE(fil->config_index() == 2); + REQUIRE_THAT(fil->retraction_length(), Catch::Matchers::WithinAbs(1.2, 1e-9)); + REQUIRE_THAT(fil->retract_lift(), Catch::Matchers::WithinAbs(0.9, 1e-9)); + REQUIRE(fil->retract_speed() == 50); + REQUIRE_THAT(fil->e_per_mm3(), Catch::Matchers::WithinRel(1.02 / crossection, 1e-9)); + } + THEN("filament-indexed reads keep using the filament id") { + REQUIRE_THAT(fil->filament_diameter(), Catch::Matchers::WithinAbs(1.75, 1e-9)); + } + } + WHEN("a negative index is injected") { + fil->set_config_index(2); + fil->set_config_index(-1); + THEN("resolution resets to the filament id") { + REQUIRE(fil->config_index() == 1); + REQUIRE_THAT(fil->retraction_length(), Catch::Matchers::WithinAbs(0.5, 1e-9)); + REQUIRE_THAT(fil->e_per_mm3(), Catch::Matchers::WithinRel(1.0 / crossection, 1e-9)); + } + } + } +} diff --git a/tests/libslic3r/test_toolordering_nozzle_group.cpp b/tests/libslic3r/test_toolordering_nozzle_group.cpp index 0a0befde3f..ae6aac4dbe 100644 --- a/tests/libslic3r/test_toolordering_nozzle_group.cpp +++ b/tests/libslic3r/test_toolordering_nozzle_group.cpp @@ -827,6 +827,78 @@ TEST_CASE("Selector write-back expands migrating filaments and survives re-apply REQUIRE(print.config().retraction_length.values == merged_retract); } +TEST_CASE("Filaments ordered after a migrator shift columns and the resolver tracks them", "[Print][H2C][Dynamic]") +{ + // When a mid-list filament expands to two columns, every later filament's values move one + // column to the right — a raw get_at(filament_id) lands in the migrator's second column. + // The layer-aware resolver must return the shifted column for both the expanded filament + // arrays and the merged machine overrides. + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.option("nozzle_diameter", true)->values = {0.4, 0.4}; + config.option("extruder_nozzle_stats", true)->values = {"Standard#1", "Standard#1|High Flow#2"}; + config.option("extruder_type", true)->values = {etDirectDrive, etDirectDrive}; + config.option("nozzle_volume_type", true)->values = {nvtStandard, nvtHybrid}; + config.option("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow", + "Direct Drive Standard,Direct Drive High Flow"}; + config.option("print_extruder_id", true)->values = {1, 1, 2, 2}; + config.option("print_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow", + "Direct Drive Standard", "Direct Drive High Flow"}; + // Three filaments: 0 -> extruder 1 (Std), 1 -> extruder 2, migrating Standard -> High Flow + // between layers, 2 -> extruder 2 (Std) — ordered AFTER the migrator. + config.option("filament_type", true)->values = {"PLA", "PLA", "PLA"}; + config.option("filament_diameter", true)->values = {1.75, 1.75, 1.75}; + config.option("filament_colour", true)->values = {"#FF0000", "#00FF00", "#0000FF"}; + config.option("filament_map", true)->values = {1, 2, 2}; + config.option("filament_volume_map", true)->values = {(int) nvtStandard, (int) nvtStandard, (int) nvtStandard}; + config.option("filament_self_index", true)->values = {1, 1, 2, 2, 3, 3}; + config.option("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow", + "Direct Drive Standard", "Direct Drive High Flow", + "Direct Drive Standard", "Direct Drive High Flow"}; + config.option("nozzle_temperature", true)->values = {200, 210, 220, 230, 240, 250}; + config.option("filament_retraction_length", true)->values = {0.5, 0.5, 0.7, 0.9, 1.4, 1.4}; + config.option("retraction_length", true)->values = {0.8, 0.9, 1.0, 1.1}; + + Model model; + model.add_object("cube", "", make_cube(20, 20, 20))->add_instance(); + + Print print; + print.apply(model, config); + + std::vector 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); + } + // Filament 1: Standard nozzle on layer 0, High Flow nozzle on layer 1; filament 2 stays Standard. + std::vector> layer_maps = {{0, 1, 1}, {0, 2, 1}}; + std::vector> layer_seqs = {{0, 1, 2}, {0, 1, 2}}; + auto group = LayeredNozzleGroupResult::create(layer_maps, nozzle_list, {0, 1, 2}, layer_seqs); + REQUIRE(group.has_value()); + REQUIRE(group->is_support_dynamic_nozzle_map()); + print.set_nozzle_group_result(std::make_shared(*group)); + + print.update_to_config_by_nozzle_group_result(*group); + + // Filament 1 holds columns 1-2; filament 2's values shift to column 3. + REQUIRE(print.config().filament_self_index.values == std::vector{1, 2, 2, 3}); + REQUIRE(print.config().nozzle_temperature.values == std::vector{200, 220, 230, 240}); + // The migrator resolves per layer to its two columns. + REQUIRE(print.get_filament_config_indx(1, 0) == 1); + REQUIRE(print.get_filament_config_indx(1, 1) == 2); + // The filament after it no longer lives at its raw index on any layer. + REQUIRE(print.get_filament_config_indx(2, 0) == 3); + REQUIRE(print.get_filament_config_indx(2, 1) == 3); + // Merged machine override: filament 2's value sits in the shifted column, while a raw + // get_at(2) would read the migrator's High Flow column. + const std::vector merged = print.config().retraction_length.values; + REQUIRE(merged.size() == 4); + REQUIRE_THAT(merged[3], Catch::Matchers::WithinAbs(1.4, 1e-9)); + REQUIRE_THAT(merged[2], Catch::Matchers::WithinAbs(0.9, 1e-9)); +} + TEST_CASE("Selector slicing keeps the result valid across re-apply", "[Print][H2C][Dynamic]") { // The dynamic counterpart of the static re-apply test above: a full process() run through