The Unit Tests job sparse-checks-out only .github/scripts/tests, so the
baked-in absolute PROFILES_DIR was missing at runtime; the shipped-profile
test then read a non-existent JSON and null-dereferenced in opt_string.
Check out resources/ in the unit-test job, and guard the test helper to
skip when the profile is absent and require the key before dereferencing.
query_topology: indexing a body face-by-face was quadratic — face_by_index
re-walks the explorer and edge_by_index rebuilds the whole indexed map on every
single call. On a 15.7k-face / 25.6k-edge imported solid this blew past the
MCP 15 s main-thread timeout with the UI frozen throughout. GeometryEngine
gains faces_of()/edges_of(), which enumerate once in the very same order (ids
stay interchangeable with the _by_index accessors, so fillet/up_to_face targets
are unaffected). Measured on that body: 15 s timeout -> 0.46 s.
Feature ops: every commit-time m_doc.recompute() (fillet, cut, shell, boolean,
extrude, ...) now goes through recompute_guarded(), which runs the rebuild on a
worker thread. Live-preview/drag paths stay inline on purpose — yielding inside
a drag would be worse than the stall.
run_off_ui_thread(): the progress dialog is now created only after 300 ms, so a
fast op does not flash a dialog, while input stays blocked (wxWindowDisabler)
for the whole operation since the worker owns the document.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Viewport: GLCanvas3D::on_idle() cleared m_dirty even when
_refresh_if_shown_on_screen() rendered nothing because the canvas was not on
screen yet — a frame requested while the notebook was still showing a page got
silently swallowed and the viewport stayed blank until some later event dirtied
it again. _refresh_if_shown_on_screen() now reports whether it rendered, and
on_idle keeps the canvas dirty when it did not. Covers Design/Prepare/Preview.
Freeze: a big STEP (17.8 MB) spent ~40-50 s inside OCCT on the UI thread
(read_step_solids + recompute), so the window stopped repainting and the
compositor marked the app unresponsive. Both now run on a worker thread behind
an app-modal pulsing progress dialog: the UI keeps painting and the document
cannot be touched while the worker owns it. OCCT's Standard_Failure is not a
std::exception, so the worker catches it explicitly — an escaping exception
would terminate the process.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
request_repaint() only invalidates the canvas (Refresh()) on the hardware-GL
path and relies on a wxEVT_PAINT to follow. When the notebook re-shows the
Design page, that invalidation is issued mid-show and dropped: no paint event
arrives, the canvas never renders, and the pane stays blank until another tab
switch forces an expose. Software GL renders directly, so it never showed there.
force_repaint() defers past the show, then Refresh() + Update() for a
synchronous paint. Called from on_tab_shown().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
The new "Prime-tower visits..." test from #14685 (H2C/A2L support) throws
"Coordinate outside allowed range" at random on CI, on both Windows arm64 and
Linux x86_64. It's an uninitialized read of WipeTowerData::height.
#10780 (H2D/H2S) added a second wipe tower path, generate_new(), that fills in
depth, bbx, brim_width and rib_offset but not height. The older generate() sets
height, and clear() never did, so on the generate_new path it stays garbage.
first_layer_wipe_tower_corners() passes height to get_wipe_tower_cone_base() as
R = tan(cone_angle/2) * height. The stray bytes are usually zero, so R is zero
and the slice is fine, which is why it passes most runs on every platform. When
they aren't zero the cone radius runs past ClipperLib's limit and the slice
throws. Nothing selects for it, so it just flakes around.
#14685's test is the first to exercise this path, so that's when it started
showing up.
Initializing height in clear() fixes it, same as the m_origin fix in #13712. The
BBL generate_new path has no stabilization cone, so height = 0 is right.
Keep action identity and display metadata
constructor-set so registry keys cannot drift
from the objects they index.
Require sources to publish unique ownership while
the registry retains shared keepalive for runs.
Preserve opaque IDs and persisted state keys.
Lifecycle callbacks can be registered while
another thread dispatches them, risking races
and iterator invalidation.
Snapshot shared callback objects under a mutex,
then invoke them unlocked to preserve reentrancy
and mutable callback state.
CLI: guard 4 null derefs when loading a 3mf with no preset ids
At OrcaSlicer.cpp:1700-1704 the post-load block reads printer_settings_id,
print_settings_id, filament_settings_id, and nozzle_diameter from the
config that the 3mf carries. If the 3mf is a BBL/BBS-flavored 3mf but
was produced by a non-GUI writer (e.g. CLI --export-3mf without a
loaded preset) any of those keys can be absent, and config.option<T>(...)
returns nullptr — the ->value / ->values deref then SIGSEGVs.
Wrap each optional lookup in an if-let. printer_model, printer_extruder_variant,
and print_extruder_variant already pass create_if_missing=true and are safe.
Repro (BEFORE this patch):
orca-slicer --export-3mf out.3mf in.stl # produces preset-less 3mf
orca-slicer --info out.3mf # SIGSEGV at :1700
bt: __cxx11::basic_string::_M_assign
-> Slic3r::CLI::run @ OrcaSlicer.cpp:1700
AFTER: --info out.3mf returns exit 0 with the mesh summary.
The same failure mode affected --inspect-mesh, --inspect-paint, and
every other action that has to walk the loaded model's config; --slice
would only survive because it always injects a printer via
--load-settings.
For non-BBL host printers (Moonraker/Klipper, Qidi, Snapmaker, Creality), switch_printer_agent() only re-selected the machine when the agent type changed. Switching between two printer presets that use the same agent left the selected machine and the agent's cached device_info pointing at the previously active preset's host, so filament sync kept hitting the old printer.
Re-select the machine when the agent type is unchanged but the target host differs, so the selected machine and device_info always follow the active printer preset.
Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
Plater's pImpl (unique_ptr<priv> p) is destroyed before the wxWindow base
destructor runs DestroyChildren(), so child GLCanvas3D windows are torn down
after p is gone. GLCanvas3D::~GLCanvas3D() -> reset_volumes() then dereferences
the freed p through two paths:
- Selection::clear() -> plater()->canvas3D() -> p->get_current_canvas3D()
- _set_warning_notification() -> plater()->get_notification_manager()
Guard both with the existing wxGetApp().is_closing() flag; both are UI-only
side effects that are no-ops during shutdown, so normal-use behavior is
unchanged.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix text rendering issue when using MacType, with the original harmony os sans installed globally (OrcaSlicer/OrcaSlicer#14566)
The new fonts are generated using fonttools that only add gasp table to the original font without touching other parts