From 35ffe49b17eb4eea78c334a48893a34d3882693c Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 13 Jul 2026 18:13:15 +0800 Subject: [PATCH] Fix sending print jobs to Bambu H2C printer --- src/libslic3r/GCode/GCodeProcessor.cpp | 76 ++++++++++++++++++++++++++ src/libslic3r/GCode/GCodeProcessor.hpp | 5 ++ src/libslic3r/Print.cpp | 8 ++- 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index e76aa6a276..b5d12fe50f 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3452,6 +3452,13 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) const ConfigOptionFloat* z_offset = config.option("z_offset"); if (z_offset != nullptr) m_z_offset = z_offset->value; + + // Reprocessing an existing g-code (from-previous reload / imported g-code) reaches apply_config + // through this DynamicPrintConfig overload; rebuild the per-filament nozzle grouping onto the + // result so the multi-nozzle device GUI can map filaments to physical nozzles. The normal + // streaming export uses the PrintConfig overload and hands the live grouping over separately, so + // it is intentionally not touched here. + ensure_nozzle_group_result(static_cast(m_result.filaments_count)); } void GCodeProcessor::enable_stealth_time_estimator(bool enabled) @@ -6575,6 +6582,75 @@ void GCodeProcessor::init_filament_maps_and_nozzle_type_when_import_only_gcode() } } +// Surface a per-filament nozzle grouping onto m_result when reprocessing an already-generated g-code +// (from-previous reload / imported g-code) — process_file does not otherwise rebuild it, so the +// multi-nozzle device GUI would find nozzle_group_result == NULL and the rack print-dispatch mapping +// request fails with code -1. Only invoked from the DynamicPrintConfig apply_config (the reprocess / +// import path); the normal streaming export keeps handing the live grouping over separately. +void GCodeProcessor::ensure_nozzle_group_result(int min_filament_count) +{ + if (m_nozzle_group_result) { + // A grouping was already seeded from the reloaded result (initialize_from_context). Publish it + // onto m_result so extract_result() carries it. (The reference relies on the streaming-export + // handover for this and returns early here; the reprocess path has no such handover.) + m_result.nozzle_group_result = m_nozzle_group_result; + return; + } + + int filament_count = std::max(1, min_filament_count); + filament_count = std::max(filament_count, static_cast(m_filament_maps.size())); + + std::vector filament_map = m_filament_maps; + if (filament_map.empty()) { + filament_map.assign(filament_count, 0); + } else if (static_cast(filament_map.size()) < filament_count) { + filament_map.resize(filament_count, filament_map.front()); + } + + int min_value = *std::min_element(filament_map.begin(), filament_map.end()); + if (min_value >= 1) { + for (int &value : filament_map) { + value -= 1; + } + } + + for (int &value : filament_map) { + value = std::max(0, value); + } + + int max_extruder_id = *std::max_element(filament_map.begin(), filament_map.end()); + max_extruder_id = std::max(0, max_extruder_id); + + std::string nozzle_diameter = format_diameter_to_str(DEFAULT_TOOLPATH_WIDTH); + std::vector nozzle_list; + nozzle_list.reserve(static_cast(max_extruder_id + 1)); + for (int extruder_id = 0; extruder_id <= max_extruder_id; ++extruder_id) { + MultiNozzleUtils::NozzleInfo info; + info.diameter = nozzle_diameter; + info.volume_type = NozzleVolumeType::nvtStandard; + info.extruder_id = extruder_id; + info.group_id = extruder_id; + nozzle_list.emplace_back(std::move(info)); + } + + std::vector filament_nozzle_map(filament_count, 0); + for (int i = 0; i < filament_count; ++i) { + filament_nozzle_map[i] = filament_map[i]; + } + + std::vector used_filaments; + used_filaments.reserve(filament_count); + for (int i = 0; i < filament_count; ++i) { + used_filaments.push_back(static_cast(i)); + } + + auto result = MultiNozzleUtils::LayeredNozzleGroupResult::create(filament_nozzle_map, nozzle_list, used_filaments); + if (result) { + m_nozzle_group_result = std::make_shared(*result); + m_result.nozzle_group_result = m_nozzle_group_result; + } +} + bool GCodeProcessor::use_multi_nozzle_change_time_model() const { // Multi-nozzle context = a printer that can incur nozzle-change events the single-arg model diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index f6d434960a..8a66cdc9e4 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -1197,6 +1197,11 @@ class Print; public: GCodeProcessor(); void init_filament_maps_and_nozzle_type_when_import_only_gcode(); + // Reprocessing an already-generated g-code (from-previous / imported g-code) does not rebuild + // the per-filament nozzle grouping the multi-nozzle device GUI needs. Surface it onto the + // result: keep an already-seeded grouping (from initialize_from_context), otherwise synthesize + // a default one from the filament map so the result is never left without it. + void ensure_nozzle_group_result(int min_filament_count); // check whether the gcode path meets the filament_map grouping requirements bool check_multi_extruder_gcode_valid(const int extruder_size, const Pointfs plate_printable_area, diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 9416b43c2f..a35bd2c45c 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -4260,13 +4260,19 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces const Vec3d origin = this->get_plate_origin(); processor.set_xy_offset(origin(0), origin(1)); // Reloaded sliced projects re-estimate with the same nozzle-grouping slot context as the - // original export; harmless when the result carries none (slot 0). + // original export; process_file re-derives the device-side nozzle grouping onto the result + // (via ensure_nozzle_group_result), so the multi-nozzle send/monitor mapping survives here. if (result != nullptr && result->nozzle_group_result) processor.initialize_from_context(result->nozzle_group_result); //processor.enable_producers(true); processor.process_file(file); + // filament seq is loaded from file, processor result will override the value + auto filament_seq_loaded = result->filament_change_sequence; + auto nozzle_seq_loaded = result->nozzle_change_sequence; *result = std::move(processor.extract_result()); + result->filament_change_sequence = filament_seq_loaded; + result->nozzle_change_sequence = nozzle_seq_loaded; } catch (std::exception & /* ex */) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str(); throw Slic3r::RuntimeError(