Commit Graph
826 Commits
Author SHA1 Message Date
Kris Austin 66b300987b feat: faster startup by lazy-loading main window panels on idle or first use (#15811) 2026-09-23 19:11:10 -03:00
ExPikaPaka 4504f315ee Include what the new code uses
The Windows build stopped on test_kdtree.cpp: it calls std::iota without
including <numeric>, which libstdc++ happens to pull in anyway. Added
there, and the same for <limits> and <algorithm>/<cmath> where the
recent changes rely on them being included by something else.
2026-09-23 16:16:46 +02:00
ExPikaPaka 944f01c68a Merge branch 'feature/texture_displacement' of https://github.com/OrcaSlicer/OrcaSlicer into feature/texture_displacement 2026-09-23 14:08:47 +02:00
ExPikaPaka 9327e7770b Rename the fast preview to the shaded preview
Its shader files, the name they are registered under, its members and
the panel text now match what it does: shading only, no geometry.
2026-09-23 09:51:08 +02:00
ExPikaPaka 6e88ad7f52 Visit seam candidates as the search finds them
Collecting every candidate within the radius into a vector cost more
than the search itself. Same order, so the seams are unchanged;
align_seam_points ~19.6 s at 0.1 mm / 2000k, was ~21.
2026-09-23 02:04:49 +02:00
Ian Chua 01fcd71aa3 Merge branch 'main' into feat/plugin-lifecycle-evts 2026-09-23 03:16:07 +08:00
peachismomo 0fb8f3d487 fix: synchronize lifecycle hook shutdown with active dispatches 2026-09-23 02:14:56 +08:00
ExPikaPaka 5baefd8a4e Tile the booleans on layers of many pieces
ClipperLib slows down with the number of edges on a scan line, and a
layer cut through a fine relief has tens of thousands of pieces.
detect_surfaces_type ~50 s at 0.1 mm / 2000k, was ~145.
2026-09-22 18:06:54 +02:00
Ian Chua 323cd3afe1 Keep Python Printer Agent Exceptions Out of the Host (#15819)
# Description
<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
  > * What issue does this PR address or fix?
  > * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->

A Python printer agent plugin could take the host down, and one of its
operations could never report its result. This PR fixes both in
`PrinterAgentPluginCapabilityTrampoline.hpp`.

## Changes

### A faulty printer agent no longer throws into the GUI

`IPrinterAgent` reports failure through return values, and none of its
callers catch. A Python `raise`, a missing override or a wrongly typed
return from a printer agent plugin therefore escaped the trampoline as a
C++ exception.

Every trampoline operation now catches, logs `Printer agent plugin
'<key>': <operation> failed: <error>`, and answers with what
`NetworkAgent` returns when no printer agent is set. `BBLPrinterAgent`
returns the same values when the Bambu plug-in is unavailable:

- `-1` for every `int` status code
- `false` for `start_discovery` and `fetch_filament_info`
- `""` for `get_user_selected_machine`
- an empty `AgentInfo` for `get_agent_info` (registration already
rejects an empty agent ID)
- `FilamentSyncMode::none` for `get_filament_sync_mode`

`ORCA_PY_AGENT_OVERRIDE(ret, name, ...)` derives the fallback from the
return type through `printer_agent_failure<ret>()`, so the call sites
carry no fallback values of their own.

An exception is the safety net for plugin bugs, not an error channel. A
plugin reports an expected failure by returning a code, as the Bambu
plug-in does. A raise is logged as a failure and collapses to the
generic `-1`, so the GUI shows the generic message instead of the
specific one (`-18` cancelled, `-4020` FTP upload failed, …).

### `bind_detect` results now reach the host

`detect` is an out-parameter (`detectResult&`). pybind11 casts a
reference argument to an override with a copy, so a plugin that filled
in `detect` wrote to a throwaway object and the host always saw an empty
`detectResult`. It is now passed so that Python edits the caller's
struct. Plugins see the same `DetectResult` argument as before.

## TODO

- Expose the `BAMBU_NETWORK_*` return codes to Python (the
`orca.printer_agent` binding and the generated stub from
`scripts/generate_orca_python_stubs.py`). Plugins can already return
them, but only as hard-coded numbers.

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

- New `tests/slic3rutils/test_plugin_printer_agent.cpp`: an agent whose
operations raise, one that omits them, and one that returns the wrong
type all answer like a missing agent, and the interpreter stays usable.
A working agent's answers reach the host unchanged, including
`request_bind_ticket`'s out-param and the fields a plugin writes into
`bind_detect`'s `detect`.
- The `bind_detect` check failed before the fix (`"" == "192.168.0.2"`)
and passes after.
- `slic3rutils` passes under `ctest` (144/144); full Release build clean
on Linux.
- End to end on Linux with a test plugin whose chosen operations raise
(`start_discovery`, `get_filament_sync_mode`, `disconnect_printer`):
selecting the plugin's agent in the printer preset and switching back
logged each raise as a `Printer agent plugin '…': <operation> failed`
line, and the app kept running and closed cleanly (exit 0). Without the
guard, the first raise (`start_discovery`, on selecting the agent) ended
the app with `Uncaught exception` and SIGABRT (exit 134); that run used
a build whose printer-agent files are identical to `main`.

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-09-22 16:57:55 +08:00
Hanif Koh 57b3a040e2 Pass bind_detect's Result to Python by Reference
pybind11 copies a reference argument to an override, so a Python
printer agent that filled in detect wrote to a throwaway object and the
host always saw an empty detectResult. Pass it as a pointer so Python
edits the caller's struct.
2026-09-22 14:55:18 +08:00
HanifKoh 2876374b45 Add a Dockable HTML Panel API for Plugins (#15736)
orca.host.ui.create_dock_panel(html, title, width, height, on_message,
on_close, dock) hosts plugin HTML in a pane of the Plater's dock manager,
next to the sidebar, and returns a UiDockPanel handle
(post/show/hide/close/is_open). The arguments follow create_window(). The
panel uses the window.orca bridge of plugin windows, restores its position
and size from the saved window layout, hides with the Plater off the Prepare
and Preview tabs when floating, and is closed with its plugin; plugin panes
are removed in MainFrame::shutdown().

The web view hosting moves out of PluginPage into a shared WebPanel base:
bootstrap page and swap to the plugin HTML, theme, element-default and
bridge scripts, window.orca message parsing, delivery to the page, and live
re-theming, also re-applied on every load after the swap. Pages tabs and
docked panels both derive from it. Pages tabs now re-theme in place on a
theme change instead of being reloaded, and a window.orca call a host does
not support is logged.

What the hosts share no longer lives in one of them: the bootstrap page, the
base URL and the plugin-window bridge move to Widgets/WebHosting, used by
WebDialog and WebPanel alike. The Plater restores plugin panes with a new
saved-layout parser, GUI/AuiPaneLayout, kept in its own small header so
slic3rutils can test it without pulling in the Plater.

The web hosting classes carry no plugin name, so other hosts can reuse them:
PluginWebDialog becomes WebDialog (its bootstrap page moves to
resources/web/dialog/WebDialog), and destroy_for_plugin(),
load_plugin_content() and plugin_defaults_user_script() become
destroy_silently(), load_page_html() and element_defaults_user_script().

Includes a sample plugin (sandboxes/orca_dock_panel_plugin_any.py) and
binding and layout-helper tests in slic3rutils.
2026-09-22 14:52:25 +08:00
SoftFever 211dd7daaa Merge branch 'main' into feature/texture_displacement 2026-09-22 14:46:36 +08:00
Hanif Koh fd9c1218a4 Keep Python Printer Agent Exceptions Out of the Host
IPrinterAgent callers do not catch, so a Python raise, a missing
override or a wrongly typed return from a printer agent plugin escaped
into the GUI. Each trampoline operation now logs the failure and
answers with NetworkAgent's no-agent value: -1 for status codes, the
empty value otherwise.
2026-09-22 14:35:14 +08:00
Clifford GarwoodandClaude Opus 5 af1cf71a4c Read the IDEX/IQEX enum settings back as the values they were saved as
A coEnum config value has two representations: the typed ConfigOptionEnum<T> a
config cloned from the static classes carries, and the ConfigOptionEnumGeneric
that a config assembled from the option definitions creates - which is what a
preset, a project's own settings and the CLI all hold. imex_cfg_enum() accepted
only the first, so every IDEX/IQEX reader took the option default instead: a
printer saved as rear-left came back front-left in the settings, the bed zones
and the carriage markers, while the preset on disk still held rear-left.

Read the generic form too, keyed on the value map it carries, since only T's own
map yields a T. The last reader that matched on the coEnum tag alone and cast
across the two hierarchies now goes through the helper with everything else.

Declaring the three keys the static classes were missing is what lets a change
to imex_tool_layout invalidate the slice it moves, which it never did before.
The two that are only ever drawn stay out of that: a colour scheme and the
advisory margin bands do not reach a slice, so changing one must not discard it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 18:09:40 -04:00
Kris Austin 824b216f18 feat(gui): assignable keyboard shortcuts (#15706) 2026-09-21 17:36:04 -03:00
Clifford GarwoodandClaude Opus 5 2d80c6a24e Drop the single-filament tower case the estimate no longer reports
The estimate now returns no tower for a lone filament whatever the flush matrix
says, so the discrepancy this case pinned between it and normalize_fdm_2 is gone
and the sibling case covers what remains.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 09:56:44 -04:00
Rodrigo Faselli dc021c6ef6 Merge branch 'main' into main 2026-09-21 09:47:39 -03:00
ExPikaPaka 7aef3d1215 Merge remote-tracking branch 'origin/feature/texture_displacement' into feature/texture_displacement
# Conflicts:
#	src/libslic3r/TextureDisplacement.cpp
2026-09-21 09:00:18 +02:00
ExPikaPaka cef6527f9d Texture displacement: per-layer bake, UV pane redesign, unwrap and layer view fixes
- Bake: each layer is sampled only on its own painted area; analytic
  projections used to stack every layer over every painted region, so the
  top layer's texture showed on all of them (colour sampler too)
- Auto resolution follows the texture's texel size and sharpness again
- Unwrap: charts cut by each face's own normal (a cube gives 6 islands, not
  12 triangles); non-disk charts (tubes, closed shells) are split until they
  flatten; connected nets test real triangle overlap, grow from the largest
  chart and are packed side by side
- UV edits are stored per unwrapped copy, so dragging a seam vertex no
  longer moves its copies in neighbouring islands
- UV pane: tool strip with unwrap settings moved in from the panel, sharp
  HiDPI icons, clearer island/edge/selection drawing with hover, texture
  picker from the thumbnail, texture no longer lost on reopen (GL state
  from the 3D view, background upload retries)
- Panel: whole-model select/erase as icons in the tools row; inactive
  layers' paint shown muted; colour textures shown in colour in the picker
- Built-in displacement texture library
- Tests for unwrap segmentation, connected nets, UV edits and per-layer
  sampling
2026-09-21 08:59:26 +02:00
Kris Austin b4b4438a47 fix: custom G-code keyword check uses the wrong list and skips the editor (#14908) 2026-09-20 10:24:55 -03:00
Kiss Lorand caa15491c3 Fix duplicate timelapse G-code on i3 printers (#15734) 2026-09-19 13:19:54 -03:00
SoftFever c168d0c8db Stop estimating a prime tower for a single used filament (#15760)
# Description

A plate using one filament no longer reserves or draws a prime tower in
the plater just because multiple filament slots are configured. The
shared footprint estimate gated "no tower" on the purge volume being
zero, but on non-Bambu printers with the shipped defaults that volume
comes from the SEMM flush matrix, which reads every configured slot and
stays nonzero even when only one filament is used — so the preview
showed a tower the print could never contain. The estimate now decides
from the filament count alone, with wrapping detection and smooth
timelapse staying the only reasons a single-filament plate keeps a
tower, matching `Print::has_wipe_tower()`. The gate also moved above the
flush-volume scan so the lone-filament path no longer pays for it.

No change to slicing output — toolpath generation already omitted the
tower for a single used filament; this removes the phantom preview,
placement reservation, and validation footprint.

# Screenshots/Recordings/Graphs
Before the fix:


https://github.com/user-attachments/assets/c5b0ea61-127d-408e-bafe-f1f76c58b996

After the fix:


https://github.com/user-attachments/assets/ac6632b3-5d44-4013-aca3-73f6cfd1139c



## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

Added a shipped-defaults single-filament case to
`tests/libslic3r/test_wipe_tower_estimate.cpp`: it estimates a 5.04
mm-deep tower before the fix and zero depth after. `[WipeTowerEstimate]`
and the fff_print `[WipeTower]` suites pass.

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-09-19 12:46:00 +08:00
Kris Austin 49c811ceb6 fix: H2D extruder sync does nothing on the Motion ability page (#14931) 2026-09-18 20:54:43 -03:00
Ian Bassi 8a17df4a89 Fix some overhang detection (#15694) 2026-09-18 14:23:38 -03:00
SoftFever 60ebbf7daa Stop estimating a prime tower for a single used filament
The no-tower case was gated on there being no purge volume, but the SEMM flush matrix reads every configured slot and is nonzero even when only one filament is used, so the plater preview drew a tower the print would never contain.
2026-09-19 00:38:22 +08:00
SoftFever 533b68ed9f Fix colour textures loading upside down and crashes on damaged PNGs
Colour height maps now displace the same way up as grayscale ones. A
truncated or corrupt PNG in the texture folder or a project file now
loads as an empty texture instead of aborting the app, and a 16-bit
colour PNG is converted on load rather than displacing as noise.
2026-09-18 20:37:35 +08:00
Clifford GarwoodandClaude Opus 5 1c42bb0cd6 Merge upstream main: Design tab CAD, OTA OPC CI, JAYO filament profiles
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 08:00:12 -04:00
SoftFever 56561242a7 Merge branch 'main' into feature/texture_displacement 2026-09-18 16:33:18 +08:00
Lam Wei Lun 8f4e3dde55 Merge main 2026-09-18 15:59:00 +08:00
SoftFever f1f68ffc3f Merge branch 'main' into cad-mainline 2026-09-18 14:01:23 +08:00
Clifford GarwoodandClaude Opus 5 84c13d4913 Merge upstream main: wipe tower port, per-filament bounds checks, CLI mixed rules
Four files overlapped, and each resolution favours upstream where the two sides
had done the same work:

WipeTower's M104/M109 tool qualifier. Both sides bounds-checked the physical
extruder map lookup; upstream omits the T qualifier when the map cannot answer,
where this branch fell back to the logical index and so named a carriage that may
not be the one printing. Upstream's behaviour is what this branch documents
elsewhere, so its version is taken and the local helper is dropped.

get_extruders' mixed-slot switch. Upstream added the same concept to the CLI
overload as expand_mixed_slots, so the GUI overloads' parameter is renamed to
match rather than carrying two names for one idea.

GLCanvas3D's sequential-clearance branch gains upstream's
update_compacted_wipe_tower_clearance for the by-layer case.

The printer_agent re-sync in TabPrinter::reload_config was upstream's and their
preset-undo fix removed it, so it goes; the IMEX modes grid re-sync beside it
stays, since it spans three options and is not a Field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:50:53 -04:00
Clifford GarwoodandClaude Opus 5 b6a2c76e5d Share the prime-tower rule with the slicer instead of copying it
imex_wipe_tower_hull() open-coded "does this plate print a tower?" as one
filament and no forcing reason. normalize_fdm_2, which is what actually
clears enable_prime_tower before slicing, has a second arm the copy omitted,
and reads the mixed-filament flag project-wide where the copy read the
plate. Two cases went wrong in opposite directions.

A ByObject plate with several objects prints no tower and the scene draws
none, yet the copy validated one and could refuse the slice with "the prime
tower overlaps an area reserved for IDEX/IQEX parallel printing" - with
nothing on screen to move.

A plate using one plain slot while some other slot in the project is a blend
does print a tower, because normalize_fdm_2 keeps it for any mixed filament
in the project, and the copy skipped validation entirely, so the tower could
be placed in a carriage zone and sliced.

prime_tower_is_printed() in libslic3r states the rule once, and both the
gate and a test use it. The counts are the ones normalize_fdm_2 is handed:
filament slots as authored, so a mixed slot counts once, and distinct
objects rather than instances. filament_is_mixed is a project option, so it
is passed in rather than read from the print preset.

The test drives every combination the rule looks at and compares the verdict
against normalize_fdm_2 itself, so the two cannot drift again without
failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:41:42 -04:00
Clifford GarwoodandClaude Opus 5 182dbd55e4 Pin the flush-matrix purge for a single filament
With single_extruder_multi_material and purge_in_prime_tower, the flush
matrix replaces the prime volume and its average is non-zero for one
filament, so the estimate reports a tower. normalize_fdm_2 clears the tower
for that same plate, so none is printed: the estimate answers how big a
tower is, never whether there is one, and a caller reading a non-zero depth
as "a tower is printed" reserves space for, or blocks on, a phantom. The
test asserts that disagreement directly.

Type1 is the exception in the same case, because it decides from its
per-filament purge list and a lone filament is never changed to. The scene
reads the same estimate either way. Type2 is the default for every non-Bambu
printer, and so for every IDEX/IQEX one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:41:42 -04:00
Clifford GarwoodandClaude Opus 5 3c31856b85 Bound the second-layer temperature lookup in filament-slot space
nozzle_temperature is variant-expanded, so its length is columns rather than
filament slots. On the dynamic-nozzle path that makes it longer than the
slot count, and an out-of-slot index reached get_filament_config_index() and
came back as filament 0 - the clamp the bounds-checking was meant to remove.
Bound by the slot count instead; on the ordinary path the two are equal and
nothing changes. The is_extruder_used write gains the matching lower-bound
guard.

IMEXHelpers.hpp now states both halves of the rule its call sites follow.
Bound anything derived from the extruder map against the filament slot count
before using it as a filament id, not against the option about to be read.
And a miss is -1, which is a correct tool qualifier but matches no physical
head, so it cannot serve as a skip-the-primary sentinel: which head prints a
filament is answered by the filament and the map, never by a mode role,
since a primary-mode print may use any or all tools, one at a time.

The consequence is recorded there rather than left implicit. The two skip
sites skip nothing for a slot past the end of the map, so a plate with more
slots than nozzles double-writes the primary's pressure advance. It is
narrow and unreported, and a guard there would be a smaller change than
naming a head.

The header also records that RepRapFirmware sends an unqualified pressure
advance as M572 D0, naming drive 0 absolutely rather than the active tool.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:27 -04:00
Clifford GarwoodandClaude Opus 5 b6f690a65c Migrate the IDEX/IQEX config keys testers already have
The parallel printing options shipped to testers as is_ixex and ixex_*,
became is_imex and imex_* when the feature stopped being called iXex, and
the two clearance keys were renamed once more to say what they measure:
nozzle to carriage edge on the collision side, not the carriage's full
width. Nothing translated any of it, so loading an existing printer profile
dropped every one of these values - the keys are unknown and cleared.
is_ixex is the one that matters most, because without it the others migrate
into a feature that stays switched off, leaving settings that look
configured and do nothing.

Per-plate mode is persisted twice and only one path went through
handle_legacy. Plate metadata in a 3MF is matched by exact attribute name
and written with set_key_value, so a project saved between the per-plate
mode landing and the rename loaded every plate back on Primary and sliced
single-carriage with no warning. The loader now accepts the old attribute
name.

ixex_primary_col and ixex_primary_row are dropped rather than mapped: the
primary is a role inside the mode's active-tools string now, not a grid
coordinate, and they were never in an option list, so no saved file carries
them.

The test drives the full era-1 key list and asserts the enum values rather
than non-nullness, since a forward-compatible substitution would otherwise
hide a failed deserialize behind a default. handle_legacy had no test before
this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:27 -04:00
Clifford GarwoodandClaude Opus 5 f7a08b0868 Spell the mode loop initializers as std::string
Two range-for loops bound const std::string& to braced lists of string
literals, so each iteration constructed a temporary to bind to. GCC 16
reports it as -Wrange-loop-construct, which upstream's blanket -Werror turns
into a build failure; clang does not report it at all. Spell the
initializers the way the loop above them already does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:26 -04:00
Clifford GarwoodandClaude Opus 5 98544fd6bc Bound the parallel pressure-advance lookup to the filament count
Review findings on the preceding commit, plus one defect it should have
caught.

- The IDEX/IQEX pressure-advance loop fed resolve_filament_for_head()'s
  result straight into enable_pressure_advance and pressure_advance. That
  result is bounded by physical_extruder_map, which holds one entry per
  NOZZLE, while both options are indexed per filament SLOT. On a printer
  with more nozzles than the project has filaments the two spaces diverge
  and get_at() clamped the overflow onto filament 0, emitting its pressure
  advance on a secondary carriage. The second-layer temperature loop bounds the
  same lookup, but against nozzle_temperature, which is variant-expanded and so
  is not the slot count either -- it is not the precedent it looks like.
  IMEXHelpers.hpp states the rule
  once, and a test pins the contract that makes the bound necessary:
  resolve_filament_for_head() answers in nozzle space, so a non-negative
  result is not by itself safe to use as a filament id.

- The header claimed every caller renders a -1 tool qualifier as "emit
  none". RepRapFirmware substitutes the historical D0 instead, deliberately
  and with its own comment in GCodeWriter. Say so, rather than leaving a
  contract a future author would code against.

- A cross-reference pointed at a hard-coded line number that the preceding
  commit had itself shifted by nine lines. Name the function instead.

- The multi-color rejection reasons reach the user through Print::validate()
  as raw English, while the returns on either side of them use L(). Wrap
  them and register IMEXHelpers.cpp for extraction. They also still said
  "IMEX", the internal name, so they move to IDEX/IQEX with the rest of the
  user-facing strings rather than shipping the internal one to translators.

- Trim the preceding commit's comments. One block explained the same
  clamping hazard six times; the canonical explanation now lives in
  IMEXHelpers.hpp and the call sites point at it. The mode grid carried
  twelve lines of commentary and no code, most of it archaeology already in
  the commit message, and one claim about the modes editor that was not
  true. The ArrangeJob threading note stays: it documents an invariant that
  cannot be recovered from the code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:26 -04:00
Clifford GarwoodandClaude Opus 5 e309029b28 Fix extruder-map bounds, popover lifetime, and arrange thread safety
Review findings on the IDEX/IQEX parallel printing code, all in paths the
feature owns.

- physical_extruder_map lookups used ConfigOptionVector::get_at(), which
  clamps an out-of-range index to values.front() rather than reporting a
  miss. The map holds one entry per nozzle while filament ids index slots,
  and nothing caps the slot count at the nozzle count, so a project authored
  with more filaments than the printer has extruders silently addressed the
  primary's head: pressure advance pinned to the wrong carriage, and
  skip-primary loops suppressing whichever head sat at pem[0]. Bounds-check
  at all four sites and treat the miss as "no mapping" (-1). Covered by a new
  imex_pem_tool_for test; the header note now warns against get_at here.

- IMEXFilamentPickerPopover leaked a top-level window per ghost click:
  wxPopupTransientWindow::Dismiss() only hides, and never reaches OnDismiss().
  Destroy from an OnDismiss() override and dismiss the picker through
  DismissAndNotify(), which is the path a successful pick takes.

- ArrangeJob read PartPlate's IMEX zone cache from the worker thread, where
  a cache miss rebuilds GLModel members with no GL context current while the
  GUI thread may be painting them. Snapshot the zones in prepare(), on the
  main thread, already converted to plate-local coordinates.

- The mode grid anchored its row window to the Primary's gantry row. A window
  as tall as the grid can only start at row 0, so this drew tiles for tools
  that do not exist and hid real ones. Render the whole grid instead; a
  Primary outside it is a data problem the zone layout already reports.

- Build the mode tooltip from one format string rather than two catalog
  fragments concatenated around a runtime value, so translators can move the
  mode name within the sentence, and register IMEXModesCtrl.cpp for string
  extraction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:32:26 -04:00
SoftFever f520e9221f Repair shipped default materials and obsolete settings, and validate them (#15741)
* add orca profile skill

* add default material check

Improve validation for default materials and filament profiles

* Fix default materials and obsolete keys

* clarifying orca-profiles skill
2026-09-18 00:39:46 +08:00
Valerii Bokhan 60b4a61854 Fix: Show indexed coFloatsOrPercents options in unsaved changes dialog (#15472) 2026-09-17 10:56:17 -03:00
Ian Bassi 59e40a2c2e Print unsupported walls last (#15411) 2026-09-17 09:14:20 -03:00
Ian Bassi 82e91bd472 Port wipe tower BBS improvements (#15485) 2026-09-17 09:08:50 -03:00
Ian BassiandRodrigo Faselli 72774e5398 Toolchange Cyclic Order (#14868)
* Toolchange Cyclic Order

* Apply cyclic order to first layer

* Unit test

* Copilot fixes

---------

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-09-16 12:19:03 -03:00
Kris Austin a610d2d899 ci: build Windows with build_win.bat and drop the old scripts (#15721)
* ci: build Windows with build_win.bat and drop the old scripts

The deps and slicer jobs called build_release_vs.bat; they now call
build_win.bat. --deps-dir and --build-dir name the build/build-arm64
directories the cache keys and later steps already use, and the
script's own VsDevCmd call replaces the Enter-VsDevShell blocks.
With both stages configured each way into the same directory, the deps
superbuild is byte-identical and the slicer build files are
byte-identical apart from CMakeCache.txt recording how DEP_BUILD_DIR
was set.

Two changes beyond the script swap:

- The compiler is the clang-cl bundled with Visual Studio, the script's
  default. The old script's bare "clang-cl" resolved to the LLVM on the
  runner image's PATH, 20.1.8 on x64 and 22.1.8 on arm64; both arches
  now build with the 22.1.3 VS 18.9 ships. Cached dependencies are only
  rebuilt when deps/ changes, so they stay on the LLVM they were built
  with; the arm64 leg already links deps built with Clang 19 into a
  Clang 22 slicer.
- The deps job no longer zips the dependencies afterwards. The zip was
  never uploaded and was not in the cached path.

A failed cmake --build now fails the job. The old script returned 0, so
the arm64 failure fixed in #15719 was reported as success and the
half-built dependencies were saved to the cache.

build_release.bat, build_release_vs.bat and build_release_vs2022.bat are
removed; nothing referenced them any more.

* ci: run Build all when the Windows build script changes; tests doc builds the deps

The push filter of build_all.yml never listed a build script, and CI
now depends on build_win.bat, so it and its test suite join the list
the pull_request filter already has.

tests/AGENTS.md told Windows to run build_win.bat --run-tests, which
only implies -s and stops at the dependency check on a clean checkout.
The old build_release_vs.bat tests built the dependencies first, so
the line now says -ds --run-tests.
2026-09-16 09:50:27 -03:00
SoftFever ade9e77b6b Run every profile maintenance job from one tool (#15726)
* Run every profile maintenance job from one tool

orca_id_tool.py becomes orca_profile_tool.py, and orca_extra_profile_check.py
and orca_filament_lib.py fold into it as subcommands: check, generate-id, fix,
trim, update-index and update-snapshot. The three scripts already overlapped --
the checker imported half of its rules from the id tool, which in turn kept a
copy-pasted set of output helpers to avoid the resulting import cycle -- while
disagreeing on how a vendor is enumerated, how a JSON file is read and what the
exit code means. One file settles all three.

check, normalize, trim and update-index reproduce their predecessors exactly; normalize and
update-index were diffed byte-for-byte against the old scripts over a copy of
the whole tree. Deliberate changes: the compatible-printers check no longer
switches itself off when --check-materials is passed, an error exits 1 rather
than -1, update-index honours --profile-type and reports a profile it cannot
place instead of dropping it from the index, fix and update-index gained
--dry-run, trim keeps an unindexed file some surviving profile still inherits
from, and vendors are enumerated as directories with an index -- which is why
blacklist.json, a data file that an unscoped index rebuild once wrote four empty
list sections into, loses them here and will not collect them again. The dead
rename_filament_system() helper is gone.

The suite under scripts/tests now covers the maintenance commands too, and CI
runs it; nothing there ran in CI before. No shipped profile data changes apart
from those four keys.

* update vendor index files with "python3 ./scripts/orca_profile_tool.py update-index" and "python3 ./scripts/orca_profile_tool.py normalize"
2026-09-16 19:53:23 +08:00
Lam Wei Lun 075a84093f Fixes issue with showing hidden settings in speed dial. 2026-09-16 16:02:39 +08:00
packerlschupfer 93c8b3f2b0 CLI: --ground-* orientation from the Lay on Face planes, and --inspect-mesh (#15073)
* CLI: --ground-face-* / --lay-flat / --center-on-bed orientation primitives

Adds the CLI counterparts to the GUI's lay-flat / face-pick gizmos.
Scripted / CI / AI pipelines can now set orientation without rendering
a wxWidgets frame; today the only way is a GUI round-trip.

New CLI actions (all operate in the mesh-local frame so they compose
with prior --rotate-* / --orient flags):

  --ground-largest-face 1     Auto-detect the largest planar-face
   or  --lay-flat 1           cluster (area-weighted), rotate so its
                              normal points -Z. Covers "this part has
                              one obvious flat side" cases.

  --ground-face-normal NX,NY,NZ    Pick the face whose mesh-local
                                   normal best matches the given
                                   vector; ground it. e.g.
                                   `--ground-face-normal 1,0,0`
                                   stands a part on its +X side.

  --ground-face-point X,Y,Z        Find the triangle containing the
                                   given mesh-local point; ground its
                                   face. Disambiguates when several
                                   faces share a normal (largest
                                   containing triangle wins).

  --center-on-bed 1                Translate so the XY bounding-box
                                   centroid lands at the bed center
                                   (derived from printable_area).

New file `src/slic3r/Utils/MeshOrient.{hpp,cpp}`:
- collect_triangles_object / compute_face_clusters — quantize
  per-triangle normals (0.001, ~0.06°) and area-weighted-average
  within clusters. Same clustering logic used by lay-flat.
- apply_ground_rotation — same math as Selection::flattening_rotate
  in the GUI (Selection.cpp:1432): world-space quaternion from the
  transformed normal to -Z, applied as offset * new_rot * old_no_offset
  on every instance of every object, then a per-instance Z-lift so the
  grounded face lands at exactly 0 (avoids "No layers were detected"
  from FP-error z≈-1e-9).
- ground_face_point uses a top-N cluster search + point-in-triangle
  test in local space; largest-area triangle wins on ambiguity.

Rationale: without these, any CLI pipeline that needs a specific
face on the bed must either encode custom rotation math per part or
break out of the pipeline into the GUI. Both are bad for
reproducibility. The --ground-face-* triple + the largest-face
auto-mode cover essentially every orientation intent expressible
in a slicing wizard.

Scope:
- `src/slic3r/Utils/MeshOrient.{hpp,cpp}` — new, ~420 lines
- `src/slic3r/CMakeLists.txt` — 2-line registration
- `src/libslic3r/PrintConfig.cpp` — 5 new CLIMiscConfigDef entries
- `src/OrcaSlicer.cpp` — 58-line handler block + 1 include

No behaviour change when the flags are absent.

(cherry picked from commit c45a9795e1)

* CLI grounding: choose among the Lay on Face planes, per object

Addresses review:
- Move the geometry of GLGizmoFlatten::update_planes() into
  libslic3r/LayOnFace and use it from the gizmo and the CLI, so the
  --ground-* options pick convex-hull faces per object and instance,
  with part transformations (--rotate-x/y) applied.
- Drop --center-on-bed, the --lay-flat alias and MeshOrient; make
  --ground-largest-face a coBool.
- Parse --ground-face-normal and --ground-face-point strictly. A point
  that only some objects contain grounds those and leaves the others.
- Fold in --inspect-mesh from #14603, reporting the same planes.
- Tests in tests/libslic3r/test_lay_on_face.cpp: bounding boxes before
  and after, rotate then ground, two objects, and a ribbed part whose
  parallel inner faces outsum its base.

* CLI --inspect-mesh, --ground-face-*: reject missing input and empty values

- Without an input file or --load-assemble-list, --inspect-mesh printed
  nothing and exited 0. Reject it up front with CLI_INVALID_PARAMS.
- An explicit empty --ground-face-normal or --ground-face-point was
  silently ignored. Only options given on the command line reach the
  transforms loop, so an empty value now fails the strict parse like any
  other malformed value.
2026-09-16 12:56:46 +08:00
packerlschupfer 9321f24959 CLI: --strict, and a warnings array in result.json (#14601)
# Description

Add `--strict` for CI and scripted pipelines, and a structured
`warnings`
array in `result.json`.

## `--strict`

A NON_CRITICAL slicing warning is logged and the slice succeeds: return
code
`0`, G-code written. That suits interactive use, but a pipeline then
ships a
slice with a warning nobody saw. With `--strict`, such a warning fails
the run
with `CLI_SLICING_ERROR` before the G-code is exported. Without the
flag,
nothing changes.

In FFF the warning that reaches this path is "support needed but
disabled"
(`PrintObject::generate_support_material`). `--no-check` skips that
check, so
`--strict --no-check` is rejected with `CLI_INVALID_PARAMS`.

`--strict` is read before any work, so it doesn't depend on argument
order and
`result.json` reports it for early failures as well.

## `result.json`

Two new top-level fields:

- `warnings`: `[{"class", ...details}]`. One class is wired:
`slicing_warning_non_critical` with `plate_id` and `text`, recorded
whenever
such a warning fires, with or without `--strict`. The array also fills
on
  runs that succeed, so `return_code` stays the verdict.
- `strict_mode`: whether `--strict` was on.

`record_exit_reson` writes `result.json` on Linux only, so both fields
exist
only there. The non-zero exit works on every platform.

## Tests

- `tests/fff_print/test_support_material.cpp` (all platforms): an
overhang
sliced with support off raises the NON_CRITICAL support-needed status,
and
  the no-check flag suppresses it.
- `tests/cli/test_cli_strict.sh` (Linux only): runs `orca-slicer`
without
flags, with `--strict`, and with `--strict --no-check`, and checks the
shell
status and `result.json` of each. It runs the built binary, so it
carries the
`RequiresApp` label, which `scripts/run_unit_tests.sh` excludes because
the
  unit-test job only receives `build/tests`. Run it with
  `ctest --test-dir build/tests -C Release -L RequiresApp`.
- CI: `unit_tests.yml` now passes `Release` on Linux too.
`build_linux.sh`
configures Ninja Multi-Config, and without a config ctest drops the
labels of
plain `add_test()` tests, so this test ran as "Not Run" instead of being
excluded. The docs that assumed Linux was single-config are corrected
too.

Built and run locally on Linux (GCC 14) on current `main`: both tests
pass,
and the touched files compile clean under Clang with `-Werror`.
2026-09-16 12:54:48 +08:00
Lam Wei Lun f76e4b1e02 Fixed centering of icons. Disabled zooming in/out on Windows. Added default icons 2026-09-16 12:15:27 +08:00
Lam Wei Lun 435336a3cf Merge branch 'main' into weilun/speed_dial 2026-09-16 10:20:13 +08:00