Commit Graph
30514 Commits
Author SHA1 Message Date
Clifford GarwoodandClaude Opus 5 6c67fcfe07 Read IMEX geometry defaults from print_config_def instead of literals
Five sites carried a hardcoded fallback for imex_tools_per_gantry that had
to match the value registered in print_config_def, with nothing enforcing
the agreement, and several explanatory comments miscounted the sites they
described or cited stale line numbers.

Add imex_cfg_int/_float/_bool/_enum<T> to IMEXHelpers, which return the
value registered for the key when it is absent from the config, so the
registration is the single source and there is nothing left to keep in
sync. Route every read of the IMEX geometry keys through them: 32 call
sites across IMEXZones, PartPlate, GCodeViewer and Tab. The only direct
lookup left is the bail in PartPlate::imex_multicolor_block_reason, which
must not default because it reports a routing conflict and a defaulted
grid would produce a false warning.

imex_cfg_enum uses dynamic_cast on both halves rather than the type()
comparison the others use: every ConfigOptionEnum<T> reports coEnum, so a
type() check cannot tell one enum type from another and would cast a
ConfigOptionEnum<OtherEnum> to the requested T. The ConfigOptionPercent :
ConfigOptionFloat inheritance that rules dynamic_cast out for the float
accessor has no analogue for enums.

Correct the comments that prompted this: the cache-key input list in
PartPlate named five inputs for a nine-part key, the ImexMarkerKey note
in GCodeViewer called imex_tool_layout an input only the preview reads
when the plate keys it too, and four file:line citations pointed at the
wrong lines. Values are unchanged at every converted site; cache key
strings keep their existing representation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 20:34:04 -04:00
Clifford GarwoodandClaude Opus 5 e45c241466 Address the review on the IMEX parallel printing PR
Resolves all 21 inline comments, plus six changes that altered behaviour for
users not using the feature and seven defects found alongside them.

The reviewer's central point generalised: a pressure-advance change had moved
every RepRapFirmware user onto an unverified command form. Auditing for that
class found five more — 14 config keys leaking into every exported g-code, an
ungated Moonraker sync writing to non-IMEX printers' presets, every slice
eagerly re-rendering all plate thumbnails, a preset delta-encoding regression,
and physical_extruder_map being normalised for printers that read it the other
way.

The structural asks landed as asked: the 392-line bed-zone geometry moved to
libslic3r and is now unit-tested, the preview consumes that same layout instead
of a second copy, nine open-coded mode lookups became one, and the modes editor
moved out of Tab.cpp. Making the geometry testable exposed three further
defects in it, including an aggregated gantry that raised no collision strip.

The worst bug was not in the review: the GUI computed the firmware-managed
slice offset in the plate-list world frame while both consumers subtracted the
plate origin again, so every plate after the first failed to slice with 'part
is off the plate'.

User-facing strings now read IDEX/IQEX, honouring 461c69c83e. Config keys, C++
identifiers and 3MF metadata keys keep the imex_ spelling as on-disk format.

815/815 tests pass in Release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 00:26:21 -04:00
Clifford GarwoodandClaude Opus 5 c09ce3a0d1 Merge upstream main: CLI argument parsing, GUI string fixes, nozzle type undo tracking, warning cleanups
No IMEX code upstream, so nothing in this merge touches the feature. All 21 overlapping
files auto-resolved; verified every upstream addition is present in the merged tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 00:17:36 -04:00
HanifKoh 57ce18d70d [CLI]: CLI Argument Parsing Fixes (#15478)
* Reject invalid CLI argument values instead of silently accepting them

* Add read_cli accept/reject tests

* Update Option Type for LogFile argument

* Add read_cli vector option tests

* Accept common bool spellings on the CLI, cover --logfile in tests

* Add unit tests for truthy bool parsing
2026-09-04 11:31:42 +08:00
TheLegendTubaGuyandRodrigo Faselli 7acea3ed09 Honor symbolic default bed types for new printers (#15273)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-09-03 19:42:31 -03:00
Kris Austin b370d8ef31 build: clear 31 warnings - copy and move declarations (#15507) 2026-09-03 18:35:10 -03:00
Kiss Lorand c57ea0ec67 Fix nozzle type undo and unsaved changes tracking (#15515) 2026-09-03 18:28:26 -03:00
Kiss Lorand 6a13cc2ab6 Fix Detach from parent checkbox not updating visually (#15520)
Refresh detach-from-parent checkbox state

Allow the detach checkbox toggle event to propagate to the custom CheckBox control so it refreshes its bitmap after the value changes.
2026-09-04 00:25:21 +03:00
Clifford GarwoodandClaude Opus 5 5eac300d91 Use IDEX/IQEX in the user-facing strings
Closes review comment 10.

`461c69c83e` settled this in April — IMEX internally, IDEX/IQEX as the user-facing label — but the
UI strings were never converted. Every translated string naming the feature now reads IDEX/IQEX:
41 occurrences across the printer and process option labels and tooltips, the modes editor, the
plate mode indicator, the pre-slice warnings, the placement refusals and the slicing errors. The
reviewer listed eight; the rest were in the same class.

Nothing else moves. The config keys keep the `imex_` spelling — `is_imex`, `imex_mode_names`,
`imex_parallel_mode` and the rest are on-disk format in existing printer presets and 3MF projects,
so renaming them would break every profile and project already saved. C++ identifiers, filenames,
comments and test names keep IMEX as well: it stays the internal name of the subsystem, which is
what covers the topology space (one gantry with 2-4 tools, 2x1 and 2x2 grids) that neither acronym
names on its own. Where a tooltip quotes a key, the key spelling is preserved and only the feature
word around it changed.

The `is_imex` tooltip is reworded rather than substituted: it already named the hardware families
parenthetically, so a literal replacement would have said IDEX/IQEX twice in one sentence.

No translation impact — no IMEX string had reached OrcaSlicer.pot or any catalogue, so there is
nothing to migrate. One test asserted on the old error text and now matches the new one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 10:05:37 -04:00
Kris Austin f92bd81190 fix: build_win.bat builds with whatever clang-cl is first on PATH (#15504)
* fix: build_win.bat builds with whatever clang-cl is first on PATH

VsDevCmd appends the Visual Studio LLVM directory to the end of PATH, so
a standalone LLVM already on it shadows the Visual Studio one. -l -x
passed a bare clang-cl.exe for CMake to resolve, so the build ran on
whichever copy came first. For one reporter that was an LLVM 11, which
failed the compiler check before anything was compiled:

    -- Check for working C compiler: C:/Program Files/LLVM/bin/clang-cl.exe - broken
    lld-link: error: undefined symbol: __guard_eh_cont_table

The compiler is now resolved through vswhere and passed as a full path,
so PATH order no longer matters. CMake derives the linker from the
compiler directory, so lld-link follows. Only a configure passes it to
CMake, so -p and --no-configure resolve nothing and stay buildable on a
machine with no clang installed.

When Visual Studio has no clang toolset the script falls back to the
first clang-cl on PATH and names it. With none installed at all it now
errors with what to add, instead of failing later inside CMake. Every
clang-cl run that configures prints the compiler it resolved.

The suite gains a clang-cl fixture earlier on PATH than the Visual
Studio one, and an empty ProgramFiles(x86) to put vswhere out of reach,
which covers both fallbacks without touching the machine.

* fix: build_win.bat pointed at a solution file that is not there

The Visual Studio 2026 generator writes OrcaSlicer.slnx and the releases
before it OrcaSlicer.sln. The summary hard-coded the second, so the path
it printed after an MSVC build against 2026 was wrong.
2026-09-03 09:56:33 -03:00
Clifford GarwoodandClaude Opus 5 bd8dfd6250 Cover the IMEX slice offset, the mode G-code placeholders, and the non-IMEX heater guard
Closes review comments 13, 14 and 15, and adds the test for a shipped-profile
regression that nothing guarded.

- 14 and 15: compute_imex_slice_offset had eight tests on the calculation and none
  on the result, which is the whole firmware-managed path. test_imex_slice_offset
  now covers the derivation end (which config produces a non-zero offset, and that
  it is plate-local rather than moving with the plate origin -- the bug that
  shifted every plate after the first) and the consumption end (emitted
  coordinates and first_layer_print_min/max both move by the derived amount).
  The first_layer case also cross-checks the two consumers against each other: the
  declared bounds must keep the same relationship to the emitted toolpaths in both
  frames, which fails if exactly one of them is shifted. It deliberately does not
  pin the size of that gap -- it is 2.225 mm here, set by the wall generator, the
  same with no offset at all, and pinning it would fail on an unrelated change.
- 13: nothing exercised the imex_mode / imex_mode_index / imex_mode_gcode
  placeholders or the {global} flow into machine_start_gcode that their ordering
  exists to guarantee. Seven cases now do, including the ordering itself -- the
  mode script declares a global and machine_start_gcode reads it back, so moving
  the mode processing later leaves the variable undefined and fails the export --
  plus the inert cases (Primary mode, and a printer with the table filled in but
  is_imex off). All matching is whole-line, because the config block the exporter
  appends repeats machine_start_gcode verbatim and would make substring checks
  meaningless.
- New: GCodeWriter passes this->config.is_imex.value into the heater remap, and
  nothing tested that it passes the flag rather than a constant. Hardcode true
  there and the whole suite stays green while fdm_bbl_3dp_002_common, which ships
  physical_extruder_map [1,0], starts sending filament 0's M104/M109 to heater 1.
  The new case runs a two-nozzle non-IMEX printer with that map and asserts each
  filament's temperature reaches only its own tool. It uses idle_temperature via
  ooze prevention rather than nozzle_temperature: keys in
  filament_options_with_variant are re-indexed per filament by variant slot at
  apply time, and this harness pins nozzle_diameter to one value, so every filament
  resolves to the same slot and the temperatures stop telling the heads apart.

Also fixes two weaknesses in tests added earlier in this branch: an assertion that
would have been prefix-satisfied by the very routing it was meant to exclude, and
a whole-file command comparison between two slices, which this slicer's output is
not stable enough to support.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:57:14 -04:00
Clifford GarwoodandClaude Opus 5 7c49660c05 Share one routing derivation with the slicer, and stop two GUI paths acting on non-IMEX printers
Closes review comment 20; the other two are non-IMEX leaks found auditing the
branch.

collect_imex_warnings re-derived the active mode, its tools, the primary and the
filament routing that Print::validate also derives, so the warning and the hard
block could drift apart -- and this function had already had one index-confusion
bug, the AFC/MMU wrong-filament names fixed in fbc58d2a1d. Both now read
imex_resolve_routing() and derive nothing themselves. The function stays
file-static: what is left in it is PresetBundle lookups, bed-type resolution and
formatting, none of which can disagree with the slicer about what the plate is
doing, and the index-confusion surface is now library code with tests covering the
AFC manifold in both directions. A latent out-of-bounds read went with it -- the
primary fallback can return -1 and the bounds checks were upper-only, so
filament_presets[-1] was reachable on a profile whose roster names a head absent
from the map.

Slicing eagerly re-rendered every plate thumbnail on the main thread after
switching to Preview, up to two blocking offscreen GL renders per plate on every
slice click. The work was already redundant: select_view_3D("Preview") invalidates
the thumbnails and marks the toolbar dirty, and the next frame force-regenerates
them anyway. It could not have served its stated purpose either, since it ran
immediately after reslice(), which only starts the background slice. Both calls
dropped; the plate badge state is recomputed per frame and is unaffected. The
export_3mf thumbnail log lines are back at info, and the slice-event traces
restored.

The Moonraker device sync wrote physical_extruder_map into the edited printer
preset ungated, so any Klipper machine running a current AFC build had its preset
marked dirty with no user action, and saving persisted the map into every 3MF
after. The map is indexed by logical extruder while the device reports one entry
per lane, and nothing in the lane payload carries the logical slot, so the two
index spaces coincide only when the counts match. Now gated on is_imex, written
only when the counts agree, and only when the value actually differs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:56:55 -04:00
Clifford GarwoodandClaude Opus 5 1e608fe24c Have the preview consume the shared zone layout instead of rebuilding it
Closes review comment 17.

GCodeViewer had its own copy of the flip_x/flip_y corner mapping, the active
column/row sets, the physical-to-zone index mapping and the zone pitch -- the same
derivation as the plate's, with nothing keeping the two in step. It now calls
compute_imex_zone_layout() and consumes head_zone_centers. The mirror is expressed
as a reflection about the midpoint of the two zone centres rather than about a
zone-relative strip width, which is algebraically identical for equal-sized zones
and needs no pitch, and the toolhead-box face is chosen by comparing zone centres
instead of physical columns.

The July report of a math error in the visualizer for non-primary heads was this
drift: the sec_box_offset_y else-branch hardcoded -imex_box_wy, which happened to
equal the primary's offset on the rear-* layouts and pointed the wrong way on the
front-* ones. Structurally unreachable now.

Verifying the two sides matched turned up two config defaults that disagreed, both
fixed in their own commits: imex_nozzle_clearance_x/y (the viewer's 30.0f matched
PrintConfig, the zone code's 0.0 did not, and the strip loops are gated on it) and
imex_tools_per_gantry (the library's 2 matched, both GUI paths used 1).

Consuming the shared function meant resolving it per frame, and the sequential-view
marker flag is sticky, so one drag of the slider made every subsequent frame parse
five strings and allocate a dozen containers from inputs that never change. The
resolve now sits behind a cache key mirroring PartPlate::build_imex_cache_key(),
plus the two inputs only the preview reads -- the bed extents and the tool layout.
An idle frame compares scalars and allocates nothing. The toolhead-box mesh, which
was being re-uploaded to the GPU every frame for the same reason, is rebuilt only
when the clearances change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:56:55 -04:00
Clifford GarwoodandClaude Opus 5 838c2f2df8 Reset plates to Primary when their mode is deleted
Closes review comment 9.

PartPlate::reset_imex_mode() had no callers, while the PR description said
deleting a mode resets affected plates to Primary. IMEXModesCtrl now exposes an
on_mode_removed callback that TabPrinter::build_fff handles by resetting every
plate whose mode matches the deleted row, under a single undo snapshot, followed
by the same dirty/update sequence the plate's own mode button runs.

Wired to deletion only, on purpose: the name field notifies on every keystroke, so
routing renames through the same path would orphan and reset the plate on the
first character typed. Renames stay covered by the slice-time fallback and its
warning. The callback is copied to a local before notify(), because notify()
reaches load_from_config() -> clear_rows(), which tears down the row the handler
is running inside.

The rest of this file is the modes editor moving out to its own translation unit,
leaving the include and the construction site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:56:55 -04:00
Clifford GarwoodandClaude Opus 5 f75bdd6013 Move the IMEX modes editor into its own file and fix four defects in it
Closes review comments 21, 4 and 12, and the widget half of 19.

- 21: IMEXModesCtrl was 572 lines inside Tab.cpp. It now lives in
  IMEXModesCtrl.{hpp,cpp} next to IMEXFilamentPickerPopover, which was the
  precedent named in the comment. The move itself is exact -- member order,
  comments and every string literal unchanged -- and the class had no file-local
  dependencies in Tab.cpp, only its include list, so the new source states those
  explicitly.
- 4: a mode row with an empty Name was silently dropped on save, tools and G-code
  with it, and matches_config() compared against that same filtered output so the
  preset never went dirty and the row stayed on screen. Rows are now given a
  generated unique name instead of being discarded, and add_row() pre-fills one so
  the common path never produces a blank. Names are deliberately not translated:
  objects store a mode name in imex_parallel_mode and GCode.cpp matches it by
  string, so a localized name would break a project reopened in another language.
- 12: the editor had a third parser that read a bare token and an unknown role
  suffix as Primary, while parse_imex_active_tools reads both as Copy -- so the
  editor and the slicer could read one imex_mode_active_tools string two different
  ways. Deleted; the editor now uses the same two helpers the slicer does.
- 19: tile state was an int shadowing ImexRole, with the role letters duplicated in
  a second switch that wrote the on-disk format. The tile now holds
  optional<ImexRole>, with Inactive spelled as the absence of a role rather than a
  fifth integer, and the letters come from kImexRoleTable.

Four further changes, from testing rather than the review:

- Deleting a mode reported only the row's current name, so renaming a mode and then
  deleting it left every plate using it stranded on a name that no longer exists.
  Both the build-time and current names are now reported, minus any a surviving row
  still carries.
- The instruction text and colour legend were built once in the constructor and
  never rebuilt, so raising gantry count to 2 gave the tiles a Span role the legend
  never explained until the preset was saved and the page reopened. Both are
  rebuilt with the grid, and the per-role detail moved into legend tooltips so the
  panel no longer opens with a paragraph.
- The tile holding Primary is now read-only. Primary is tool 0 and moves only via
  Tool 0 Position; a click could previously demote the only Primary, leaving a mode
  that parses to no primary at all, which degrades the plate to an ordinary
  single-tool print with nothing in the editor showing what is wrong. A mode
  arriving without a Primary keeps every tile live so it can still be repaired.
- Names and G-code were read with ToStdString() (the ANSI codepage on Windows) and
  written with from_u8() (UTF-8). On a non-UTF-8 codepage a name like "Modus A"
  with a diaeresis was stored as invalid UTF-8, came back blank, and was then
  silently renamed by the auto-naming above. Every read is now into_u8() and every
  write from_u8(); EditGCodeDialog was affected in both directions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:56:26 -04:00
Clifford GarwoodandClaude Opus 5 5b30af463d Reduce PartPlate's zone code to a wrapper and key its cache on the tool layout
calc_imex_zones() is now 69 lines: fetch the two edited configs, call
compute_imex_zone_layout(), store the result, and clip each returned rect to the
bed outline to build the GLModels. That last step is the only part that needs GUI
types, which is why it stayed. See the extraction commit for the behaviour-
preservation evidence.

refresh_imex_slice_offset() is deleted along with its call in
update_slice_context(); the offset is derived in the engine now, and it was
computing it in the plate-list world frame, which double-counted the plate origin
for every plate after the first.

Two smaller changes:

- The zone/ghost cache key omitted imex_tool_layout, which decides which physical
  corner tool 0 occupies and therefore moves every zone rectangle, collision strip
  and ghost offset while every other keyed field stays put. A layout change
  produced an identical key. That this currently appears to work is incidental --
  some other path happens to rebuild -- and not something to depend on. Found by
  building the preview's own cache key against this one.
- The tools-per-gantry fallback for a missing key was 1 in two places where
  PrintConfig registers 2 and the zone code uses 2. All four sites now agree; a
  missing key otherwise grouped tools against a grid divided a different way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:56:26 -04:00
Clifford GarwoodandClaude Opus 5 5ac1d05fcb Stop three IMEX changes from altering non-IMEX behaviour
None of these came from the review; they were found auditing the branch for the
same class of leak review comment 6 identified.

physical_extruder_map was normalised through effective_physical_extruder_map on
every Print::apply(), so any printer with more than one nozzle and no authored map
got the identity [0,1,...,n-1] where the single-element {0} default belongs. The
key carries two readings: the IMEX paths index it by logical extruder and need one
entry per extruder, while the inherited BBL paths read it through the clamping
get_at(), for which {0} means "everything is physical 0". Deriving unconditionally
imposed the IMEX reading on profiles that mean the other one, changing the config
block line and the {first_tools} / {first_filaments} / {curr_physical_extruder_id}
placeholders for multi-nozzle non-IMEX printers. Gated on is_imex; every consumer
needing the per-extruder form is already IMEX-gated, and profiles with an authored
map of the right length are unaffected either way.

That gating unmasked a latent out-of-bounds read: WipeTower's M104/M109 emitters
index m_physical_extruder_map by tool with no bounds check, which reads past the
end of the single-element default on a multi-nozzle machine. Upstream's bug, from
the BambuStudio wipe tower sync, previously hidden because the map was being
widened for everyone. Now bounds-checked, falling back to the tool's own index --
the form GCodeProcessor already uses for the same map.

Preset::save() and get_preset_differed_for_save() carried a branch storing the
full vector whenever a child and its parent had different lengths. It was written
against a set_with_nil that threw on mismatched sizes; upstream #13035 replaced
that with a tolerant version that keeps the child vector verbatim and nil-marks
only the overlapping range, and that fix was already in the tree when this branch
was rebased. Left in, it defeated the delta encoding for every user printer preset
whose extruder count differs from its parent's: a 7-extruder profile inheriting a
single-extruder base wrote out all of its per-variant retraction keys as literals,
including ones identical to the parent, pinning them against future vendor updates
while the UI still reported the preset as inheriting. Removed; the two save loops
are now identical to upstream. Note this only affects new saves -- presets already
written keep their frozen values until re-saved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:58 -04:00
Clifford GarwoodandClaude Opus 5 a5ba39393e Escape the IMEX plate attributes written into the 3MF
Closes review comment 1.

imex_parallel_mode and imex_head_filament_map were streamed raw into XML attribute
values, while every other free-text attribute in the same writer goes through
xml_escape. Mode names are free text, so "PLA & ABS", a quote or a "<" made the
document malformed. The failure is not a bad value on reload: both load paths for
model_settings.config return false on an expat error, and m_is_bbl_3mf is set
before the second entry loop runs, so the whole project fails to open with
"Archive does not contain a valid model config".

Both attributes now use xml_escape_double_quotes_attribute_value(), which also
emits tab, CR and LF as numeric character references. That matters and plain
xml_escape would not do: XML normalises literal whitespace in attribute values on
read, so a tab in a mode name would come back as a space and silently rename the
mode. The read side needs no change -- it takes expat's already-decoded value with
no second unescape -- so this is a lossless round trip and a file written by the
new code still loads in an older build.

The round-trip test used "copy_mode", which exercised none of this; it now carries
&, <, a quote and a tab, and also pins that ' and > come back unmodified, since
both are legal raw inside a double-quoted value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:58 -04:00
Clifford GarwoodandClaude Opus 5 5629dd29e9 Restore the pre-IMEX pressure advance output for non-IMEX printers
Closes review comment 6.

The per-tool pressure advance work changed set_pressure_advance() for users who
are not using the feature. RepRapFirmware lost its D qualifier when no tool index
was supplied: upstream emits M572 D0 S<pa> unconditionally, and a bare M572
applies to whatever tool is currently selected and errors when there is none, so
PA started depending on tool-selection state for every RRF user. The D is back,
defaulting to 0, and D<tool> is reached only from the IMEX paths.

The same rewrite had also changed the comment separator from "<value>; Override"
to "<value> ; Override" on the Klipper, RRF, Marlin 2.x and Marlin Legacy
branches, so every non-IMEX print of those flavors carried a one-byte diff.
Restored. Upstream is internally inconsistent here -- BBL and Repetier do use the
spaced form -- and the point is to match it exactly rather than to tidy it.

Emitted output for all six flavors with no tool index is now byte-identical to
upstream. Verified on a real slice: a Klipper profile emits
"SET_PRESSURE_ADVANCE ADVANCE=0.02; Override pressure advance value", an exact
string match, with no EXTRUDER= qualifier. The tests were pinning the regressed
form and are inverted.

Also records at the imex key registrations why they are kept out of the g-code
config block, matching the house convention at the other banned keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:35 -04:00
Clifford GarwoodandClaude Opus 5 612e0e3932 Fall back to Primary when an IMEX mode does not resolve, and keep imex keys out of the config block
Closes review comment 2, part of 18, and one non-IMEX regression the review did
not cover.

An imex_parallel_mode naming no entry in imex_mode_names still entered the
parallel branches. get_imex_active_tools() returned empty and the else was
skipped, so no head received its 1st-to-2nd layer temperature transition, and
imex_suppresses_bare_toolchange() still dropped the initial T<n> on the
expectation that a mode script would select the tool. validate() did not catch it
because its guard is declared_primary >= 0 and an unresolved mode yields -1. You
reach it by renaming a mode after a plate is set to it, or by opening a 3MF whose
printer preset names its modes differently. m_imex_parallel_mode is no longer
assigned before the lookup; a non-Primary name that matches no row now warns and
re-resolves against the Primary row, which is the fallback the PR description
already claimed. A mode that resolves to an empty tool roster takes the same path,
since the emitted G-code is wrong in the same way.

Warned rather than blocked: opening someone else's 3MF is a legitimate way to get
here and the Primary reading prints correctly, so refusing to slice would turn a
recoverable situation into a dead end. Silent was not an option either, because
the plate keeps showing the stale mode name while drawing no zones.

Separately, the 14 imex config keys all register non-nil defaults, so
append_full_config was emitting "; imex_* = <default>" into every exported
G-code, including on single-nozzle printers with nothing to do with the feature.
They are banned from the dump, matching the treatment already given to the
fast-purge, extruder-change and timelapse keys, so the config block is
byte-identical to the pre-IMEX baseline for the whole shipping fleet. Nothing
reads them back: GCodeProcessor has no imex reference, and the two per-plate keys
round-trip through the 3MF's model_settings.config on an independent path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:35 -04:00
Clifford GarwoodandClaude Opus 5 3629056831 Derive the IMEX slice offset in the engine
Closes review comment 11, and fixes a worse bug found while doing so.

The offset was only ever pushed from PartPlate::refresh_imex_slice_offset(),
reachable from update_slice_context() and the plater -- both GUI-only, and it
dereferences wxGetApp(). A headless slice therefore kept Vec2d::Zero(), so
orca-slicer --slice on a plate with imex_firmware_managed_zones emitted
slicer-managed coordinates while the firmware applied its own offsets on top.
Print::update_imex_slice_offset() now derives it from the applied config and runs
from process() and export_gcode(), so a CLI slice gets the value a GUI slice does.
It reads m_full_print_config rather than m_config because imex_tool_layout and
imex_carriage_margin are printer-preset options with no member in the static
PrintConfig, and it takes the mode from the same place GCode.cpp resolves it, so
the shift cannot disagree with the mode that is emitted.

The GUI push and Print::set_imex_slice_offset() are deleted rather than kept as an
override, because the two did not agree. calc_imex_zones() divides
get_extents(m_shape), and set_shape translates m_shape by the plate position, so
the pushed offset carried the plate origin -- which translate_to_print_space() and
the writer offset already subtract. Plate 1 sits at the origin and agreed by
accident; every later plate had the origin subtracted twice and was shifted by a
full plate stride. Not silent, either: the displaced geometry fell outside the
printable area, so slicing plate 2 failed validation with "part is off the plate".
Confirmed fixed on hardware profiles -- the same model on plates 1 and 2 now emits
identical extents.

Deleting the push also removes the post-apply ordering constraint that forced the
duplicate call in Plater::priv::update_background_process: the value is computed
at the point of use, and process()/export_gcode() are structurally after apply().

Also routes validate()'s primary-routing check through imex_resolve_routing() so
the hard block and the plater's warning cannot describe a plate differently
(review comment 20), and through find_imex_mode() for the mode lookup (18).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:35 -04:00
Clifford GarwoodandClaude Opus 5 fcfda6c835 Move the IMEX bed-zone geometry into libslic3r
Closes review comment 16, and fixes three defects the move exposed.

PartPlate::calc_imex_zones() was 392 lines deciding where every zone, collision
strip and safety margin sits, in the GUI layer, with no test coverage. The three
wxGetApp() calls that kept it there were all in its first 25 lines, fetching two
configs. The geometry now lives in compute_imex_zone_layout(); the wrapper fetches
the configs, calls it, and clips the returned rects to the bed outline for the
GLModels, which is the only part needing GUI types. libslic3r gained no wx
dependency: it takes DynamicPrintConfig directly, so the option lookups moved
verbatim rather than through a hand-written value struct that could drift.

The move is otherwise exact -- verified by a line-for-line diff of every
arithmetic expression against the original, and by running 15 scenarios through
the extracted code against hand-derived values. The only deletion is a lambda that
was never called.

Three fixes on top, each of which needed the code to be testable:

- An off-grid or absent Primary left pri_col/pri_row at their (0,0) initialisers
  and built a layout from them, reporting the whole bed as the clear primary zone
  and the whole bed as a blocked mirror zone at once; under
  imex_firmware_managed_zones it shifted the slice by the bed centre. Guarding on
  the resolved primary head covers both routes. Reachable only from a hand-edited
  preset or a 3MF authored against another printer -- the editor pins Primary to
  tool 0 -- but that is the same class the unresolved-mode fallback handles.
- imex_nozzle_clearance_x/y fell back to 0.0 where PrintConfig registers 30.0.
  Both strip loops are gated on the value being positive, so the fallback emitted
  no collision strips at all while the preview still drew 30 mm toolhead boxes.
- The collision-strip loop asked each mirror head for its own grid cell, but an
  aggregated gantry's cell is pinned to the primary's column and expanded into a
  full-width row strip. Where the representative's column differed from the
  primary's, no boundary matched and the plate came back with no strips and no
  margin bands -- an object flush against the shared boundary sliced without a
  warning while the far carriage occupied it. Present since Span aggregation was
  added in 4966d0fae8 and carried out of PartPlate verbatim. The flags now come
  from the painted cells; an exhaustive sweep of the reachable grid, role and
  layout space (1,630,720 configurations) shows the only behaviour change is the
  missing strips appearing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:04 -04:00
Clifford GarwoodandClaude Opus 5 171a721304 Harden the IMEX helper layer and give it one mode lookup
Closes review comments 3, 5, 7, 8, 18 and 19, plus the library half of 20.
These share a file, so they share a commit; each is independent of the others.

- 3: ::isspace(char) is undefined for bytes above 0x7F because char is signed on
  our targets. Three call sites now go through one strip_whitespace() using an
  unsigned char cast. Line 308 parses imex_head_filament_map straight out of 3MF
  metadata, so a non-ASCII byte reached it without passing through the UI.
- 5: an imex_head_filament_map override past the end of physical_extruder_map now
  falls through to the printer's own routing instead of resolving to a wrong
  filament. Bounded in resolve_filament_for_head, where the slot count is known,
  rather than at the parse site, which has no count to check against; the parse
  site also gains the absolute MAXIMUM_EXTRUDER_NUMBER cap its sibling already had.
- 7: imex_physical_heater_for's !is_imex early return is what keeps a stock BBL
  profile (physical_extruder_map [1,0]) out of the heater remap, and had no test.
  Six cases now cover it, pinning pass-through rather than get_at()'s clamp.
- 8: ImexRole::Span was missing from the imex_head_transform switch, so it warned
  under -Wswitch. Identity is correct, not merely convenient: a Span tool prints
  the primary's own zone through mid-print toolchanges and has no zone to be
  translated into.
- 18: three positionally coupled string vectors were resolved by nine open-coded
  lookups using three incompatible bounds idioms. None read out of bounds, but six
  folded the guard into the match condition, so a ragged row did not stop the scan
  and a later duplicate name could win. struct ImexMode + find_imex_mode() is now
  the only resolution rule: the names array is the roster, first match wins, a
  short sibling pads to empty and sets ragged, not-found is an explicit -1.
- 19: the letters P/C/M/S existed in three independent copies, one of which was the
  writer of the on-disk format. kImexRoleTable is now the single source, read by
  both parsers and the serializer. Adding a role was 14 edit sites with one
  compiler-enforced; it is now the enum, the table entry, and four -Wswitch
  switches. Verified by adding a fifth enumerator and recompiling: exactly four
  warnings, nothing else.
- 20: imex_resolve_routing() extracts the mode/primary/routing chain that
  Print::validate and the plater's warning collector each derived separately.

The three config keys keep their names, types and on-disk representation. This is
a read-side view only; presets and 3MF files are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 00:55:03 -04:00
Kris Austin e6501bb1ce build: stop rebuilding the Flatpak dependencies on every run (#15501) 2026-09-02 21:45:16 -03:00
53c26a5724 fix: save 3d mouse settings (#15397)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-09-02 20:21:27 -03:00
Rodrigo Faselli 3605614ee7 More Labels (#15511) 2026-09-02 19:06:01 -03:00
Kris Austin 8bf1d3ea84 fix: clear 12 warning sites that only appear away from Windows (#15437) 2026-09-02 18:18:17 -03:00
Kiss Lorand 51fd6327fe Fix omitted assembly parts with height ranges (#15499) 2026-09-02 18:15:18 -03:00
Ian Bassi b81c0e30c1 Localization update (#15497)
* gettext

* AI Translated

* Update OrcaSlicer_es.po
2026-09-02 10:27:18 -03:00
Alexandre Folle de Menezes 640fd9f454 Fix misc. errors on GUI strings (#15468)
* Fix misc. errors on GUI strings

* Adding context to single-letter unit strings

* Fix duplicate strings on po files
2026-09-02 09:36:54 -03:00
Kris AustinandRodrigo Faselli 1749c293a6 build: clear 237 warnings - unused lambda captures (#15417)
* build: enable /Zc:lambda for MSVC

MSVC keeps its legacy lambda processor under /std:c++17, which rejects
reading a constexpr constant inside a lambda that does not capture it
(C3493). No other compiler requires that capture, and clang reports it as
an unused one, so the two cannot both be satisfied without the flag.

/Zc:lambda selects the conforming lambda parser that clang and GCC
already use. It is implied by /std:c++20 and /permissive-, so it is only
needed while we are on C++17. clang-cl is conforming already and does not
take the flag.

It requires VS2019 16.8, so build_release_vs.bat now says 16.8+.

* build: clear 237 unused lambda capture warnings

236 captures across 81 files, 142 of them `this`. Removing an unused
capture changes no behavior; clang does not report a capture whose type
has a non-trivial destructor, so nothing held only to extend an object's
lifetime is in this set.

Nine of them are the second half of the warning, "is not required to be
captured for this use", where the capture is a const or constexpr value
the body does read. Those depend on the /Zc:lambda change in the previous
commit. One of them, in FillRectilinear.cpp, had been worked around with
an #ifndef __APPLE__ guard around the capture list, which is now gone.

GUI_ObjectTableSettings.cpp captured its reset button only to read it
inside #ifdef __WXOSX_MAC__. That branch now takes the button from the
event it is already handling.

* build: fail configure on MSVC older than 19.28 instead of dropping /Zc:lambda

cl.exe answers an unrecognized /Zc: sub-option with warning D9002 and keeps
going, so on VS2019 before 16.8 the flag is silently ignored and the build
instead dies with C3493 in FillRectilinear.cpp, nowhere near the cause.

* fix: delete three locals that are now unused

Their only remaining use was the lambda capture this branch removed. The
Clang builds set -Wno-unused-variable, so the build never flagged them.

---------

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-09-02 07:38:05 -03:00
SoftFever e523acc164 Add script to run full profile checks locally (#15496)
* Run the CI profile checks locally
2026-09-02 15:22:55 +08:00
CliffordandClaude Opus 5 b6ef6cf1be fix: out-of-bounds write migrating per-variant values when switching printers (#15456)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 19:29:11 -03:00
Kiss Lorand e8115658e0 Fix overhang fan control when overhang slowdown is enabled (#15158) 2026-09-01 18:07:02 -03:00
weng haishiandIan Bassi 36228c4755 fix: prevent heap corruption when repairing models with auto-backup (#15395)
* fix: prevent heap corruption in model repair with auto-backup

The CGAL model repair (fix_model_with_cgal_gui) runs on a worker thread
that mutates the live ModelObject (split / delete_volume / set_mesh).
Those mutators transitively call save_object_mesh(), which hands the
object to the auto-backup manager. The manager clones and serializes the
object on its own thread via an internal Model documented as "visit only
in main thread". Running that path from the repair worker races the
backup thread on the shared model, causing use-after-free / heap
corruption -- EXC_BAD_ACCESS and libmalloc "corruption of free block"
aborts, always with the "cgal_fix_model" worker on the stack inside
add_object_mesh -> Model::add_object / delete_object.

Wrap the repair in a SaveObjectGaurd so the backup manager ignores the
object for the duration of the repair; a single backup is taken when the
guard is released on the main thread after the worker joins. This mirrors
existing batch-edit usage of SaveObjectGaurd (Model.hpp, GUI_ObjectList).

Repro: repair a multi-part / splittable object with auto-backup enabled
(Preferences > Backup); crashed within a few repairs on macOS arm64.

* Update FixModelByCgal.cpp

---------

Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2026-09-01 16:39:12 -03:00
Ian Bassi e108ddfd56 Fix wrong JA jerk translation. (#15488)
Update OrcaSlicer_ja.po
2026-09-01 15:37:08 -03:00
Thomas 918d3914ba Fixed "jerk" translation inconsistency. (#15463)
* Fixed "jerk" translation inconsistency.

* Fixed an other "jerk" translation.
2026-09-01 15:05:10 -03:00
21aa07b9cc build: add build_win.bat, a Windows build script for deps, slicer and toolchain setup (#15436)
* build: add build_win.bat, a Windows build script for deps, slicer and toolchain setup

build_release_vs.bat takes no options: what it builds is decided by editing
it. This adds build_win.bat alongside it, with short and long options, a
grouped help message, a dry-run mode that prints every command instead of
running it, and one option per thing a developer actually varies - the
configuration, the architecture, the compiler, the generator, the Visual
Studio release, how much gets rebuilt, and where the dependency tree lives.

It works from any directory, needs no developer command prompt in either
generator mode, and keeps CMake ahead of Strawberry Perl on PATH so a build
does not depend on how the user ordered their environment.

scripts/test_build_win.ps1 covers it with table-driven cases that run the
script under --dry-run and assert on the commands it prints, so nothing is
configured or built. The Windows build jobs wait on that suite.

Based on the script from OrcaSlicer#11097.

Co-authored-by: Ocraftyone <24759591+Ocraftyone@users.noreply.github.com>

* build: report what build_win.bat produced and what to do next

Every successful run now ends with a block naming what it built and the
commands to carry on with. Those commands repeat the flags that reproduce
the run, so a rebuild after a clang-cl Ninja build is not silently an MSVC
one. A failure gets a framed block naming the command that failed and a
retry scoped to the stage that failed, so a slicer error does not suggest
discarding an untouched dependency tree.

Installing is now opt-in behind -i. The install tree is a second full copy
of the build that exists mainly so the release can be zipped from it, while
the build tree is already runnable, with the DLLs beside the binary and
resources symlinked rather than copied.

Configuring against a dependency tree that was never built now names it
instead of failing several hundred lines into CMake's package resolution.

scripts/test_build_win.ps1 covers all of it, and gains -Name so one case
can be run without the full pass.

* build: let the test options stand alone, and say which build things apply to

--run-tests named two things to do and then did neither without -s, so
`build_win.bat -lx --run-tests` answered "Nothing to do". Both test
options now imply the slicer build they cannot happen without, unless
another action was already named, so -d --tests is still a dependency
build. --install-vs has turned on --install-deps the same way all along.

That makes them actions, so they move to the group that says so. -i goes
the other way, to the step toggles beside --no-configure and --no-gettext,
since it does not stand alone and adds a step rather than describing what
kind of build to make. An example shows the tests run with toolchain
flags, because flags pick which build gets tested and a bare --run-tests
would build and test a default tree the developer never asked for.

Two help lines named defaults that were not the defaults. --build-dir said
"instead of build/" and --deps-dir said "instead of deps/", but trees are
named for the configuration, compiler and architecture, so build/ is only
the default for a release x64 MSVC build, and deps/ is the source
directory rather than a tree anything is built in.

The hint for a missing dependency tree now carries the flags that
reproduce the run. It said "Build them with -d", which after a clang build
points at the MSVC tree, so following it left you no better off. Every
other suggestion the script makes already repeats them.

-k counted on the developer to read taskkill invocations as progress. It
now names each image and how many processes it is about to stop, which is
what explains the pause, and skips the ones that are not running instead
of printing taskkill's "not found" as though something had gone wrong. No
image can stop the rest.

The environment example set SLIC3R_ASAN, which -a already does, teaching
the long way round to a flag the script owns. It now sets options that
have no flag. The note under it said "Use these for a value containing
spaces. Ampersands are not supported", which named neither what "these"
were an alternative to nor where ampersands were a problem.

The test harness gains a NotExists field, because output cannot show what
a run did not create, and two cases needed to prove exactly that.

---------

Co-authored-by: Ocraftyone <24759591+Ocraftyone@users.noreply.github.com>
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-09-01 15:02:19 -03:00
ndrwrbgsandIan Bassi ec06c8788d Minor display text fixes (#15454)
* Minor display text fixes

* Update in translations

---------

Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2026-09-01 14:55:16 -03:00
Mikhail f. Shiryaev 96e85b1db5 Fix build with disabled precompiled headers (#15484)
Fix broken build with disabled PCH
2026-09-01 14:21:38 -03:00
Kris Austin 7eaff76cec build: use clang-cl for Windows CI builds (faster slicing and faster builds) (#15428)
* ci: build the Windows x64 dependencies and slicer with clang-cl

Passes -l -x from the Windows jobs, using the clang-cl and Ninja options
added in #15373. Ninja runs each dependency's build step as a plain command,
so the jobs set up a VC environment for OpenSSL's nmake. arm64 stays on MSVC
for now.

The deps cache key gains the compiler, so windows-x64 becomes
windows-x64-clang and windows-arm64 becomes windows-arm64-msvc.

* deps: select OpenSSL's ARM64 target from DEPS_ARCH

CMAKE_GENERATOR_PLATFORM is only set by -A, which Ninja never receives, so
the Ninja build selected the x64 target on ARM64. DEPS_ARCH is derived from
CMAKE_SYSTEM_PROCESSOR and is already independent of the generator.

* ci: build the Windows ARM64 dependencies and slicer with clang-cl

Three dependencies need handling first. libpng and OpenCV each build an ARM
SIMD path that does not compile with clang-cl, so those paths are off; PNG
already had the same opt-out for Apple ARM. OCCT is built with cl, since
clang-cl cannot emit one of its large generated files and there is no option
to turn that off. All three are gated to Windows ARM64 with clang.
2026-09-01 10:30:45 -03:00
Kris Austin 9933cab59f build: drop the pkg-config requirement from the Windows build (#15469)
The FFmpeg camera view port made pkg-config a required build tool on
Windows. Windows does not ship one, so every Windows developer has to
install it before the build will configure:

  Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
  Call Stack (most recent call first):
    CMakeLists.txt:480 (find_package)

Nothing on Windows needs it. FFmpeg there is a prebuilt zip unpacked
into the deps prefix, whose DLLs the top level CMakeLists already names
by exact soname. The version is fixed before configure runs, so
find_library against that prefix does the job, as on macOS.

Also drops the CI step that installed pkg-config, gated on !SELF_HOSTED
so it never ran on self-hosted runners, and re-comments the if(WIN32)
block that #15234 uncommented only for that find_package.
2026-09-01 09:19:58 +08:00
Kris Austin 5436e422b9 ci: build macOS with 3 parallel jobs instead of 1 (#15393) 2026-08-31 16:59:58 -03:00
Leo Lobato ca903faa56 Fix uninitialized first_layer_time on CLI-sliced 3MF (#13429)
PartPlate::store_to_3mf_structure read first_layer_time from the indirect cali_bboxes_data struct,
which the GUI populates at Plater.cpp:10600 but the CLI never writes to. The result was uninitialized
memory leaking into slice_info.config

Read directly from get_slice_result()->initial_layer_time, which is populated by
GCodeProcessor::finalize() in both code paths and matches the pattern already used a few lines
above for gcode_prediction.

Also default-initialize PlateBBoxData::first_layer_time to 0.0f as a defense against any other consumer
reading it without an explicit write.
2026-08-31 16:36:55 -03:00
Kris Austin fcf6f0a3a5 build: clear 54 warnings - dead private fields (#15423)
build: clear 54 dead private fields

54 of the 161 -Wunused-private-field warnings, across 31 files. These are
the ones needing no judgment. Each member is declared once and appears
nowhere else in src/, counting the .mm and .c sources as well as .cpp and
.hpp, so nothing writes them and nothing reads them. Every removal is a
whole line, and no declaration shares a line with another member.

The remaining 107 are left alone. Those members are mentioned elsewhere,
usually assigned and never read, where the fix might be deleting the
member or might be restoring a read that went missing.
2026-08-31 16:36:52 -03:00
Kris Austin 36740ffdd8 build: clear 53 warnings - discarded values and i18n markers (#15421)
build: clear 53 unused value warnings

49 of them are deliberate i18n markers. L(s) expands to s, so
L("Main Extruder"); is a string literal as a statement and its value is
discarded. The strings have to stay, because the real values come from
printers/*.json at runtime and xgettext cannot scan those. Each block is
now a static const char *const markers[], which uses the values rather
than discarding them. Extraction is unchanged: the xgettext invocation
from scripts/run_gettext.bat gives 76 msgids over the two marker files
before and after, with identical msgid and msgctxt sets.

The other 4 are statements with no effect. AMSItem.cpp:117 and :174
construct and drop a wxColour(255, 255, 255); AMS_TRAY_DEFAULT_COL is
that colour, and the line above already assigns it. UpgradePanel.cpp:865
reads a member and drops it.

wgtDeviceNozzleSelect.cpp:269 writes
if (item; auto ptr = m_nozzle_rack.lock()), which puts the null check in
the init-statement position where its value is discarded, so the check
never runs, and sGetNozzlePosId then dereferences item. Nothing reaches
that today, because the only sender of the event sets itself as the
event object and the dynamic_cast always succeeds. The check now runs.
2026-08-31 10:55:42 -03:00
Ian Chua 27e99ca713 feat: initial plugin auditing workflow (#14989)
# Description

This is an initial draft of the plugin audit workflow.

It focuses on the user experience and developer-facing permission
workflow. It does not yet include the complete implementation of every
operation that should be audited, such as the full filesystem,
networking, and process-spawning event coverage.

## User workflow

When a plugin is loaded:
1. The plugin’s register_capabilities() function is executed.
2. The plugin declares the permissions it requires.
3. OrcaSlicer displays a permission dialog listing the requested
resources.
4. If the user grants access:
  - The permission is persisted in the plugin’s .install_state.json.
  - Capability registration continues.
  - The plugin is materialized and loaded.
5. If the user denies access:
    - Plugin loading fails before capabilities are materialized.
    - on_load() is not called.
- The plugin’s install state is marked with "enabled": false to prevent
repeated automatic load attempts.

At runtime, if a plugin accesses a resource that was not approved during
loading, the audit hook displays another permission dialog. For
filesystem requests, the dialog identifies the requested filepath.
  - Granting access persists the permission and allows the operation.
  - Denying access raises a Python PermissionError.
- The error propagates to the host, which records the failure and
unloads the plugin.

Host-side traceback logging is performed outside the plugin audit
context so that logging does not generate additional permission dialogs.

## Developer-facing API

Plugins can declare filesystem read permissions through the new API:
```python

import orca
AUDIT_PATH = __file__


@orca.plugin
class ExamplePackage(orca.base):
   def register_capabilities(self):
        orca.request_permissions(
            fs_read=[AUDIT_PATH],
        )
        orca.register_capability(ExampleCapability)
```
orca.request_permissions() must be called from register_capabilities()
while the plugin is being loaded.

Currently supported permission:
orca.request_permissions(fs_read=[...])

The paths should be explicit filesystem paths that the plugin intends to
read. The host deduplicates repeated paths, presents the request after
registration completes, and persists granted paths in the plugin
install-state sidecar. This API is still experimental, and is by no
means the final implementation.

Support for additional permission categories, including filesystem write
access, networking, and process spawning, is reserved for subsequent
work.

# Screenshots/Recordings/Graphs

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

<img width="869" height="799" alt="image"
src="https://github.com/user-attachments/assets/8a5903cc-0cbb-45a8-b88a-706d6cba790f"
/>


## Tests

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

<!--
> 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-08-31 14:29:36 +08:00
Clifford GarwoodandClaude Opus 5 855c1b51ac fix: size the destination row before migrating per-variant values
update_values_from_multi_to_multi_2 iterates the destination PRINTER's variant
list while writing into a row taken from the destination PRINT preset. Those two
lengths are maintained independently -- print_extruder_variant against
printer_extruder_variant -- and Tab::load_current_preset() runs the migration
before the print preset is re-selected for the new printer. Opening a project
saved on a single-variant printer and switching to a seven-variant one therefore
wrote six elements past the end of a one-element vector. The corruption stays
silent until the next allocation, so the abort surfaces somewhere unrelated and
the backtrace points at innocent code.

Size the row to the variant count before indexing it. Every write is then in
range, and the result carries one value per destination variant, which is what
the callers consume. Pad with nil rather than a copied value: set_to_index()
skips nil entries, so a variant the object has no opinion about keeps tracking
the print preset instead of being pinned to another variant's number.

The same shape -- a count from one array indexing another -- appears twice more
in this file. update_values_from_multi_to_multi has three of these writes
protected only by assert(idx < old_count), and NDEBUG is defined for every
non-Debug configuration, so those guards are absent from shipping builds.
update_values_from_single_to_multi has the read half. Both are bounded here;
leaving them would fix one third of one defect.

Source reads are bounded too. is_nil(size_t) indexes values[idx] without
checking, so an index past the end was undefined behaviour on that side as well.

Where the row already matches the variant list -- every case that was not
corrupting the heap -- the resize is a no-op and the output is unchanged.

Fixes #15455

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 01:47:45 -04:00
Clifford Garwood 9abc3bcaf7 Merge upstream main: mixed-filament extruder-count fix, FFmpeg camera view, plugin storage API, warning cleanups 2026-08-30 23:58:48 -04:00
56a452875e Port FFmpeg based camera view from BambuStudio (#15234)
* Add ffmepg dep

* NEW: reimpl wxMediaCtrl from ffmpeg

Jira: none
Change-Id: I46a47118a7649b2a50fcce8911e2888342ef25de
(cherry picked from commit d6c7f08769c8cfdbbf0e80ad280c9b3408a3c27d)
(cherry picked from commit 94d91be60bfe9bbbcdd21f85b46abc3faf126f17)

* FIX: reset bambu lib after restart network plugin

Change-Id: I4a3a4b7420745835ca3fa00c6edebe9d8d98cbf6
Jira: STUDIO-7571
(cherry picked from commit 28d9c6743fae80bfd40e4ee391e30d62cb16d4ab)

* FIX: ffmpeg decoder memory leak

Change-Id: I997572b5730618a969959f9b24c405d80fa9f83c
Jira: STUDIO-7597
(cherry picked from commit 342cea29bd9593fa89cbb33caff58055b46ebeec)

* FIX: install ffmpeg symbolic sos

Change-Id: Ia4a45182cefcf62a7a4b4a5c89c92251609c5a68
Jira: none
(cherry picked from commit b7f8fa1efdbe0ac2cc896ca24f063f5894fe9f90)

* FIX: ffmpeg swscale & frame_size

Change-Id: I9f4cb8c739b726f7e5cdbe0df7ed06b2eb2154d5
Jira: STUDIO-7624
(cherry picked from commit 5a2c75d835fb437667b590a803eef148baa30875)

* FIX: wxMediaCtrl3 idle image & center pos

Change-Id: Ib9652573e31bfd6229f174c0a1388942d9d98822
Jira: STUDIO-7633
(cherry picked from commit d51247c46e26460b151de79c598d81151280e79c)

* FIX: AVVideoDecoder sws_ctx_ == nullptr on zero size

Change-Id: I9698354bb1f341e276ec9780d4ef4fcd9f8a1028
Jira: STUDIO-7706
(cherry picked from commit ff622e25026a8471c39eb308cf5b115c4a9d84aa)

* fix:cannot open shared object file on linux

Change-Id: Ica66500506cfe8932eac3ae0a58fb7ff30d1da9b
jira:none
(cherry picked from commit febd1aeb4d453bc96571fa5e5727e9e10046cb80)
(cherry picked from commit 5ad579f929154779abd84b01438fd235c647dbf5)

* NEW:add ffmepg build Cmake

buildLinuxImage add ffmpeg so file

jira:nojira

Change-Id: I3e1be53aa58a179b8d9ae048ed7538de3ae8d111
(cherry picked from commit 2d70a1bcb6a5ba601525b08a38e7610f018fe106)

* FIX: ffmpeg cmake install error

jira:nojira

Change-Id: I74cc0f7c86b5364e55cad2af2bd9a82306ee6864
(cherry picked from commit 805df79e3bb044dac29ec1c06736751ccf3675f9)

* FIX: decode video to wxImage on Linux

Change-Id: I5e332a1b0622b3dfc70ac5c4c3bfa62b3411ebdc
Jira: none
(cherry picked from commit c787ba921a31f259e8eb23fd59f178e96279caf9)

* FIX: wxMediaCtrl3 enter Stopped state soon

Change-Id: I120e9d4b9f85599a184650d1d95fe2bec42af171
Jira: STUDIO-8280
(cherry picked from commit 7648d96305d510b9e97f22124961de5115cde830)

* FIX: reset decode buffer zero when scale width changed

Change-Id: Iaa2f99111dd5f7228b7b25e1be0a8cbdbfe982a6
Jira: STUDIO-8422
(cherry picked from commit 659ebc7d07a8f6045ba5443141b44277d7257cec)

* slic3r: Fix missing declarations in wxMediaCtrl3.h

src/slic3r/GUI/wxMediaCtrl3.h:80:10: error: ‘condition_variable’ in namespace ‘std’ does not name a type
   80 |     std::condition_variable m_cond;
      |          ^~~~~~~~~~~~~~~~~~
src/slic3r/GUI/wxMediaCtrl3.h:27:1: note: ‘std::condition_variable’ is defined in header ‘<condition_variable>’; did you forget to ‘#include <condition_variable>’?
   26 | #include "Printer/BambuTunnel.h"
  +++ |+#include <condition_variable>
   27 |
src/slic3r/GUI/wxMediaCtrl3.h:81:10: error: ‘thread’ in namespace ‘std’ does not name a type
   81 |     std::thread m_thread;
      |          ^~~~~~
src/slic3r/GUI/wxMediaCtrl3.h:27:1: note: ‘std::thread’ is defined in header ‘<thread>’; did you forget to ‘#include <thread>’?
   26 | #include "Printer/BambuTunnel.h"
  +++ |+#include <thread>
   27 |

In file included from src/slic3r/GUI/MediaPlayCtrl.h:17,
                 from src/slic3r/GUI/MediaPlayCtrl.cpp:1:
src/slic3r/GUI/wxMediaCtrl3.h:77:13: error: field ‘m_frame’ has incomplete type ‘wxImage’
   77 |     wxImage m_frame;
      |             ^~~~~~~

(cherry picked from commit 727a73333bd67acf5ff2b1c51ff284c2bacdb413)

* slic3r: Fix missing includes in AVVideoDecoder

In file included from src/slic3r/GUI/AVVideoDecoder.cpp:1:
src/slic3r/GUI/AVVideoDecoder.hpp:28:20: error: ‘wxImage’ has not been declared
   28 |     bool toWxImage(wxImage &image, wxSize const &size);
      |                    ^~~~~~~
src/slic3r/GUI/AVVideoDecoder.hpp:28:36: error: ‘wxSize’ has not been declared
   28 |     bool toWxImage(wxImage &image, wxSize const &size);
      |                                    ^~~~~~
src/slic3r/GUI/AVVideoDecoder.hpp:38:10: error: ‘vector’ in namespace ‘std’ does not name a template type
   38 |     std::vector<uint8_t> bits_;
      |          ^~~~~~
src/slic3r/GUI/AVVideoDecoder.hpp:9:1: note: ‘std::vector’ is defined in header ‘<vector>’; did you forget to ‘#include <vector>’?
    8 |     #include <libswscale/swscale.h>
  +++ |+#include <vector>
    9 | }

src/slic3r/GUI/AVVideoDecoder.cpp:145:89: error: invalid use of incomplete type ‘class wxBitmap’
  145 |     bitmap = wxBitmap((char const *) bits_.data(), size.GetWidth(), size.GetHeight(), 32);
      |                                                                                         ^

(cherry picked from commit 781ce14e061366da64fdc2d0d592fa35ee57e67e)

* slic3r: Fix missing includes in wxMediaCtrl2

src/slic3r/GUI/wxMediaCtrl2.cpp: In lambda function:
src/slic3r/GUI/wxMediaCtrl2.cpp:170:13: error: ‘wxMessageBox’ was not declared in this scope; did you mean ‘wxInfoMessageBox’?
  170 |             wxMessageBox(_L("Your system is missing H.264 codecs for GStreamer, which are required to play video.  (Try installing the gstreamer1.0-plugins-bad or gstreamer1.0-libav packages, then restart Bambu Studio?)"), _L("Error"), wxOK);
      |             ^~~~~~~~~~~~
      |             wxInfoMessageBox
src/slic3r/GUI/wxMediaCtrl2.cpp: In member function ‘void wxMediaCtrl2::Load(wxURI)’:
src/slic3r/GUI/wxMediaCtrl2.cpp:179:5: error: ‘wxLog’ has not been declared
  179 |     wxLog::EnableLogging(false);
      |     ^~~~~

(cherry picked from commit 73908d38d8b1f7c8dcae92d55711bc08cbfff23c)

* slic3r: Fix missing wxPaintDC declaration

src/slic3r/GUI/wxMediaCtrl3.cpp: In member function ‘void wxMediaCtrl3::paintEvent(wxPaintEvent&)’:
src/slic3r/GUI/wxMediaCtrl3.cpp:121:5: error: ‘wxPaintDC’ was not declared in this scope; did you mean ‘wxPoint’?
  121 |     wxPaintDC dc(this);
      |     ^~~~~~~~~
      |     wxPoint

(cherry picked from commit 9ab5009235d212699f91e01d7f930f92849ed1e3)

* slic3r: Fix missing BOOST_LOG_TRIVIAL declaration

src/slic3r/GUI/wxMediaCtrl3.cpp:181:23: error: ‘info’ was not declared in this scope
  181 |     BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
      |                       ^~~~
src/slic3r/GUI/wxMediaCtrl3.cpp:181:5: error: ‘BOOST_LOG_TRIVIAL’ was not declared in this scope
  181 |     BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
      |     ^~~~~~~~~~~~~~~~~

(cherry picked from commit c5c41e20ca2fc7f3b53a4c769961f73df6992008)

* FIX: wxMediaCtrl3 zero size crash

Change-Id: I16a3f7b3afe142bb957a1740b8e8c9820c92b349
Jira: STUDIO-8522
(cherry picked from commit 8cdaea1162ccbcc0bd03ecd99346f3b9cf52cf64)

* FIX: TabCtrl button margin

Change-Id: If8b05a4ef9efb8b57989ee1de6543631e5a3cf90
Jira: STUDIO-8265
(cherry picked from commit 1c5e65707109ad0582b6442cf8e515344f799c27)

* ENH: wxMediaCtrl3 display video frame at pts

Change-Id: I8847236d2307101e5f2befc6477cd20b3691841c
Jira: none
(cherry picked from commit 05328da4612c11d50f6fd90e872b97f5f8f46b1d)

* Fix: fix memory leak caused by ffmpeg decoding

Change-Id: I162ad4ea8d4601c1ffe17a65f292566c9dea6f0b
jira: no-jira
(cherry picked from commit eb20d03186c86b7398b97e3bae0a3c7a7b81c58c)

* ENH: update some missing codes

jira: no-jira
Change-Id: Icb2da53911430ac144b0fb601637a7ad31e7e8db
(cherry picked from commit 13b4213f8a24c76c16e49daf905fa29c0f646a5a)

* Fix build

* Update idle image

* Attempt to fix Windows CI build

* FIX: GTK video window resize ran in a free function without member access

wxMediaCtrl_OnSize referenced wxMediaCtrl2's private m_gtk_video_window,
which does not compile on Linux/GTK. Move the resizing into
wxMediaCtrl2::DoSetSize where the member is in scope.

* Install required tools for Linux

* Install required tools for macOS

* Add ffmpeg to flatpak

* Fix Linux build

* Try fix appimage build

* Fix Linux AppImage bundling of deps-built shared libraries

The AppImage dependency closure resolves each bundled ELF's DT_NEEDED
entries with plain ldd, which cannot resolve the deps-built FFmpeg stack
(libavcodec/libavutil/libswscale) once it is copied into the bundle:
those libs are not installed in any standard loader path and carry no
RUNPATH of their own, so ldd reports the siblings as missing and the
build aborts. Extend the loader path with the bundle directory plus the
source directories of already-bundled files (mirroring
scripts/check_appimage_libs.sh), and key the dedup set on the bundled
file path instead of the source path so dependencies resolved from the
bundle directory are not copied onto themselves.

Co-Authored-By: Claude <noreply@anthropic.com>

* Attempt to fix Linux unit test

* Fix Linux unit tests loading deps-built FFmpeg libraries

The test executables that link libslic3r_gui (which links PkgConfig::LIBAV)
have a load-time dependency on the deps-built FFmpeg shared libraries. The CI
unit-test runner only receives the tests artifact, so those libraries were
unresolvable there (Ubuntu 24.04 ships libavcodec.so.60, not .61). Copy the
libraries next to each affected test executable and give it an $ORIGIN rpath,
mirroring the Windows branch that copies DLLs next to every test executable.
orcaslicer_copy_sos now places the copies in the per-config output directory
for multi-config generators, like orcaslicer_copy_dlls does.

Co-Authored-By: Claude <noreply@anthropic.com>

* Add design doc for macOS FFmpeg player

Co-Authored-By: Claude <noreply@anthropic.com>

* Add implementation plan for macOS FFmpeg player

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: use FFmpeg media player on macOS with static FFmpeg

* build: build static-only FFmpeg for macOS deps

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: remove old BambuPlayer-based media player from macOS

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add static FFmpeg NOTFOUND guard; drop dead wxMediaCtrl2.h include

* build: drop redundant --enable-shared in FFmpeg deps configure

The literal --enable-shared was always overridden by ${_link_cmd}
(--enable-static --disable-shared on Apple, --enable-shared elsewhere)
and FFmpeg configure processes these flags in order, last one wins.
Remove it and the stale comment documenting the workaround.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: remove dead wxMediaCtrl2 player

wxMediaCtrl2 was never instantiated on any platform (USE_WX_MEDIA_CTRL_2
is 0 everywhere); wxMediaCtrl3 replaced it. Delete wxMediaCtrl2.cpp/h,
drop them from the Win/Linux source list and the gettext list.txt, and
collapse the preprocessor-dead #if USE_WX_MEDIA_CTRL_2 gate in
MediaPlayCtrl.h.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: remove dead GStreamer bambusrc plugin and its build dep

gstbambusrc was the GStreamer source element for the old wxMediaCtrl2
Wayland player, its only consumer (deleted in the previous commit).
The new FFmpeg player handles bambu:/// URIs through the Bambu C API
instead. Drop the plugin and the gstreamer-1.0 / gstreamer-base-1.0
REQUIRED pkg-config dependencies that existed solely for it.

Co-Authored-By: Claude <noreply@anthropic.com>

* build: move FFmpeg media player sources to the common GUI list

wxMediaCtrl3 and AVVideoDecoder are platform-neutral C++ compiled on
all three platforms, so list them once in the common SLIC3R_GUI_SOURCES
instead of duplicating them in the APPLE and non-APPLE branches. The
else() branch is now empty and drops out entirely.

Co-Authored-By: Claude <noreply@anthropic.com>

* deps: disable FFmpeg VideoToolbox/AudioToolbox HW-accel on macOS

The static libavcodec.a/avutil.a compiled the auto-detected
videotoolbox/audiotoolbox objects, which reference VideoToolbox
framework symbols (_VTDecompressionSession*). The app link line
happened to satisfy them transitively, but the orca_stubgen module
link (CI-only) failed with undefined symbols. The player decodes in
software (swscale), so disable both HW-accel paths to keep the
static libs self-contained.

Co-Authored-By: Claude <noreply@anthropic.com>

* Copy the decoded frame instead of aliasing the decoder's buffer

wxImage with static_data set stores the pointer and never copies it, so the
frame handed to wxMediaCtrl3 aliased AVVideoDecoder::bits_. That buffer is
rewritten by the next sws_scale with the mutex released, reallocated by
bits_.resize() when the window grows, and freed outright when the decoder
leaves PlayThread's loop body at end of stream, all while the GUI thread may
be painting from it.

Windows is unaffected either way, since toWxBitmap already copies the bits
into GDI.

---------

Co-authored-by: chunmao.guo <chunmao.guo@bambulab.com>
Co-authored-by: BBL\chuan.he <chuan.he@bambulab.com>
Co-authored-by: MackBambu <yongfang.bian@bambulab.com>
Co-authored-by: Bastien Nocera <hadess@hadess.net>
Co-authored-by: chao.zhang <chao.zhang@bambulab.com>
Co-authored-by: lane.wei <lane.wei@bambulab.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-08-30 11:35:24 +08:00