From 780b2f1ebe66a043692e3ebb0e503232c0370284 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sun, 12 Jul 2026 00:46:15 +0800 Subject: [PATCH] fix(engine): publish the nozzle group result for sequential prints The Print-level LayeredNozzleGroupResult had a single producer, the by-layer branch of ToolOrdering, which is gated to non-sequential prints. The by-object branch in Print::process computed a grouping only in auto map modes and never stored it, so a sequential slice exported with a null group result: the per-nozzle placeholder tables came up empty and any start g-code indexing nozzle_diameter_at_nozzle_id[] aborted with "Indexing an empty vector variable". A prior by-layer slice masked the bug by leaving its (never cleared) result on the Print. Now the by-object branch runs get_recommended_filament_maps in every static map mode (in manual modes the result mirrors the user's assignment, deviations throw as in by-layer) and publishes it print-wide. The config write-back stays gated to auto modes: in manual modes it would only re-store the pre-slice values. Regression test: a two-object by-object print must publish a non-null group result and resolve nozzle_diameter_at_nozzle_id[] in start g-code (both fail without the fix). Suites green (libslic3r 48929/162, fff_print 633/60); 18-fixture byte gate identical; the by-object repro project goes from the export error to valid g-code, determinism x2. --- src/libslic3r/GCode.cpp | 4 ++-- src/libslic3r/GCode/ToolOrdering.cpp | 3 ++- src/libslic3r/Print.cpp | 16 ++++++++++------ tests/fff_print/test_print.cpp | 22 ++++++++++++++++++++++ 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 2870be7875..cdd567457d 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -8473,8 +8473,8 @@ std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType li 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. + // Orca: defensive — with no published group result the statically applied config maps stay + // authoritative. if(!group_result) return; diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 3cdde4f32c..f9c0f31c3e 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -2116,7 +2116,8 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first { // Orca: the ToolOrdering member is stored unconditionally, but the Print-level store is gated // behind a not-sequential check. There is no is_sequential_print() here, so it is mirrored - // with the same predicate the branch above uses. + // with the same predicate the branch above uses. Sequential prints publish their result + // from the by-object branch in Print::process instead. const bool not_sequential = print_config->print_sequence != PrintSequence::ByObject || (m_print && m_print->objects().size() == 1); if (m_print != nullptr && not_sequential) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 117ae02428..54609f481b 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2511,15 +2511,19 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) auto filament_unprintable_volumes = this->get_filament_unprintable_flow(used_filaments); std::vectorfilament_maps = this->get_filament_maps(); auto map_mode = get_filament_map_mode(); - // get recommended filament map + // Grouping returns a nozzle-aware result; the 1-based extruder map for the by-object + // path is derived from it. It is computed in every static map mode (in manual modes it + // mirrors the user's assignment) and published print-wide: GCode's per-nozzle + // placeholder and config-index lookups read it via get_layered_nozzle_group_result(), + // and without it sequential exports on multi-nozzle printers see an empty nozzle table + // (e.g. nozzle_diameter_at_nozzle_id[]) and custom g-code fails to resolve. + auto grouping_result = ToolOrdering::get_recommended_filament_maps(all_filaments, this, map_mode, physical_unprintables, geometric_unprintables, filament_unprintable_volumes); + this->set_nozzle_group_result(std::make_shared(grouping_result)); // Orca: the sequential write-back stays gated to auto modes. In manual modes the // config maps already carry the user's assignment (the per-object ToolOrdering below // consumes them directly), so a write-back would only re-store the pre-slice values; // keeping the gate avoids churning the config on every sequential manual slice. if (map_mode < FilamentMapMode::fmmManual) { - // Grouping returns a nozzle-aware result; the 1-based extruder map - // for the by-object path is derived from it. - auto grouping_result = ToolOrdering::get_recommended_filament_maps(all_filaments, this, map_mode, physical_unprintables, geometric_unprintables, filament_unprintable_volumes); auto derived_maps = grouping_result.get_extruder_map(false); if (!derived_maps.empty()) { filament_maps = derived_maps; @@ -3532,8 +3536,8 @@ int Print::get_nozzle_config_index(int filament_id, int layer_id) int Print::get_config_index(int filament_id, int layer_id, const std::vector &variant_list, const std::vector& self_index_list, FilamentIndexMap &index_map) { auto group_result = get_layered_nozzle_group_result(); - // Orca: sequential (by-object) prints never publish a Print-level layered group result on this - // branch, so fall back to the static identity: one filament-variant column per filament. + // Orca: defensive — when no grouping producer has published a result yet, fall back to the + // static identity: one filament-variant column per filament. if (!group_result) return filament_id; auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id); diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index 8ba10a6ec9..9cb085f78b 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -418,3 +418,25 @@ TEST_CASE("Sequential printing follows model order", "[Print]") REQUIRE_THAT(first_object_peak_z, Catch::Matchers::WithinAbs(20.0, 0.3)); } + +// A sequential (by-object) print must publish the print-level nozzle group result just +// like a by-layer print, so custom g-code can index the per-nozzle placeholder tables +// (e.g. nozzle_diameter_at_nozzle_id[]) instead of failing on an empty vector. +TEST_CASE("Sequential printing publishes the nozzle group result", "[Print][MultiNozzle]") +{ + SECTION("process() publishes the result") { + Print print; + Model model; + place_two_cubes_apart(60.0, { { "print_sequence", "by object" } }, print, model); + print.process(); + REQUIRE(print.get_layered_nozzle_group_result() != nullptr); + } + + SECTION("start g-code can index the per-nozzle diameter table") { + const std::string gcode = slice_two_cubes_arranged({ + { "print_sequence", "by object" }, + { "machine_start_gcode", "{if nozzle_diameter_at_nozzle_id[0] > 0}; SEQ-ND-OK\n{endif}" }, + }); + CHECK(gcode.find("; SEQ-ND-OK") != std::string::npos); + } +}