From 6c7bd484dbe2f5a8849216082fe8c7a8d3671986 Mon Sep 17 00:00:00 2001 From: Clifford Garwood Date: Fri, 1 May 2026 08:14:07 -0400 Subject: [PATCH] fix(imex): guard IMEX zone refresh against headless CLI slicing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check_outside` (placement check) calls `ensure_imex_zones()` to make sure zone geometry is current before deciding if an instance is in-bounds. In the GUI path that's fine, but the CLI / headless 3MF-load path also reaches this through `PartPlateList::load_from_3mf_structure -> reload_all_objects -> add_instance -> check_outside`, and CLI mode has no GUI_App initialized. `build_imex_cache_key` and `calc_imex_zones` both dereference `wxGetApp().preset_bundle` — without a GUI_App, `wxGetApp()` returns memory that segfaults on member access, killing the slicer with SIGSEGV before any G-code is produced. Latent since 461c69c83e (Apr 9), surfaced now that upstream's main carries the headless regression-test CI step (#13353) that exercises CLI slicing on every PR build. Fix: short-circuit `ensure_imex_zones()` when `m_plater` is null (already the GUI/CLI marker used by `calc_imex_ghosts`). Also tighten the existing `build_imex_cache_key` null check to consult `m_plater` first as defence in depth, so the function stays safe if reached from another headless caller. Verified locally against the upstream regression suite — klipper / p1s_multicolor / toolchanger_4_color all slice cleanly within the 20% baseline tolerance. Co-Authored-By: Claude Opus 4.7 --- src/slic3r/GUI/PartPlate.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index a81f7770eb..7a699ea2c1 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -954,7 +954,11 @@ void PartPlate::calc_imex_zones() // adjust the scale. std::string PartPlate::build_imex_cache_key() const { - if (!wxGetApp().preset_bundle) + // CLI / headless slice: no GUI_App is initialized, so `wxGetApp()` returns + // memory that segfaults on member access. `m_plater` is set only by the GUI + // construction path; treat its absence as "no IMEX state to compute" and + // return an empty key. Same guard pattern used by calc_imex_ghosts. + if (!m_plater || !wxGetApp().preset_bundle) return ""; const DynamicPrintConfig& printer_cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config; auto* is_imex_opt = printer_cfg.option("is_imex"); @@ -1002,6 +1006,10 @@ void PartPlate::refresh_imex_icon() // Called before any collision check AND before rendering so both paths share the same data. void PartPlate::ensure_imex_zones() { + // CLI / headless slice: no GUI_App, no plater, no IMEX state to track. Bail out + // before touching `wxGetApp()` (which segfaults without a GUI_App) — placement + // checks reach this from the headless 3MF-load path through `check_outside`. + if (!m_plater) return; std::string cache_key = build_imex_cache_key(); if (cache_key != m_imex_zones_mode_cache) { m_imex_zones_mode_cache = cache_key;