Commit Graph
100 Commits
Author SHA1 Message Date
SoftFever c2f88b17be Complete the plugin settings removal after the feat/plugin-feature merge
The merge kept this branch's PluginConfig design, which deletes
PluginDescriptor::settings, get_plugin_settings() and ctx.params, but left
references to them behind: the slic3rutils target did not build, and the
bindings test still asserted the removed ctx.params attribute.

Port the two settings tests onto PluginConfig instead of dropping them. They
guard a field bug where a cloud-metadata refresh wiped a plugin's settings and
it silently ran on its own defaults, so the equivalent properties are still
worth pinning: that a stored config survives the refresh, and that an edited
config reaches the plugin through a real dispatch.

Also defer PluginsConfigDialog's web commands off the webview script-message
callback, as PluginsDialog already does. Its remove_preset_override handler put
a modal wxMessageBox on that stack, which is the GTK crash class fixed in
b779a7bfed/f2ccbfc8b5 for the sibling dialog.
2026-07-17 16:09:19 +08:00
SoftFever b663f851fe Merge branch 'main' into feat/plugin-feature 2026-07-17 11:30:34 +08:00
SoftFever 1c9fda463b Fix plugin settings lost on cloud metadata refresh
Cloud catalog records never carry [tool.orcaslicer.plugin.settings], so the
metadata merge wiped the locally-parsed settings and plugins silently ran on
their built-in defaults (ctx.params arrived empty).
2026-07-17 02:09:34 +08:00
SoftFever f7145c9b37 Update Maschine G-Code for X2D [Update Bambu Lab X2D 0.4 nozzle.json] (#14778)
Update Maschine G-Code according to latest Bambu Studio Version X2D
filament_change gcode: 2026/07/01
X2D layer_change gcode: 2026/07/01
X2D start gcode: 2026/06/05
X2D timelapse gcode: 2026/06/03

# 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?
-->

# 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.
-->

<!--
> 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-07-16 23:43:59 +08:00
SoftFever a4d69962fc Merge branch 'main' into patch-4 2026-07-16 23:19:58 +08:00
SoftFever 0a0d59b76b Fix plugins dialog hiding behind the main window on macOS 2026-07-16 22:36:31 +08:00
SoftFever 6f30d03e28 fix test errors 2026-07-16 21:40:21 +08:00
SoftFever f2ccbfc8b5 Open the plugin terminal off the webview callback stack
open_terminal_dialog is reached from the plugins dialog's webview
command, and TerminalDialog hosts a webview of its own — same class as
the plugin-window crash. Defer the window work via CallAfter, guard the
re-front Show() per #13657, and drop the redundant Raise() on creation.
2026-07-16 21:25:30 +08:00
SoftFever b779a7bfed Create plugin windows off the webview callback stack
wx 3.3.2 delivers webview script messages synchronously inside the native
callback on GTK and macOS, so script plugins run with the plugins-dialog
webview's signal/delegate frame on the stack. Creating and presenting the
orca.host.ui window from there crashed on Linux at Raise() --
gtk_window_present while GTK's deferred show was still in flight.

Defer the whole window creation to a CallAfter with a pre-bound registry
handle (post/close stay FIFO-safe, teardown races become a no-op), and
drop Raise() plus the show_modeless_dialog wrapper: Show() already
activates and fronts a new window on every platform.
2026-07-16 20:07:41 +08:00
SoftFever 8d8e4c2835 fix profiles errors spotted with new changes 2026-07-16 00:33:14 +08:00
SoftFever e5106a23b6 feat: AMS filament drying control (#14637)
## What this does
Ports the AMS filament drying control feature from BambuStudio. Most
work was done by Claude Code with deepseek-v4-pro. Thanks Bambu & CC &
DeepSeek :P


Allows users to start, monitor, and stop AMS-based filament drying
directly from the OrcaSlicer UI for N3F (AMS 2 Pro) and N3S (AMS HT) AMS
units.


Mostly from
https://github.com/bambulab/BambuStudio/commit/c8f70c6ca76e53775aa021a197fe9ec972db709e

Part of #12091

## Screenshots
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/24f579cb-c67c-4d6e-bf77-c31e018f2f70"
/>
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/74f628aa-6f5f-4150-b2e9-082e4ffc3527"
/>
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/d2f26412-a054-4085-9236-5074a030b001"
/>
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/7beef770-8fbc-4375-b244-e0de44b8db2f"
/>

## Changes
- **Data model:** Added drying status enums (`DryStatus`,
`DrySubStatus`, `CannotDryReason`, etc.), `DrySettings` struct,
`DevFilamentDryingPreset` struct to `DevAms`/`DevFilaSystem`
- **Promoted `DevAmsType`** to a global enum (`EXT_SPOOL=0, AMS=1,
AMS_LITE=2, N3F=3, N3S=4`), renamed `DUMMY` → `EXT_SPOOL`
- **JSON parsing:** Extended `DevFilaSystemParser` to parse drying
status fields from printer status messages
- **Commands:** Added `CtrlAmsStartDryingHour()` and
`CtrlAmsStopDrying()` sending `"ams_filament_drying"` JSON via MQTT
- **Backend utility:** New `DevUtilBackend` class with
`GetFilamentDryingPreset()` for reading filament drying config keys
- **UI dialog:** New `AMSDryControl` dialog with three pages
(status/control, guide, progress) matching BambuStudio behavior
- **Integration:** Wired AMS humidity indicator click to open the drying
dialog for N3F/N3S AMS types
- **Assets:** 12 new drying-related images from BambuStudio
- **Firmware parsing:** Added `is_support_remote_dry` flag parsed from
`fun2` bit 5

## Constraints
- N3F/N3S only — standard AMS and AMS Lite continue to use the existing
humidity popup
- Backward compatible — existing `command_ams_drying_stop()` preserved


<!--
> 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-07-16 00:04:59 +08:00
SoftFever c7558e978b Merge branch 'main' into dev/ams-heat 2026-07-15 23:33:12 +08:00
SoftFever a34310cbda Assign setting_ids to the profiles added for slice validation
The eight presets added in 0545750c1b never got ids: the six iQ processes
had none, and K1 SE 0.8 / V-Core 4 0.8 carried ids copied from the presets
they were duplicated from — K1 SE 0.8 still shared K1C 0.8's id. Regenerated
with scripts/assign_vendor_setting_ids.py; none of the eight have shipped in
a release, so no existing id changes meaning.
2026-07-15 23:16:33 +08:00
SoftFever 0849c44ae0 Merge branch 'main' into dev/ams-heat 2026-07-15 19:45:27 +08:00
SoftFever 1551898846 Plugin-gate the reslice check and drop non-plugin leftovers
reslice() now enforces only the missing-plugin block via
refresh_missing_plugin_block (no second Print::validate); plate
ready-status returns to upstream's plain model_fits, matching
GLCanvas3D::reload_scene. Also restores main's use_bbl_device_tab and
the check_track_enable comment, and drops two unused MainFrame includes.
2026-07-15 19:18:45 +08:00
SoftFever e116181c07 Use one agent id in switch_printer_agent and restore the preset_bundle guard
Merge-resolution cleanup. The #12506 re-select path kept main's preset_bundle
null check, and both select_machine calls now use effective_agent_id rather than
mixing it with the equal-but-differently-named agent_info.id.
2026-07-15 17:54:39 +08:00
SoftFever bf932e8797 Merge branch 'main' into feat/plugin-feature 2026-07-15 17:19:14 +08:00
SoftFever 8cb0cb7693 Merge branch 'main' into feat/plugin-feature
Resolve five conflicts, all of which needed both sides rather than a pick:

- BackgroundSlicingProcess: ours was a pure tabs->spaces reformat of base, so
  keep main's per-filament volume/nozzle map read-back (its only change here).
- GUI_App: main's #12506 else-if attached to an `if` this branch deleted;
  re-expressed onto the same-agent early-return path (the agent factory caches
  per id, so pointer equality is the same predicate).
- MainFrame: both sides relocated Sync Presets independently; keep main's
  push_notification plus the branch's Plugins menu items.
- Tab: the "TODO: Orca: Support hybrid" blocks were unchanged base, not a branch
  decision; take main's enabled Hybrid to match the already auto-merged siblings.
- test_config: union of both sides' cases (6 plugin + 9 multi-nozzle).
2026-07-15 17:18:46 +08:00
SoftFever f626382209 Run slice check on arm64 Linux 2026-07-15 16:56:55 +08:00
SoftFever be165c97de Add --outdir to profile validator to save sliced g-code 2026-07-15 16:52:02 +08:00
SoftFever 8ac478be26 Add real slice validation for all printers (#14771)
# Description

Adds a --slice (-s) mode to the profile validator that slices a
two-colour cube through every shipped printer, expanding all custom
g-code (change_filament_gcode, machine start/end, etc.). This catches
invalid-placeholder / bad-flow / slicing errors that the static JSON
checks and unit tests can't see.

Included:
- Validator: new -s sweep mode; per-profile error attribution in the
log; resolves the synthetic 2nd-filament nozzle-mapping so multi-nozzle
BBL printers (incl. the Direct-Drive+Bowden X2D) validate cleanly.
- CI, two complementary paths:
- check_profiles.yml — runs the sweep on profile-only PRs (nightly
binary).
- build_all.yml — new parallel slice_check_linux job runs it on
engine/src PRs with the PR-built binary (build_all doesn't trigger on
resources/**, so no overlap). Runs off the build's artifact, so it
doesn't lengthen the build leg.
- Profile fixes surfaced by the sweep: Creality, FLSun, Ginger, Qidi,
RatRig, iQ.
- Engine: whitelist BBL firmware T-opcodes (T1001/T65279/T65535) in the
time estimator (log-only, no g-code change); dedupe a
per-filament/per-layer log flood in get_config_index.

# 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.
-->

<!--
> 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-07-15 16:32:09 +08:00
SoftFever d55e7834c8 Fix unit-test segfault on missing shipped profile in sparse CI checkout
The Unit Tests job sparse-checks-out only .github/scripts/tests, so the
baked-in absolute PROFILES_DIR was missing at runtime; the shipped-profile
test then read a non-existent JSON and null-dereferenced in opt_string.
Check out resources/ in the unit-test job, and guard the test helper to
skip when the profile is absent and require the key before dereferencing.
2026-07-15 14:55:41 +08:00
SoftFever c3f78723fb Run slice-validation sweep on engine PRs in CI 2026-07-15 02:10:16 +08:00
SoftFever f1fd49c12f Add slice-validation sweep for shipped profiles 2026-07-15 01:49:04 +08:00
SoftFever 0545750c1b fix profile errors caught in new tests 2026-07-15 00:08:26 +08:00
SoftFever 591d1ac948 fix qidi profile errors 2026-07-15 00:06:54 +08:00
SoftFever 28bb05ac83 Suppress excessive warnings 2026-07-15 00:06:13 +08:00
SoftFever 25216998b3 Add Bambu H2C and A2L machine support (#14685)
# Description

This PR adds full support for Bambu H2C and A2L printers.

All features should work, including pre-heating and cooling.

The Filament Track Switch feature has been ported but hasn't been tested
yet. Feedback is welcome.


# Screenshots/Recordings/Graphs


https://github.com/user-attachments/assets/417f0e32-1967-4536-b732-3c8dc8d4dd4a

<img width="1369" height="999" alt="image"
src="https://github.com/user-attachments/assets/3c3c12ee-69c7-44ef-90f1-badc2a7e803a"
/>

<img width="848" height="580" alt="image"
src="https://github.com/user-attachments/assets/28186a99-979d-4059-b40e-798162c30ce2"
/>

<img width="892" height="551" alt="image"
src="https://github.com/user-attachments/assets/22823921-f22d-4c4d-9b89-43070120d8a6"
/>


## Tests


- [x] Multi-color/material test printing on H2C
- [x] Test printing on A2L
- [x] Test printing on P1S
- [x] Test printing on other tool changers to confirm no regressions
~~- [ ] Optional: test with newer plugin. Note: existing v02.03.00.62
plugin works without any issues~~ Will address as separate task

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-14 02:55:08 +08:00
SoftFever 74813b401f Merge remote-tracking branch 'origin/main' into feature/h2c_support_clean
# Conflicts:
#	localization/i18n/fr/OrcaSlicer_fr.po
#	localization/i18n/list.txt
2026-07-14 02:54:20 +08:00
SoftFever df1e114b72 resolve current_filament_id/current_nozzle_id in layer_change_gcode - enhanced 2026-07-14 02:12:09 +08:00
SoftFever 56f672ecbd resolve current_filament_id/current_nozzle_id in layer_change_gcode (fixes X2D) 2026-07-14 01:26:34 +08:00
SoftFever 9aa90fa6a8 update BBL H2D and X2D profiles 2026-07-14 00:52:54 +08:00
SoftFever 2268544a68 fix test error on Windows ARM64 2026-07-13 23:26:16 +08:00
SoftFever 35ffe49b17 Fix sending print jobs to Bambu H2C printer 2026-07-13 18:13:15 +08:00
SoftFever 30aa7d9557 feat(gui): warn when the hotend rack inventory falls short of the sliced plate
Follow-up to the rack-aware pre-print checks: after the blocking checks
pass, warn (without disabling Send) when the plate needs more matching
hotends than the rack printer currently holds - suggesting rack setup,
a nozzle info refresh, or a re-slice to avoid filament waste - or when
the only matches rely on unreliable nozzle information.

Text-only warning rows; this message board has no refresh or
don't-show-again buttons.
2026-07-13 00:55:52 +08:00
SoftFever 2fecfc291f fix(gui): rack-aware pre-print nozzle checks in the send dialog
A print sliced for a nozzle that sits in the hotend rack (but is not
mounted) was blocked by the send dialog's mounted-nozzle diameter check,
even though the printer fetches the required nozzle itself (#14685).

Consolidate the three mounted-nozzle gates (_is_nozzle_data_valid,
is_nozzle_type_match, _is_same_nozzle_diameters) into a single
CheckErrorExtruderNozzleWithSlicing fed by s_get_slicing_extuder_nozzles,
which collects the plate's per-extruder nozzle requirements (hybrid
extruders contribute one entry per used sub-nozzle flow). The rack
extruder validates against its whole inventory (mounted + rack) with
guidance to calibrate the rack, refresh nozzle info, or re-slice, and
blocks when toolhead + rack are full (no free slot to stow a nozzle).
Other extruders keep the validity/flow/diameter checks against the
mounted nozzle.

Also add CheckErrorRackStatus, which holds Send while the printer is
still reading the rack hotend information, and judge material hardness
for the rack extruder per dispatch-mapped nozzle as a non-blocking
caution (mounted nozzles keep the blocking gate).
2026-07-13 00:55:16 +08:00
SoftFeverandsongwei.li e98750e713 fix(engine): real-change-only toolchange ordinals and a single M1020 toolchange command
A dual-nozzle H2C print with support filament hangs at its first nozzle
switch. The emitted file shows the change-filament block's M620 O ordinal
jumping from O1 straight to O230, plus a duplicate "M1020 S<n>" toolchange
command right after every change block. Two causes, fixed together because
they interlock (the ordinal check keys off the same toolchange detection
that suppresses the duplicate):

- append_tcr incremented m_toolchange_count once per prime-tower visit
  (roughly once per layer), while the change-filament template only emits
  its M620 O{toolchange_count + 1} line on real filament changes. With 229
  change-less sparse tower layers below the first support layer, the first
  real change reported ordinal 230. The counter now advances only when the
  expanded change block really contains a toolchange command, and the
  placeholder exposes the upcoming change's ordinal (count + 1). The
  set_extruder path already counted per real change and is unchanged.

- toolchange_prefix() returned "M1020 S" for BBL printers, so the
  custom_gcode_changes_tool() dedup could never match the stock profiles'
  line-leading "T[next_filament_id] ..." commands and the writer's own
  toolchange was appended after every change block on dual-extruder
  machines. The prefix is now the plain "T" (the manual-filament-change tag
  branch stays first), and the M1020 form moved into GCodeWriter::toolchange()
  as an explicit branch that also carries the nozzle:
  "M1020 S<filament> H<nozzle>". The nozzle parameter is signed on purpose:
  the null-safe nozzle lookup legitimately yields -1, matching the stock
  templates' own H-1 convention.

The prefix change also lets the CoolingBuffer recognize the change blocks'
T commands as tool boundaries on BBL printers (its per-filament attribution
previously keyed off the duplicate M1020, or nothing at all on
single-extruder models); its existing out-of-range guard ignores
T1000-class machine commands.

Verification: full suites green (libslic3r 48998 assertions / 169 cases;
fff_print 692 / 65 including three new scenarios - writer emission per
printer kind, dedup + ordinal progression on sequential prints, and a
prime-tower regression scenario verified to fail against the old per-visit
counting). Byte gate: 18 of 20 fixtures bit-identical; the sequential repro
differs by exactly its 3 removed duplicate M1020 lines, deterministic
across two runs. Reslicing the field project that exposed the hang yields
M620 O1 followed by a gapless O2..O59 and zero duplicate M1020 lines.

Co-authored-by: songwei.li <songwei.li@bambulab.com>
2026-07-12 21:30:12 +08:00
SoftFever 3ebd68d959 feat(engine): per-nozzle-variant machine limits in the time estimator
The time estimator's speed/acceleration limits were indexed by time
mode only, reading slot 0 of the per-(extruder x volume-type) arrays
the multi-extruder profiles already carry (H2C 0.4: 8 entries, H2D
0.4: 10). Every move was therefore modelled with the first machine
slot's limits regardless of which nozzle variant was printing -
estimation fidelity only, since emitted feedrates/accelerations are
decided on the slicing side.

Now the estimator resolves the machine slot of the nozzle currently
mounted in the active extruder: the nozzle grouping context is handed
to the processor BEFORE the streaming replay (new member + setter -
deliberately separate from the post-stream result-field handover that
gates the richer change-time model, whose timing is unchanged), the
occupancy recorder is populated on every filament change (bookkeeping
decoupled from the gated time model; recorder writes have no time
effect), and get_machine_config_idx maps (volume type x extruder type
x extruder) to the slot via the printer's variant layout, newly
carried on the processor result. The feedrate/acceleration getters
gain a slot parameter indexing [slot*2 + mode]; jerk and the
print/travel/retract accelerations stay mode-only. Reloaded sliced
projects re-estimate with the result's saved grouping context;
imported bare g-code degrades to slot 0 - the historical read.

M201/M203 write the parsed value into EVERY slot's mode entry (a
firmware envelope change is global), which keeps per-slot reads in
lockstep with the mode-only reads they replace: the fleet emits
envelope lines before any motion, so estimates - hence the estimated
time header, M73 lines, and every other byte - are unchanged (20/20
pinned-slice byte gate bit-identical, incl. the sequential repro
sliced twice). Fidelity improves where envelope emission is off or a
migrating per-layer plan moves filaments across variants.

Tests: a stub-driven processor case proving the slot follows the
active nozzle through the exact production path (T..H.. commands,
fallback recorder bookkeeping, 4x time ratio on the slow variant),
that emitted M201/M203 reach every slot, and that a missing context
degrades to slot 0. Suites green (libslic3r 48998/169, fff_print
667/62).
2026-07-12 14:48:45 +08:00
SoftFever 582017235c feat(engine): resolve per-variant config columns for raw filament reads on the export path
When a per-layer nozzle grouping migrates a filament across nozzle
variants, the write-back turns two groups of config arrays from
filament-indexed into column-indexed: the per-variant filament options
(one column per variant a filament uses) and the merged extruder
retract overrides (resized to the column count by apply_override).
Export-path readers that still indexed them with the raw filament id
read a neighbor's column for every filament ordered after a migrating
one: toolchange/standby temperatures (M104/M109), retraction lengths
and feedrates, wipe distance, z-hop types, air-filtration keys, and -
through the Extruder's cached flow term - the extrusion E of every
move.

Now every such read resolves its column through the existing
layer-aware resolver (get_filament_config_index ->
Print::get_filament_config_indx), which returns the raw filament id
whenever no per-layer grouping result is published, so static prints
are byte-inert by construction. The Extruder itself has no layer
knowledge, so it gains an injected config column (set_config_index,
default = filament id) that the generator refreshes at the only two
resolution-changing events - layer change and writer toolchange - and
that re-syncs the cached e_per_mm3 flow term. Old-filament reads
resolve at the current layer, which is safe because the per-layer maps
are gap-filled carry-forward. Whole-array placeholder copies
(toolchange temperature overrides) are rebuilt in filament order,
mirroring the existing per-variant placeholder remap. The resolvers
move to the public section so non-friend helpers (ooze prevention) can
resolve too.

Documented, deliberately unchanged: the wipe tower's per-filament
parameter rows (no layer dimension; tower x per-layer grouping is a
follow-up), travel_slope's physical-extruder read, estimator pre-heat
bookkeeping temps, and index-0 header diagnostics.

Verification: new Extruder column-injection scenario (defaults, column
follow + flow-cache rescale, filament-indexed reads unaffected, reset
semantics) and a migrating write-back case proving the column shift for
filaments ordered after a migrator and the resolver tracking it (11 +
14 assertions); suites green (libslic3r 48998/169, fff_print 655/61);
20/20 pinned-slice byte gate bit-identical (incl. sequential repro x2
deterministic).
2026-07-12 14:30:39 +08:00
SoftFever abbd420f2a feat(engine): write per-variant filament values back for selector regroups
When the per-layer filament selector (enable_filament_dynamic_map)
migrates a filament across nozzle variants (e.g. Standard -> High Flow),
the config write-back only stored the derived extruder map; every
per-variant filament value (retraction, nozzle temperature, flow,
flush...) kept the numbers resolved from the pre-slice static mapping.

Now both dynamic write-back sites (the by-layer branch and the
sequential stitch) branch on the result's dynamic support. Migrating
results run a mixed-filament expansion that regathers every
filament_options_with_variant key from the pristine per-variant
superset, giving a migrating filament one config slot per (extruder
type x nozzle volume type) it lands on - filament_self_index,
filament_extruder_variant, and all value arrays grow in lockstep - and
recompute the retract overrides with per-slot machine indices so a nil
slot falls back to its own variant's machine value. Non-migrating
dynamic results take the merged three-map write-back so re-applies
reproduce from the written maps. Unrouted filaments resolve from the
result's own default map, so slot resolution never depends on
filament_map round-tripping through the plate config.

Print::apply reproduces the identical expansion from the persisted
group result (shared dedupe helper, expansion function, and slot
indices on both sides): the expanded keys sit in the psWipeTower /
psGCodeExport invalidate lists, so without the reproduction every
re-apply after a selector slice would diff non-empty and permanently
invalidate. cal_non_support_filaments now resolves the extruder per
layer from the published result for dynamic groupings.

filament_map_2 keeps its apply-time static derivation; nothing on the
dynamic path reads it (the per-slot machine indices key the override
merge), and per-(extruder x volume-type) machine limits in the g-code
processor remain a documented follow-up.

Every change is gated behind is_dynamic_group_reorder() or a persisted
result with dynamic support; no profile sets the flag, so the static
fleet's instruction stream is unchanged (20/20 pinned-slice byte gate
identical, incl. the sequential repro sliced twice, deterministic).

Tests: expansion unit coverage (migrating slots, unrouted fallback via
the default map, mis-sized volume map ignored, nullable retract keys in
lockstep, slot machine index layout), an end-to-end stub-driven
write-back asserting expanded slots, per-layer config-index resolution,
the override merge incl. the nil-slot variant fallback, and re-apply
stability, plus a real selector slice staying valid across re-apply.
Suites green (libslic3r 48987/168, fff_print 633/60).
2026-07-12 11:15:58 +08:00
SoftFever c8db06b1d4 feat(engine): stitch per-object selector plans for sequential prints
Sequential (by-object) prints were incoherent with the per-layer filament
selector (enable_filament_dynamic_map): the by-object branch published a
static grouping while each per-object ToolOrdering independently ran the
dynamic planner from an empty nozzle status and wrote its own map to the
config (one write per object, last object wins). The exported toolchange
sequences then disagreed with the published result that drives the
per-layer maps, placeholders, and selector emission.

Now the by-object branch, when the selector is enabled, plans each unique
object once — threading the physical nozzle occupancy and the previous
object's last filament into the next plan — stitches the per-object
per-layer nozzle maps into one print-wide result (gap-filled by the new
normalize_nozzle_map_per_layer so any layer index resolves a filament's
nozzle consistently), publishes it, and writes the derived extruder map
back once. The plans are cached on the Print and g-code export consumes
the cache: the ToolOrdering seed changes the plan input (dontcare
assignment, first-layer reorder), so a fresh export-time construction
could re-plan differently from the published stitch. The per-object
dynamic write-back is gated off for sequential prints.

Every change is gated behind is_dynamic_group_reorder(); no profile sets
the flag, so the static fleet's instruction stream is unchanged (20/20
pinned-slice byte gate identical, incl. the by-object repro sliced twice).

Tests: normalize unit coverage (carry-forward, back-fill, ragged input),
stitched-blocks selector detection, and an end-to-end by-object selector
slice (apply -> process -> export) asserting the published stitched
result, one cached plan per object, the config write-back, and a clean
export. Suites green (libslic3r 48958/165, fff_print 633/60).
2026-07-12 03:24:35 +08:00
SoftFever 4e9bf47741 Plugin: Slicing-pipeline support and expose new APIs and refactoring (#14721)
# Slicing-pipeline plugins: Python hooks inside `Print::process()` with
an editable geometry API

This branch adds a new **slicing-pipeline** plugin capability on top of
the plugin framework: Python plugins can now run at defined points
inside the slicing pipeline and edit the live slicing data, with changes
cascading into perimeters, infill, and the final G-code.

## The capability

- New plugin type `slicing-pipeline`, selectable per print profile via a
new picker option (`slicing_pipeline_plugin`).
- `Print::process()` fires a hook at 13 pipeline steps (`posSlice`,
`posPerimeters`, … `psSkirtBrim`, `psGCodePostProcess`). Selected
plugins run per step with cancellation honored and failures surfaced as
ordinary slicing errors, never crashes.
- G-code post-processing is folded into this capability as the final
step (`psGCodePostProcess`); the separate "post-processing" plugin type
and its option are removed. Breaking only for the unreleased plugin API
— migrated plugins gain settings and config access in return.

## The Python API (`orca.host`)

- The live print graph is exposed as raw host classes — `Print`,
`PrintObject`, `Layer`, `LayerRegion`, `SurfaceCollection`, `Surface`,
`ExPolygon`, `Polygon`, `Point`, plus `Model`/`TriangleMesh` with
zero-copy numpy views.
- Geometry is genuinely editable through the class API: in-place
transforms, whole-surface replacement, and vertex-level rebuilds, with
`layer.make_slices()` re-deriving the C++ invariants after edits. Write
paths validate input, so malformed data raises in Python instead of
corrupting the slice.
- Bindings are organized under `src/slic3r/plugin/host/` by domain.

## Architecture

- All libslic3r hook seams (capability resolver, pipeline dispatcher)
are installed and uninstalled by one composition root,
`plugin/PluginHooks`, from `PluginManager::initialize()`/`shutdown()`.
GUI_App no longer accumulates per-capability wiring, and hooks detach
before the Python interpreter finalizes.

## Samples (`sandboxes/`) — one per editing idiom

| Sample | Step | Demonstrates |
| --- | --- | --- |
| **Inset Every Slice** | `posSlice` | Shrink every slice via polygon
offset + whole-surface replacement (`slices.set`) |
| **Twistify** | `posSlice` | Twist/taper/wobble via count-preserving
in-place transforms |
| **Fuzzy Slices** | `posSlice` | Fuzzy skin applied to the slice
contours themselves (vertex-level rebuild) — walls, infill, and the
preview all inherit it |
| **G-code Stamp** | `psGCodePostProcess` | Editing the exported G-code
file in place |

# Screenshots/Recordings/Graphs
1. Fuzzy skin example:
Orca's built-in fuzzy skin perturbs the outer-wall EXTRUSION PATHS
during
perimeter generation, so only the printed wall is fuzzy. This sample
instead
perturbs the sliced outline itself at sliced geometry:
<img width="1230" height="902" alt="image"
src="https://github.com/user-attachments/assets/bcf8b0c7-f932-4e6a-985d-7c9cc2f3d7cb"
/>

2. Twistify -- twist/taper/wobble any model at slice time
every layer's sliced surfaces are transformed by a similarity
about the object's bounding-box center as a function of Z


https://github.com/user-attachments/assets/d1309ea8-b01c-4708-adf1-821b3b00a4cc

3. Inset Every Slice -- a small, WORKING SlicingPipeline sample plugin
For every layer/region of the sliced object, this shrinks each
sliced surface by INSET_MM using a real polygon offset
<img width="1225" height="896" alt="image"
src="https://github.com/user-attachments/assets/5f2028a9-ae2a-4aea-8b38-a3d6e24f5cb4"
/>
2026-07-12 01:41:12 +08:00
SoftFever 579e58c528 Add Fuzzy Slices sample (fuzzy skin at posSlice) + coverage test
Experimental fuzzy on geometry
Mirrors libslic3r's fuzzy_polyline on the slice contours at Step.posSlice,
demonstrating the count-changing mutation idiom (rebuild ring via
Polygon.append, write back via ex.contour / ex.set_holes). C++ analogue
test proves area preservation, cascade, and bounded displacement.
2026-07-12 01:19:54 +08:00
SoftFever 780b2f1ebe fix(engine): publish the nozzle group result for sequential prints
The Print-level LayeredNozzleGroupResult had a single producer, the
by-layer branch of ToolOrdering, which is gated to non-sequential prints.
The by-object branch in Print::process computed a grouping only in auto
map modes and never stored it, so a sequential slice exported with a null
group result: the per-nozzle placeholder tables came up empty and any
start g-code indexing nozzle_diameter_at_nozzle_id[] aborted with
"Indexing an empty vector variable". A prior by-layer slice masked the
bug by leaving its (never cleared) result on the Print.

Now the by-object branch runs get_recommended_filament_maps in every
static map mode (in manual modes the result mirrors the user's
assignment, deviations throw as in by-layer) and publishes it
print-wide. The config write-back stays gated to auto modes: in manual
modes it would only re-store the pre-slice values.

Regression test: a two-object by-object print must publish a non-null
group result and resolve nozzle_diameter_at_nozzle_id[] in start g-code
(both fail without the fix). Suites green (libslic3r 48929/162,
fff_print 633/60); 18-fixture byte gate identical; the by-object repro
project goes from the export error to valid g-code, determinism x2.
2026-07-12 00:46:15 +08:00
SoftFever 03094df10e refactor(plugin): move libslic3r hook wiring out of GUI_App into PluginHooks
GUI_App::on_init_inner() carried the plugin dispatch policy inline (the
capability resolver and the slicing-pipeline dispatcher) and would grow
with every capability that fires from inside libslic3r.

plugin/PluginHooks.{hpp,cpp} now owns one file-local installer per hook,
aggregated by plugin_hooks::install() -- called from
PluginManager::initialize(), reset in shutdown() so no hook can enter
Python after the interpreter finalizes. The wx-side loader subscriptions
move into GUI_App::init_plugin_gui_wiring().

No behavior change; dispatch bodies moved verbatim.
2026-07-12 00:20:39 +08:00
SoftFever c65b0540a3 Merge remote-tracking branch 'origin/main' into feature/h2c_support_clean
# Conflicts:
#	src/libslic3r/PrintApply.cpp
#	src/libslic3r/PrintConfig.cpp
#	src/libslic3r/PrintConfig.hpp
#	tests/libslic3r/test_config.cpp
2026-07-11 23:10:54 +08:00
SoftFever e717dea94b Merge branch 'main' into feature/h2c_support_clean 2026-07-11 22:19:59 +08:00
SoftFever f5418415ac fix(gui): stop clipping the nozzle-count edit button on flow changes
HoverLabel's constructor used SetSizerAndFit, which records the
count-hidden width as the panel's explicit minimum size. An explicit
minimum outranks best size in sizer allocation, so once the "(N)" count
was shown, any ancestor Layout() - e.g. switching the extruder flow type
to Standard or High Flow - shrank the title row back to the stale width
and clipped the trailing edit button. Hybrid only appeared correct while
nothing had re-laid the row since its own Fit().

Use plain SetSizer and, on every title/count change, invalidate the
cached best size and re-lay both the row and its parent so the sizer
always allocates the current content width.
2026-07-11 22:11:47 +08:00
SoftFever dacf26b06f fix(engine): keep engine-derived filament_map_2 out of the apply diff
Print::apply rebuilds m_config.filament_map_2 to the real per-filament slot
map on every apply, while the incoming full config only ever carries the
ConfigDef default. The resulting phantom one-key print_diff hit the
invalidator's catch-all branch and killed every print-level step on each
apply, so on multi-extruder printers a fresh slice result was invalidated
the moment the GUI re-applied after slicing completed.

Dropping the key from print_diff loses no information: it is never a user
input, and the rebuild derives it from filament_map, filament_volume_map
and the variant slots, each of which is diffed and invalidation-listed on
its own.

Regression test: re-applying an unchanged config after process() must not
invalidate psSlicingFinished (fails with APPLY_STATUS_INVALIDATED without
the fix). Suites green (libslic3r 48891/154, fff_print 631/59); 19-fixture
byte gate identical incl. the Hybrid repro project, determinism x2.
2026-07-11 22:11:35 +08:00
SoftFever c17d9732be refactor(plugin): bind raw TriangleMesh instead of HostTriangleMesh wrapper
Bind libslic3r's TriangleMesh directly with a shared_ptr holder rather than
wrapping it in a HostTriangleMesh snapshot struct. ModelVolume.mesh() hands
out the volume's own shared_ptr (via const_pointer_cast, which only serves
the holder type), so the Python object pins the snapshot exactly as the
wrapper did, and the zero-copy views now use the Python object as their
array base — deleting the capsule machinery.

The wrapper's type-level constness becomes a documented rule instead:
handed-out meshes are copy-on-write snapshots shared across threads, so the
binding exposes only const methods; a future mutable-mesh API must operate
on plugin-owned copies handed back via ModelVolume::set_mesh.

No Python-visible change (orca.host.TriangleMesh, same methods/docstrings),
and plugins now hold the real class a future set_mesh() will accept.
Verified with slic3rutils and fff_print suites.
2026-07-11 18:54:07 +08:00
SoftFever 75c8d0775a feat(gui): per-filament nozzle volume selection and persistence
- FilamentMapDialog's manual page understands volume types: a mixed
  (Hybrid) extruder shows separate Standard / High Flow drop zones
  with live sub-nozzle counts, a validation timer with an inline
  error + "set nozzle count" suggestion, and composes a per-filament
  volume map on OK (persisted to the plate or globally)
- switching an extruder's Flow type rewrites the affected plate map
  entries; plate maps stay sized across filament add/delete/count
  changes (values keep their filament, no index shift)
- CLI: manual mapping on multi-nozzle printers synthesizes the volume
  map from extruder flow types when absent; nozzle-manual mode
  requires explicit maps; computed maps land on the plate so exported
  projects carry filament_volume_maps
- filament_nozzle_map joins the project options (selection seeding +
  filament-count resizing)
- pot entries for the new dialog strings

All 19 reference fixtures byte-identical; slicing an exported Hybrid
project reproduces its g-code byte-for-byte with the volume map
round-tripped through model_settings.config.
2026-07-11 16:37:50 +08:00
SoftFever 126e4d5445 refactor(plugin): split orca.host bindings into host/ by domain
PluginHostApi.cpp had grown into one TU holding the module entry point plus
three unrelated domains (presets, model/mesh graph, app access), and
PluginHostSlicing.cpp mixed ownable geometry value types with the
non-owning live print graph. Reorganize the orca.host surface into
plugin/host/ with one registrar per domain:

- PluginHost.hpp/.cpp        entry point (replaces PluginHostApi)
- PluginHostBindings.hpp     internal per-domain registrar declarations
- PluginHostGeometry.cpp     BoundingBox, Point, Polygon, ExPolygon + ndarray parsing
- PluginHostMesh.hpp/.cpp    TriangleMesh snapshot (own TU ahead of planned
                             mesh construct/mutate APIs)
- PluginHostPresets.cpp      Preset, PresetCollection, PresetBundle
- PluginHostModel.cpp        scene graph: Model, ModelObject, ModelInstance, ModelVolume
- PluginHostApp.cpp          Plater + plater()/model()/preset_bundle() accessors
- PluginHostSlicing.cpp      live print graph only, now with a single lifetime story
- PluginHostUi.hpp/.cpp      moved unchanged

PluginBindingUtils.hpp stays at plugin/ root: it is shared with pluginTypes/
and tests, not host/-specific.

No Python-visible change: same submodules, class names and docstrings.
Verified with slic3rutils and fff_print suites.
2026-07-11 16:18:59 +08:00
SoftFever 21b46044d0 feat(gcode): resolve per-filament variant slots per layer
- the g-code writer tracks the current layer id and resolves
  FILAMENT_CONFIG/NOZZLE_CONFIG (plus every non-macro variant lookup,
  toolchange placeholder scalars, and the change-filament flush
  overrides) through Print's per-filament, per-layer config-index
  resolvers instead of the filament->extruder collapse
- update_layer_related_config refreshes the per-layer
  extruder/volume/nozzle maps in the writer config;
  update_placeholder_parser_with_variant_params remaps the
  filament-variant arrays into filament-id space for custom g-code
  (Orca's flush placeholder computation moves inside it)
- the engine's concrete per-filament volume assignment now merges into
  the config write-back (the temporary hold from the producer commit
  is lifted together with these consumers), and the background process
  reads the computed volume map back to the plate
- append_full_config dumps the resolved filament_map_2 slots
- update_used_filament_values gains a bounds guard
- tests: per-filament Hybrid slot resolution + null-result fallback

Result: on a Hybrid extruder, each filament's features slice with its
assigned sub-nozzle's variant values (speeds, volumetric limits,
retraction). Verified on a 4-filament H2C Hybrid project: outer walls
split into three feedrate populations (30/50/200 mm/s), toolpath
geometry byte-identical, deterministic across repeated slices. All 18
non-Hybrid reference fixtures stay byte-identical except the
filament_map_2 header value now showing the real slot. Auto grouping
ties (multiple zero-flush perfect matchings) may pick a different
filament-to-nozzle isolation than other slicers; verified co-optimal.
2026-07-11 15:00:33 +08:00
SoftFever 03a704df7c feat(engine): wire the per-filament volume-map producer pipeline
- Print::update_filament_maps_to_config takes filament/volume/nozzle
  maps, backfills an empty volume map from extruder types, rebuilds
  filament_map_2, re-expands the per-filament variant arrays, and
  recomputes retract overrides keyed by resolved slots
- grouping writes its result back in every non-sequential mode;
  manual multi-nozzle grouping validates the user mapping and raises a
  translatable error on deviation; the engine's concrete volume
  assignment is deliberately not merged yet (per-filament arrays are
  already consumed by filament id, so materializing High Flow now
  would change motion before the layer-aware resolvers land)
- Print::apply treats the three map keys as engine outputs in auto
  modes (erased from the diff and adopted), compares them against used
  filaments in manual mode, and keeps the pre-expansion snapshot in
  sync with the late normalization pass so rebuilt headers reflect the
  sliced state instead of resurrecting stale values
- volume/nozzle maps and extruder_nozzle_stats join the invalidation
  group of filament_map (wipe tower + skirt/brim)
- PresetBundle composes full configs with an optional per-filament
  volume map (plate map, else defaults derived from each extruder's
  flow type); project config keeps the map sized across filament
  count changes
- PartPlate stores per-plate volume/nozzle maps; Plater injects them
  at every slice-composition site (incl. g-code reload and wipe-tower
  estimation); BackgroundSlicingProcess reads engine results back to
  the plate in auto modes
- per-filament map trust guards relaxed to size-match everywhere now
  that every producer sizes the map; single-filament explicit flow
  assignments are honored
- tests: grouping volume maps stay concrete, merge semantics of
  update_used_filament_values, single-filament override honoring

Motion g-code is byte-identical fleet-wide including Hybrid projects
(19-fixture gate + repro determinism double-slice). Header deltas:
the map keys now dump real values, and stale pre-normalization values
(e.g. enable_prime_tower on single-used-filament prints) no longer
leak into the config block.
2026-07-11 04:38:31 +08:00
SoftFever a04ce5f81e docs(plugin): make comments self-contained; drop design-doc and dead-code references
Review the slicing-pipeline plugin comments for context a reader of the source
alone cannot follow, and rewrite them to stand on their own:

- drop pointers to uncommitted design/plan material ("§3.6 (Twistify design)",
  "the brief's note", "Fix 4(a)/4(b)")
- fix dangling references to code this branch removed: the retired set_slices()
  and view mutators, the former G-code post-processing capability/trampoline,
  the "Post-processing" capability family, the pre-refactor array helper
- drop "v1"/"in v1" phase labels, keeping the behavior they described
- correct stale cross-references: Twistify.py -> the real sample path;
  test_plugin_host_api.cpp:32-40 -> import_orca_module in python_test_support.hpp;
  "the binding"/"graphs above" -> the named source

Comment/string-only; no code behavior change.
2026-07-11 03:30:05 +08:00
SoftFever 18b9279c26 feat(print): add per-filament config-index resolvers and filament_map_2
- Print::get_nozzle_config_index / get_filament_config_indx resolve a
  filament's variant slot per layer from the nozzle group result, with
  hashed index caches; when no group result is published (sequential
  prints), they fall back to the static filament->extruder mapping so
  behavior is unchanged
- filament_map_2 caches each filament's resolved print-variant slot;
  rebuilt in Print::apply after the filament_map diff handling and in
  the filament-map write-back
- filament retract overrides now key by slot indices: apply_override
  fallback indexing flips to 0-based, Print::apply passes
  filament_map/extruder indices, the write-back passes filament_map_2
  (identical resolution while slots equal extruders)
- filament_volume_map/filament_nozzle_map/filament_map_2/
  filament_self_index become PrintConfig static members (required for
  member access); grouping input guards tightened so their registered
  1-element defaults are never mistaken for real per-filament maps
  (single-filament manual mode keeps the mix-marker fallback)
- update_filament_self_index_cache refreshed at every full-config
  assignment
- tests: 0-based apply_override fallback, get_config_index_base
  hit/miss/mixed-type cases

The resolvers are not consumed by the g-code writer yet. Non-Hybrid
g-code is unchanged except the config header, which now serializes the
three new static keys (defaults until the per-filament producer lands);
verified by the 19-fixture byte gate: 3 added header lines per fixture,
zero motion changes.
2026-07-11 02:29:40 +08:00
SoftFever 13cd8d4864 feat(config): keep per-(extruder x volume-type) slots in variant expansion
- get_extruder_nozzle_volume_count derives per-extruder volume-type slot
  lists from extruder_nozzle_stats (absent stats = one slot per extruder)
- update_values_to_printer_extruders learns the slot layout: when any
  extruder mixes volume types, option arrays keep one slot per
  (extruder x volume type), extruder-ascending then volume-ascending;
  single-slot resolution takes the filament's volume type on mixed
  extruders
- update_values_to_printer_extruders_for_multiple_filaments applies a
  per-filament nozzle_volume_type override from filament_volume_map
  (when sized to the filament count) and remaps filament_self_index
  through the same pipeline as every other filament key
- get_config_index_base + is_auto_filament_map_mode helpers (consumers
  land with the per-filament config-index resolvers)
- callers updated: PresetBundle composition paths, PrintApply (counts
  hoisted above the extruder_applied guard), Print write-back
- new tests: slot counting, Hybrid slot expansion incl. stride 2,
  per-filament override, non-Hybrid degeneracy

Non-Hybrid printers keep their variant layout and values (proven by a
19-fixture byte gate; the only header delta is filament_self_index now
flowing through the same variant pipeline as its sibling filament
keys). Hybrid slices grow the config-block variant arrays to one entry
per sub-nozzle volume type; motion g-code is unchanged until the
g-code writer consumes the new slots.
2026-07-11 00:51:10 +08:00
SoftFever 19352215da feat(plugin)!: merge G-code post-processing into the slicing pipeline as psGCodePostProcess
G-code post-processing is now a step of the slicing-pipeline plugin rather than a
separate capability type. One capability class can transform slices at the geometry
seams AND edit the final G-code, behind a single picker/option.

- Add SlicingPipelineStepPlugin::psGCodePostProcess (bound as
  orca.slicing.Step.psGCodePostProcess). Unlike the geometry steps it fires from the
  GUI export path in PostProcessor.cpp, not from Print::process(): ctx.print/ctx.object
  are None and the plugin edits the file at ctx.gcode_path in place. It may run more
  than once per slice (file export and/or upload) and its output is not shown in the
  preview.
- Extend SlicingPipelineContext with gcode_path/host/output_name and a C++-only
  full_config; config_value() falls back to it when there is no live Print.
- PostProcessor.cpp dispatches SlicingPipelinePluginCapability at psGCodePostProcess,
  driven by the existing slicing_pipeline_plugin option.
- The exported G-code lives outside data_dir(), so the plugin audit sandbox would
  block the write; the trampoline's audit setup grants ctx.gcode_path's folder as a
  scoped allowed root, gated on a non-empty gcode_path so the geometry-step hooks gain
  no extra filesystem access.

BREAKING CHANGE: the separate G-code post-processing capability type is removed.
- orca.gcode.GCodePluginCapabilityBase and orca.PluginType.PostProcessing are gone;
  post-processing plugins migrate to orca.slicing.SlicingPipelineCapabilityBase +
  Step.psGCodePostProcess (and gain ctx.params / ctx.config_value()).
- The post_process_plugin config option is removed; use slicing_pipeline_plugin.
  Presets carrying the old key degrade to the standard unknown-key warning.
- Manifest type = "post-processing" now maps to Unknown (advisory only; the loader
  dispatches on the C++ get_type()).

Also repairs two latent build breaks the branch carried: stale Step enum value usages
in test_slicing_pipeline_hook.cpp and a reference to the removed
ConfigOptionDef::PluginType::None in Tab::on_value_change (now is_plugin_backed()).
Adds the orca_gcode_stamp sample plugin and a psGCodePostProcess binding test.
2026-07-10 19:57:35 +08:00
SoftFever 7c8d086d9e feat(gui): show per-extruder nozzle count with inline editor
The sidebar extruder cards get an interactive title row — "<name> ( <count> )"
with an edit button — showing the extruder's physical nozzle count on
multi-nozzle printers (hidden elsewhere). Clicking it opens the existing
"Set nozzle count" dialog, which now also handles a Hybrid extruder by
offering both Standard and High Flow counts (an empty mix is rejected) and
shows a hotend thumbnail.

Because `extruder_nozzle_stats` is session-only (saved presets never carry
it, so preset switches rebuild the edited config without it), the stats are
re-baselined whenever they are missing: each extruder starts with
extruder_max_nozzle_count nozzles of its selected volume type. Switching an
extruder's flow type carries its total count over to the new type, except
when the stats came from a device sync — the machine-reported per-type
breakdown must survive a manual flow switch.

The badge refreshes on preset load, flow-type change, manual edit, device
sync, and project load. Single-extruder cards keep the title row but never
enable editing.
2026-07-10 19:47:21 +08:00
SoftFever c528d3a0bc feat(gui): enable Hybrid nozzle flow for multi-sub-nozzle extruders
An extruder with more than one physical sub-nozzle can hold a mix of
Standard and High Flow nozzles. The Flow dropdown now offers Hybrid for
such extruders (extruder_max_nozzle_count > 1, nil-guarded); grouping
already expands a Hybrid extruder into per-volume nozzle groups from
extruder_nozzle_stats.

- sidebar Flow combo offers Hybrid only for multi-sub-nozzle extruders
- preset lookup treats Hybrid as Standard (presets define no Hybrid
  variant); variant strings are never fabricated for it
- printer tab splits a Hybrid extruder into Standard + High Flow rows,
  with matching selection-index arithmetic and sync-enable rules
- syncing from a printer whose extruder holds mixed nozzle flows now
  selects Hybrid instead of collapsing to the dominant flow type
- send-to-printer flow check: a nozzle-rack extruder validates its
  nozzle inventory (mounted + rack) against every needed flow instead
  of comparing only the mounted nozzle; mounted-flow lookup is now
  per-extruder, fixing an index shift when a nozzle reports no flow
- Hybrid is session-only in app config (stored as Standard), so a
  fresh session starts from concrete flow types

Printers whose extruders have a single sub-nozzle (including all
dual-extruder machines without a rack) see no new option and identical
check behavior.
2026-07-10 18:02:26 +08:00
SoftFever 21ed68963f refactor(plugin): prefix SlicingPipelineStepPlugin values with pos/ps to mirror Print steps 2026-07-10 17:28:26 +08:00
SoftFever 83d2d4179d feat(profiles): enable cooling filter on H2D, H2D Pro, and H2S
Declare support_cooling_filter=1 on the three profiles (0.2/0.6/0.8
variants inherit from 0.4) and insert the cooling-filter conditional
into the H2D and H2S machine start g-code, inside the low-chamber-temp
airduct branch:

  {if(cooling_filter_enabled)} M145.2 P0 F0 {else} M145.2 P0 F1 {endif}

H2D Pro intentionally gets no g-code edit: its duct firmware takes the
filter mode over the device channel only, so the flag merely enables
the toggle.

Impact on existing users at default settings: H2D/H2S start g-code gains
exactly one line (M145.2 P0 F1, filter off) in the cool-chamber branch;
nothing is removed or reordered. The existing support_air_filtration=1
overrides are deliberately kept so exhaust-fan behavior for ABS-class
filaments is unchanged, even though the machine-tab row is hidden while
the cooling-filter toggle is shown.
2026-07-10 16:49:22 +08:00
SoftFever 826e36fb1e feat(printer): add cooling filter support flag and expose its toggle
cooling_filter_enabled existed as a config option but was shown nowhere,
and there was no capability flag to gate it. The cooling filter and air
filtration are alternative accessories sharing the same duct, so a
printer declares one or the other.

- new hidden printer capability flag support_cooling_filter
- "Use cooling filter" toggle in the Accessory group, shown only when
  the printer supports it; the air-filtration toggle hides in that case
  (no vendor restriction: third-party printers keep air filtration)
- explicit defaults (0) in the common machine base
- H2C declares support_cooling_filter=1 instead of support_air_filtration;
  its start-gcode already carries the cooling-filter conditional, so the
  toggle is functional. On H2C this drops the two exhaust-fan lines that
  air filtration emitted for ABS-class filaments, matching the printer's
  actual duct accessory; H2C is new on this branch so no existing user
  output changes.

Printers without the flag keep exactly the previous accessory UI and
g-code.
2026-07-10 16:38:46 +08:00
SoftFever b12aad6a8c feat(orient): auto-orient the largest overhang toward the cooling fan
New printer option fan_direction (undefine/left/right/both, default
undefine) declares which side the auxiliary part-cooling airflow comes
from. When set and the printer has an auxiliary fan, auto-orient adds a
yaw rotation so the dominant overhang area faces the airflow, and newly
added primitive shapes are pre-oriented the same way (except the Cube,
whose axis-aligned bounding box the pressure-advance pattern calibration
depends on).

- FanDirection enum + fan_direction printer option (Accessory group,
  enabled only with auxiliary_fan)
- orient engine: weighted overhang areas per candidate, yaw-direction
  search, vertical rotation applied on top of the primary orientation;
  the cooling weights are taken from the candidate actually chosen,
  including the flat-bottom tie-break
- orient_for_cooling() for primitive placement
- set fan_direction=left on H2C/H2D/H2D Pro/X1/X1E/P1S 0.4 profiles
  (X1C/H2S/P2S/X2D/Qidi X-Max 4 already carried the key, which now
  takes effect)

With fan_direction unset or no auxiliary fan the vertical rotation stays
identity and auto-orient results are unchanged; slicing and g-code are
never affected.
2026-07-10 16:21:48 +08:00
SoftFever 93a81179a1 feat(gui): add purge mode selector dialog
The engine already implements prime_volume_mode (Default/Saving/Fast)
but nothing in the UI could set it, leaving prime-saving unreachable on
multi-sub-nozzle extruders and fast purge unreachable on printers that
support it.

- new PurgeModeDialog with selectable Standard/Fast or
  Standard/Prime Saving cards depending on printer capability
- "Purge mode" sidebar button next to Flushing volumes; opens the
  dialog and stores the choice in the project config
- printer preset-load gating: button shown only when the printer has
  multiple sub-nozzles per extruder or sets support_fast_purge_mode;
  stale project values the printer cannot honor reset to Default
- enable fast purge on A2L 0.4 (support_fast_purge_mode), explicit
  default 0 in the common machine base
- new dialog strings added to OrcaSlicer.pot

Printers without these capabilities never show the button and their
projects keep prime_volume_mode at Default, so slicing output is
unchanged.
2026-07-10 15:41:57 +08:00
SoftFever b2eeed2622 feat(slicing): enforce filament flow-type restrictions in auto grouping
Filament grouping already consumed per-filament forbidden nozzle volume
types, but every call site passed an empty map, so a variant-restricted
filament (e.g. one limited to "Direct Drive TPU High Flow") could be
auto-grouped onto an incompatible nozzle flow type on multi-variant
printers.

- add convert_to_nvt_type() to parse extruder variant strings
- add Print::get_filament_unprintable_flow(): forbidden volume types =
  printer extruder variants minus the filament's declared variants;
  filaments declaring no variants stay unrestricted
- feed the map into grouping at the by-object path (Print.cpp) and all
  six mapping/planning sites in reorder_extruders_for_minimum_flush_volume
- unit-test the string parser

Non-restricted configurations produce an empty map, so existing
printers' grouping and g-code are unchanged.
2026-07-10 15:10:46 +08:00
SoftFever 9e8ac03476 test(filament_group): don't assert max_group_size cap for MatchMode
The FilamentGroup property/golden harness checked a per-extruder
max_group_size cap unconditionally in check_constraints. That cap is an
invariant of the flush-partition solvers only (calc_group_by_enum /
calc_group_by_kmedoids, reached via calc_filament_group_for_flush), which
partition filaments subject to each extruder's capacity.

MatchMode (calc_filament_group_for_match) does not partition by capacity:
it maps every filament to the extruder holding the nearest-color loaded
AMS filament, and its solver capacity is the used-filament count, not
max_group_size (FilamentGroup.cpp:1067). So a legitimate MatchMode result
can place more than max_group_size filaments on one extruder.

The prop_a/b/c_mode_match specs run MatchMode, and their scenarios are
generated with std::uniform_int_distribution / std::shuffle, which are
implementation-defined. For a fixed mt19937 seed, libc++ (macOS), libstdc++
(Linux) and MSVC (Windows) draw different scenarios, so the CI failure only
surfaced on Linux/Windows while macOS passed. Verified locally: 248/600
config-A MatchMode seeds exceed the cap under libc++ — it is reachable
everywhere; seed 90400 just isn't an exceeding draw on macOS.

Gate section 3 on FGMode != MatchMode. No test case is removed or skipped:
all 57 FlushMode specs still assert the cap, MatchMode still asserts the
unprintable-filament/volume correctness constraints (which it honors), and
MatchMode grouping regressions are still caught by the golden score gate at
3% tolerance. Test-only change; slicing behavior and g-code are unaffected.
2026-07-10 02:17:52 +08:00
SoftFever b58dcf5978 test(libnest2d): fix use-after-free crash in NfpPlacer lifetime tests
NfpPlacer stores std::reference_wrapper to the items it packs and re-reads
them from finalAlign() in its destructor (via clearItems()). Two placer
tests declared the placer before the items in the same scope, so the items
were destroyed first and the destructor dereferenced dangling references.

On macOS this is a deterministic SIGSEGV: libmalloc poisons the freed block
on free, so the item's point vector reads back as ~null (deref at 0x8). On
Linux/glibc the freed bytes usually survive, which is why it slipped through
upstream CI (introduced by #14267).

Declare the items before the placer so they outlive it, matching the pattern
the sibling 'packs many items' and 'obstacle' tests already use. Test-only;
the library lifetime contract (items must outlive the placer) is unchanged
and honored in production via _Nester in Arrange.cpp.
2026-07-10 01:55:00 +08:00
SoftFever 00816968a2 Merge branch 'main' into feature/h2c_support_clean 2026-07-09 22:17:20 +08:00
SoftFever 05ed2e4dfa Fixed a rare crash on app startup caused by ENOTSUP (errno 45) on Mac (#14686)
fix: prevent startup crash when preset-sync directory scan hits a transient FS error

On startup the user-preset sync thread scans the preset folder for orphaned
.info files (scan_orphaned_info_files). It iterated the directory with a
throwing boost::filesystem::directory_iterator while running on a background
thread that has no exception guard. On macOS, readdir() can intermittently
fail with ENOTSUP (errno 45); boost then throws filesystem_error, which --
uncaught on the sync thread -- calls std::terminate and aborts the whole
application on startup.

- Iterate with the error_code-based directory_iterator so a transient read
  failure is logged and skipped instead of thrown. The orphan scan is
  best-effort and re-runs on the next sync, so skipping a cycle is harmless.
  This mirrors the existing pattern in has_json_presets() and the plugin scan.
- Wrap the entire sync-thread body in try/catch as defense-in-depth, so no
  future uncaught exception on that otherwise-unguarded thread can abort the
  app.
2026-07-09 21:57:59 +08:00
SoftFever 844c374798 update colors 2026-07-09 19:42:21 +08:00
SoftFever f688a3e741 update the profile 2026-07-09 19:01:19 +08:00
SoftFever 0d31325df0 feat: Filament Track Switch (H2-series O2L-FTS) support
The Filament Track Switch (H2-series accessory, product code O2L-FTS) feeds
every AMS to both extruders through a two-track switch. Port full support
across the device layer, project config, and GUI.

Device / config:
- Model the switch-aware AMS binding (the set of extruders an AMS can feed
  and which input track A/B feeds it), switch readiness, the O2L-FTS firmware
  module, and the fun2 capability bit for checking a slice against installed
  hardware.
- Register has_filament_switcher and enable_filament_dynamic_map as project
  config that persists with the project and restores from a saved 3mf, and
  force both back to false on every project/printer/CLI load path. Live
  device sync is the only thing that sets them true.

GUI:
- Sidebar sync activates the switch from live device state, attributes each
  AMS to the extruder its input track feeds, shows a floating status icon
  (ready / not-calibrated), and surfaces a one-time tip / not-calibrated
  warning.
- Send dialog gains a non-blocking slice-vs-hardware mismatch warning and a
  blocking error when a slice needs dynamic nozzle mapping but the switch is
  missing or not set up.
- AMS load/unload guards, AMS-view routing glyph + un-calibrated banner +
  hidden external-spool road, and mapping-popup external-spool lockout.
- Filament pickers collapse the per-extruder split into a single deduplicated
  "AMS filaments" group with a smart-assign toggle when the switch is ready.
- Firmware-upgrade panel lists the O2L-FTS accessory and its version.
- Device-provided filament-change steps (ams.cfs) drive the change-step
  display when firmware sends them, including the three switch steps.
- "Load current filament" asks which extruder to feed via a
  FeedDirectionDialog when the switch is calibrated.

Inert without the accessory: every path is gated on the switch being
installed (MQTT aux bit 29, default off) or ready, both project flags default
false, and the per-extruder AMS attribution is byte-identical, so AMS state,
the send/load UI, and sliced g-code are unchanged for every printer that does
not report a Filament Track Switch.
2026-07-09 18:57:05 +08:00
SoftFever 26a0caff96 save exact (not standard-rounded) nozzle diameter in 3mf metadata 2026-07-09 13:13:29 +08:00
SoftFever 9810397546 test+i18n: multi-nozzle filament-group goldens and ported strings
Filament-group golden harness (config_a subset) and .3mf multi-nozzle round-trip tests, plus i18n msgids for the ported H2C/A2L strings.
2026-07-09 01:16:26 +08:00
SoftFever 28b7127150 feat(gui): multi-nozzle UI for H2C/A2L
Multi-nozzle sync widget, AMS rack-nozzle mapping popup, calibration rework, send-dialog nozzle mapping and extruder-count UI. Includes the fix to persist the AMS sync badge on filament cards (H2C/A2L and direct-sync printers).
2026-07-09 01:16:26 +08:00
SoftFever a098b483f6 feat(device): H2C/A2L device layer
Nozzle rack data model and device-tab panel, multi-nozzle sync, per-nozzle filament blacklist, and print-dispatch nozzle mapping (DevNozzleMappingCtrl V0/V1).
2026-07-09 01:16:25 +08:00
SoftFever 15412cd812 feat(profiles): Bambu Lab H2C and A2L profiles and resources
Machine models and presets, process and filament presets, plus bed models, covers, previews and device images for the H2C and A2L.
2026-07-09 01:16:25 +08:00
SoftFever 237ef41b06 feat(libslic3r): multi-nozzle slicing engine for H2C/A2L
Port BambuStudio's dual-nozzle slicing core: H2C-era config keys, filament-to-nozzle grouping with per-layer dynamic regrouping, filament/nozzle/hotend gcode placeholder vocabulary, multi-nozzle wipe tower pre-heat/pre-cool, the two-pass pre-cooling injector, and corexy farthest-point timelapse.
2026-07-09 01:16:25 +08:00
SoftFever 11dd078f64 feat(plugin)!: faithful mutable geometry bindings; edit slices via the class API
Replaces the plugin-only set_slices/set_fill_surfaces/set_lslices mutators with a
faithful, mutable binding of the core geometry types, so a plugin edits the slicing
graph through the same object model the C++ code uses.

- Point, Polygon, ExPolygon, Surface and SurfaceCollection gain constructors,
  writable accessors (contour/holes, set/append/clear, filter_by_type), transforms
  (rotate/scale/translate), boolean ops and offset. Polygon exposes a zero-copy
  writable numpy view via a make_writable_rows helper.
- LayerRegion.slices/fill_surfaces stay read-only refs but are now live,
  in-place-editable SurfaceCollections; Layer.make_slices() re-derives the islands
  and refreshes lslice bounding boxes.
- Rewrites the Inset and Twistify samples on the new API (in-place ExPolygon
  transforms, ExPolygon.offset, SurfaceCollection.set), dropping their numpy
  dependency; each touched layer calls make_slices() so downstream steps see the
  edited footprint. Adds tests covering in-place edits through a live collection.

BREAKING CHANGE: set_slices/set_fill_surfaces/set_lslices and the internal
parse_expolygon(_list)/surfaces_from_py helpers are removed. Plugins mutate through
the class API (SurfaceCollection.set/append/clear, Polygon.set_points/append,
ExPolygon.set_holes) instead.
2026-07-08 15:04:40 +08:00
SoftFever fd2a489980 Merge branch 'feat/plugin-feature' into feature/plugin-slicing
Brings in the Plugins dialog as-you-type search with fuzzy match highlighting
and clickable column-header sorting (name, version, source, status) — PRs
#14610 and #14611.
2026-07-08 01:03:34 +08:00
SoftFever f81a24abfb feat(plugin): expose the slicing print-graph as raw orca.host classes + Twistify sample
Adds PluginHostSlicing, which registers the print-graph data model (Print,
PrintObject, Layer, LayerRegion, Surface, ExPolygon, extrusions, ...) into the
orca.host submodule in the same raw-class style as PluginHostApi's Model/Preset
graph, with shared helpers in PluginBindingUtils. SlicingPipelinePluginCapability
is trimmed to the capability surface (the standalone SlicingNumpy helper is folded
away). Adds the Twistify example plugin next to Inset and broadens the binding,
hook, and plugin-install tests.
2026-07-08 00:05:28 +08:00
SoftFever a1ff45284c Fix version was not properly updated on non windows OS (#14617)
Fixed an issue where on non-Windows systems, the version was not properly written to the appconfig.
2026-07-06 22:16:28 +08:00
SoftFever aafcccc83c Merge branch 'feat/plugin-feature' into feature/plugin-slicing 2026-07-06 01:12:05 +08:00
SoftFever 8747605930 Fixes invalid inherits/compatible_printers/compatible_prints references that point at a deleted or renamed preset. (#14595)
* fix profile reference for Creality

* fix profile reference for Blocks

* fix profile reference for OrcaArena

* fix profile reference for re3D

* fix profile reference for Chuanying

* fix profile reference for Prusa

* fix profile reference for Wanhao France

* fix profile reference for MagicMaker

* fix profile reference for Afinia

Remove the ABS/ABS+/PLA/TPU/Value ABS/Value PLA filament presets that referenced the non-existent "Afinia H400 Pro" printer. The real printer is "Afinia H+1(HS)", already served by the @HS filament variants.

* fix profile reference for Comgrow

Remove the orphaned "0.20mm Standard @Comgrow T500 1.0" process preset and its process_list entry. Its only compatible printer "Comgrow T500 1.0 nozzle" never existed (the T500 model defines nozzle diameters 0.4/0.6/0.8 only).

* always run check_preset_references
2026-07-05 20:26:36 +08:00
SoftFever ee1f4ef1d6 Show preset name in cloud sync conflict and error messages (#14592)
The 409 conflict notification, the force-push confirmation dialog, and the payload-too-large (413) dialog now name the affected preset. The name was already parsed from the conflict body but never surfaced. The account-level preset-limit message stays generic since it isn't about one specific preset.
2026-07-05 17:05:41 +08:00
SoftFever 5ba5c6672d Fix reload from disk for STEP models after reopening a project (#12992) (#14591)
* Fix reload from disk for STEP models after reopening a project (#12992)

reload_from_disk matched reloaded source volumes with an exact
source.input_file string comparison. After a project is saved and
reopened, the stored source path is only the filename (the default,
non-full-path save) while a freshly re-imported volume carries a full
path, so the comparison never matched: reload fell into fail_list and
the "locate file" dialog was effectively useless for STEP models.

Fall back to a case-insensitive filename comparison when the exact
paths differ, so the existing same-folder source lookup (and the
locate dialog) can reload the model. Projects that stored absolute
source paths still match exactly as before; no 3mf format change.

* Add Preferences option to store full source paths in projects

Expose the existing export_sources_full_pathnames setting (previously
only editable in the config file) as a checkbox under Preferences >
General > Project. Enabling it stores absolute source paths in saved
projects, so "Reload from disk" works when the source file is kept in
a different folder than the project (companion to #12992).
2026-07-05 16:40:00 +08:00
SoftFever 5d8aa9610a unify orca web dialog style 2026-07-05 10:39:25 +08:00
SoftFever 383969d456 support dark theme for plugin progress dialog 2026-07-04 22:14:55 +08:00
SoftFever f47eee24d3 fix dark theme issue for plugin web dialog 2026-07-04 22:14:35 +08:00
SoftFever dd99549d20 fix gcode time estimiation error (#14573)
fix gcode estimiation error
2026-07-04 15:29:19 +08:00
SoftFever b0bacdd00b feat(plugin): add the slicing-pipeline plugin capability
Introduces a plugin capability that runs Python at the seams of Print::process(),
letting a plugin read and rewrite slicing state as it is computed.

- New slicing_pipeline_plugin config option; selected plugin refs are serialized
  into the print manifest.
- Print gains an injectable hook fired at each pipeline step (posSlice,
  posPerimeters, posInfill, ...). It is a no-op when unset, fires only on genuine
  (re)computation, and never on the use-cache path.
- orca.slicing submodule: SlicingPipelineCapabilityBase plus a trampoline and a
  Step enum. Capabilities read the live graph through zero-copy int64 numpy views
  (contour/holes geometry with unscaled coordinates, flattened toolpath data) and
  edit it through 2D-geometry mutators with cache-invariant refresh.
- GUI dispatcher runs capabilities during slicing under the GIL, turns plugin
  errors into slicing errors, honors cancellation, and adds the plugin picker.
- Ships the InsetEverySlice sample plugin and binding/hook tests.
2026-07-04 04:33:20 +08:00
SoftFever 4e26e7233e Merge branch 'main' into feat/plugin-feature 2026-07-04 00:19:39 +08:00
SoftFever 43bc76d9a9 update to python 3.12.13 and attempt to fix windows arm build 2026-07-03 22:30:58 +08:00
SoftFever 0918f9319c rename example plugin 2026-07-03 16:46:28 +08:00
SoftFever da04681289 rename example plugin 2026-07-03 16:41:54 +08:00
SoftFever 32756f0402 add orca plugin example 2026-07-03 16:30:28 +08:00
SoftFever 6c89dd4e50 expose more assemble api 2026-07-03 16:30:16 +08:00
SoftFever 6fd1176661 add status bar for plugin/capability status instead of message box 2026-07-03 14:57:35 +08:00
SoftFever a6ccbced03 Feat/update preset validator (#14507)
# Description

This PR expands profile validation so we can catch backward
compatibility issues with custom presets generated by older OrcaSlicer
releases. It also adds missing `renamed_from` metadata for presets that
were renamed or moved, so older user presets can resolve their original
parent names against the current system profiles.

## Background

Many users have reported missing preset issues after upgrading past
2.4.1. Investigation showed two common causes:

- preset lookup and compatibility checks did not always account for
`renamed_from`
- some renamed base presets were missing the old preset name in their
`renamed_from` metadata

The existing profile workflow validates the current system profile tree
and a single nightly-generated custom preset bundle. That is useful for
catching current profile errors, but it does not validate user presets
generated by older OrcaSlicer versions against the current system
profiles. As a result, older missing-parent compatibility gaps can slip
through.

## Changes

- Update `check_profiles.yml` to validate historical custom preset
fixtures from `OrcaSlicer/OrcaSlicer-profile-validator`.
- Download the fixture manifest from the public `fixture-archive`
release.
- Validate each `orca_custom_presets_<version>.zip` fixture
independently against the current PR's `resources/profiles`.
- Generate per-version validation logs and upload them as workflow
artifacts.
- Fail profile validation if any historical fixture version fails.
- Add missing `renamed_from` aliases for renamed/moved presets found by
the historical fixture validation.

## Profile Compatibility Fixes

This PR adds aliases for older parent names including:

- `0.20mm Bambu Support W @BBL X1C` -> `0.20mm Standard @BBL X1C`
- `Bambu PLA Impact @BBL X1C` -> `Bambu PLA Impact @System`
- `Ginger Generic rPLA` -> `Ginger Generic PLA`
- `Ginger Generic rPETG` -> `Ginger Generic PETG`
- legacy `Panchroma PLA Stain` BBL filament names -> current `Panchroma
PLA Satin` names
- legacy Elegoo casing/name variants such as `Elegoo RAPID PLA+`,
`Elegoo RAPID PETG`, `Elegoo RAPID PETG+`, and `Elegoo PETG Pro @System`

## Validation Flow

The custom preset validation step now:

1. Downloads `manifest.json` from the `fixture-archive` release.
2. Iterates over every fixture listed in the manifest.
3. Copies the current branch's `resources/profiles` into a temporary
profile tree.
4. Removes any existing `user` directory from that temporary tree.
5. Unzips exactly one historical fixture into the temporary tree.
6. Runs `OrcaSlicer_profile_validator -p <temp profile tree> -l 2`.
7. Writes a version-specific log and a consolidated summary.

This keeps validation scoped per fixture version and avoids mixing
generated user presets from different OrcaSlicer releases.

## Fixture Source

Historical fixtures are stored as public release assets in:

`OrcaSlicer/OrcaSlicer-profile-validator`, release tag `fixture-archive`

Each release asset is expected to be named like:

```text
orca_custom_presets_v2.4.1.zip
```

## Testing

Validated locally with:

- current system profile validation
- BBL filament subtype validation
- historical custom preset fixture validation
- extra profile JSON check in a clean profile tree

The affected historical fixture set passed after adding the missing
`renamed_from` aliases.

The release manifest controls which fixture versions are validated.
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-02 18:47:49 +08:00