Commit Graph

29822 Commits

Author SHA1 Message Date
SoftFever
2717c37f63 feat: Python Plugins (#14530)
# Introducing a Python Plugin System (WIP)

This PR opens up a way to extend OrcaSlicer with **Python plugins** —
small scripts (or full wheels) that run inside an embedded CPython
interpreter, without anyone having to fork the app or touch the C++
core.

I'm putting this up **early and on purpose**. It works end-to-end today,
but it is not finished and the public surface is deliberately small.
Before we lock anything in, we want the community's opinions on the
three decisions that are hard to reverse later: **what API we expose,
which plugin types we invest in, and how the security/audit layer should
behave.** Consider this a request for comments more than a merge
candidate.

## Why

People keep wanting to bolt their own behavior onto the slicer — custom
G-code post-processing, automation, bespoke printer/host integrations,
one-off analysis. Today that means maintaining a patched fork. The goal
here is a *sanctioned* extension path: a stable, documented seam where a
plugin can hook into a specific point in OrcaSlicer's workflow, with a
clear boundary around what plugin code is allowed to do.

## What's in this PR

**An embedded Python runtime.** A single CPython interpreter is started
once (intended to be on the main thread), with proper GIL handoff so
plugin code can run from worker threads. Plugin `stderr` (including
tracebacks from threads a plugin spawns) is persisted to
`data_dir()/log/python_*.log`.

**One API module, `orca`.** This is the surface a plugin sees. It
exposes the plugin base classes, the `@orca.plugin` decorator and
`register_capability()`, a typed `ExecutionResult`, and the
`PluginType`/`PluginResult` enums, along with per-type base classes
under `orca.gcode` / `orca.script` / `orca.printer_agent`. A host
bridge, `orca.host`, provides **read-only** access to the current model
and preset/config values, plus interactive `host.plater()` and `host.ui`
helpers (messages, dialogs, windows, progress). There is deliberately no
*write* access to slicer models or config, and no general GUI/toolkit
access beyond these host helpers. The exact shape of `orca.host` is one
of the things we most want feedback on.

**Three plugin types to start:**
- `post-processing` — runs during G-code export and receives the G-code
path + output context.
- `script` — a manual "Run" action from the Plugins dialog.
- `printer-connection` — a Python "printer agent" that registers into
the network layer on load. This is still WIP, along with a printer agent
workflow that is also WIP.

(The `PluginType` enum reserves several more names — Automation,
Analysis, Importer, Exporter, Visualization — but only the three above
are wired up.)

**Two packaging forms:** a single `.py` file with [PEP
723](https://peps.python.org/pep-0723/) inline metadata, or a `.whl`
wheel (with third-party dependencies installed via a bundled `uv`).

**Discovery, install, and a Plugins dialog** — local side-loading plus a
cloud subscription service, catalog/loader lifecycle, and per-plugin
error reporting in the UI.

**Audit-hook groundwork (PEP 578).** Every C++→Python call opens a
per-call audit context, and a CPython audit hook filters filesystem
access against a write allow-list (`data_dir()`, plus scoped roots like
the current G-code folder). This is *groundwork, not a sandbox* — see
Limitations.

**Docs.** Substantially complete author and contributor guides live
under `docs/plugins/` (development guide, security/audit deep-dive,
architecture overview, worked examples); the *feature* is what's WIP,
not the docs.

## Orca Cloud integration

Plugins are **fully integrated with Orca Cloud**, distributed in a
similar way to preset bundles — so this builds directly on the cloud
foundation rather than bolting on a separate mechanism.

- **Subscribe, don't side-load.** Instead of manually copying files, you
subscribe to a plugin from the cloud and OrcaSlicer pulls it down and
loads it for you — the same one-click experience as preset bundles.
- **Tied to your account, synced across machines.** Subscribed plugins
live under your user (`orca_plugins/_subscribed/<user_id>/`) and follow
you to any machine you're signed in on, exactly like your presets. Sign
out and the cloud plugins are unloaded; sign back in and they're
restored.
- **Stays up to date.** When a new version is published, OrcaSlicer can
fetch and install the update rather than leaving you on a stale copy.
- **Managed from the Plugins dialog.** Browse, install, update, and
unsubscribe live alongside local side-loading — which still works for
development and private plugins.
- **Integrated with presets.** Plugin references travel with a preset
bundle (via the preset's `plugins` fields), so publishing a preset that
uses plugins carries those references through the existing cloud sync.
If a referenced plugin is missing when you install the bundle on another
machine, OrcaSlicer **offers to install** the missing plugins for you (a
one-click prompt), provided those plugins are on the cloud.

## Where we need feedback

Really any form of feedback would be helpful; we'd rather grow this
slowly from real use cases than expose internals we can't keep stable —
which is why the current surface is kept small. The `orca.host` API in
particular is where we'd most value opinions.

## Limitations / known gaps (it's WIP)

- **The audit hook is not a sandbox.** It currently enforces only the
`open` event's writes, and only for string paths (fd/bytes opens are not
checked). `subprocess`, sockets, `ctypes`, `os.open`, and non-`open`
filesystem mutations (`os.remove`/`rename`/`mkdir`) are **not** blocked
yet. An `Enforcing` mode is stubbed but not yet wired, so today all
calls run in the writes-only "loading" mode. More details can be found
[here](https://www.orcaslicer.com/wiki/developer_reference/plugin_development/plugin_audit_hook.html#limitations).
- **The `orca` API is unstable** and will change based on this
discussion. Don't build anything load-bearing on it yet.
- The `requires-python` field is parsed but not enforced.
- Dependency install and some of the Plugins dialog UX are functional
but still rough around the edges.

## Docs
[How to
Use](https://www.orcaslicer.com/wiki/plugins/getting_started.html)
[Developer
Reference](https://www.orcaslicer.com/wiki/developer_reference/plugin_development/plugin_system.html)

## Software Development Kit
Currently, there is a script `generate_orca_python_stubs.py` to generate
the `.pyi` files that can be used for intellisense. We will release the
stub file as an SDK in future releases, but for now, if you intend to
develop plugins, you can generate the stub files locally.

## Notes

This system was developed primarily on Windows and Linux; testing on
macOS has so far been limited. macOS-specific behavior — the bundled
Python/`uv` runtime, path handling, and the audit hook — is the most
likely to need attention, and feedback or testing from macOS users is
especially welcome.

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)

## Orca Cloud to OrcaSlicer Plugins Workflow Overview:


https://github.com/user-attachments/assets/abbd7900-3062-4e33-8f77-5d30d567be1d
2026-07-17 23:39:18 +08:00
SoftFever
3576ed6445 Merge branch 'main' into feat/plugin-feature 2026-07-17 23:38:34 +08:00
SoftFever
ee2e7e7a28 Add actions speed dial to run plugin scripts (#14697)
# Description

Use Space to trigger a new **speed dial**, which allows users to run
**app actions**. The only app actions implemented currently are python
plugin scripts.

## Notes

- Only toggleable in the Prepare (3D) view. Press Space, type to filter,
Enter or double-click to run.
- Focuses on search bar automatically
- Frecency-sorted (run count + recency) with alphabetical fallback (C++
computes)
- Pin actions as favourites and they will show on the top favourites
bar. Done through star icon on each row.
- Script plugins have no icon art yet, so tiles show a collision-aware
monogram: the capability's initial, escalating only when names collide -
prepend the package initial, then add an ordinal - so same-named actions
from different plugins stay distinguishable.
- E.g., capability Bravo from plugin Alpha normally shows just B. If
another action's name also starts with B, they disambiguate by
prepending the package initial (Alpha -> AB). If two still collide on
both initials (both AB), they become AB1 and AB2.
- "Run X?" confirm with a per-plugin "don't ask again" scope, owned
C++-side; suppression persists.
- Persistence (`speed_dial` AppConfig section): `favourite_actions`
(ordered id list) + per-action `stats` + `ask_suppressed`.

- Example of shape in data_dir:
```json
{
    "speed_dial": {
        "ask_suppressed": "[\"9b12aa079924bbc4\"]",
        "favourite_actions": "[\"ccfdf8b9e492b624\",\"9b12aa079924bbc4\",\"b7abfa67626248e4\"]",
        "stats": "{\"31d9d129a616a8b7\":{\"count\":4,\"last\":1783924989},\"53ec17d430634f62\":{\"count\":3,\"last\":1783939329},\"9b12aa079924bbc4\":{\"count\":5,\"last\":1783924981},\"9f2cb0d3ca56a87c\":{\"count\":1,\"last\":1783668370},\"b7abfa67626248e4\":{\"count\":4,\"last\":1783939325},\"f93469da14248128\":{\"count\":3,\"last\":1783939337}}"
	},
}
```

# Screenshots/Recordings/Graphs

<img width="688" height="335" alt="image"
src="https://github.com/user-attachments/assets/683efa3b-9401-4977-a347-d70193188165"
/>

<img width="681" height="172" alt="image"
src="https://github.com/user-attachments/assets/fafb4965-054b-4fd5-ad6a-03145264fbe0"
/>

<img width="683" height="335" alt="image"
src="https://github.com/user-attachments/assets/00d5bd49-95c0-4f2f-966b-6c04c14c3cf3"
/>


## Tests

- **Web layer (green):** node-vm logic test `test-speeddial-logic.js`
covers `filterActions`, `visibleFavourites` (incl. the runnable guard),
`selectedActionId`, `resultCountText`, `actionLabel`, `tileCode`,
`nextSel`, and payload seeding - pure helpers, DOM-free.
- **Backend:** `ActionRegistry` FNV-1a id golden-vector Catch2 test
(`test_speed_dial_action_id`) pins the hash; the registry was verified
by fresh-context review including a threading fix (`run()` operates on a
stack copy so a queued refresh can't reallocate the action vector
mid-run).
- **Manual (all passing):**
- Space opens the dial in Prepare only; no regression to existing
Prepare-tab keys or the Plugins dialog.
- Search auto-focuses; typing filters live; a freshly-run action rises
in the frecency order.
- Up jumps to the favourites bar, Down into the list, Alt+1..9 hits
favourites; Enter and double-click run the highlighted action.
- Star pins/unpins an action; favourites persist across an app restart.
- "Run X?" confirm with per-plugin "don't ask again" is respected on
later runs.
  - The `?` shortcuts dialog shows the Space row.
  - Verified in both light and dark themes.

## Known Issues

When there are no actions, plugins, or scripts, the search bar will show
"Search 0 actions". This is bad UX. One alternative considered was to
show a call to action, for example "Please load plugin scripts so that
they appear here".

However, this is ultimately not implemented, as eventually it is not
expected that actions will be empty. The action registry will not be
expected to be empty because we will include in-app actions such as
opening dialogues or other app actions.

<img width="722" height="117" alt="image"
src="https://github.com/user-attachments/assets/a4b9c0db-b2bf-44bc-9550-398dd8b4c7aa"
/>

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-17 23:36:34 +08:00
SoftFever
ee4fca1493 clean up 2026-07-17 23:04:42 +08:00
SoftFever
2614ef7d63 Defer speed-dial web commands off the webview callback stack 2026-07-17 22:37:56 +08:00
Ian Chua
1c82b88c0a fix: unit test shutting down pythonintepreter 2026-07-17 21:56:04 +08:00
Ian Chua
dda49a2b7b Merge branch 'feat/plugin-feature' into feature/speed-dial 2026-07-17 21:29:25 +08:00
Ian Chua
5e15a67425 fix: deprecate show_dialog api and add an option to create_window params 2026-07-17 21:12:55 +08:00
SoftFever
f0f496e9c3 Merge branch 'feat/plugin-feature' into feature/speed-dial
Adapt the speed dial's ActionRegistry to the collapsed
get_plugin_capability(PluginCapabilityId) overload, and restore the
script success/skipped status message the dialog lost when its
PluginScriptRunner refactor was superseded by ActionRegistry.
2026-07-17 19:32:44 +08:00
SoftFever
f6ff15ed99 Feat/plugin config (#14746)
# Description

This PR introduces persistent, capability-scoped configuration for plugins. Users can configure plugins through the Plugins dialog instead of manually editing the plugin’s Python source.

Configuration exists at two levels:

- **Global** — one configuration per capability, shared by every preset. Edited in the Plugins dialog’s **Config** tab.
- **Per preset** — an optional override stored on a process or printer preset, edited from that preset’s **Plugin Preferences** group. A preset that overrides a capability configures the slices it drives; presets that do not simply use the global configuration.

Plugin authors can use the built-in JSON editor or provide a custom HTML settings interface. Both levels use the same editor and the same stored shape.

## New Plugin Configuration APIs

The following APIs are available to all plugin capability types.

### Python APIs

- `get_config() -> str` — Returns a raw JSON string of the capability’s effective configuration: the active preset’s override if it has one, otherwise the global configuration, otherwise `{}`.
- `save_config(config) -> bool` — Persists JSON-compatible configuration for the capability and returns whether the write succeeded. Always writes the **global** configuration (see [Preset overrides](#preset-overrides)).
- `get_config_version() -> str` — Returns the plugin version that last saved the configuration `get_config()` returned, from that same level, or `""` if it has never been saved.
- `has_config_ui() -> bool` — Override and return `True` to use a custom configuration interface instead of the built-in JSON editor.
- `get_config_ui() -> str` — Returns the HTML used to render the custom configuration interface.
- `get_default_config() -> str` — Returns a raw JSON string of the configuration applied by **Restore defaults** in the Plugins dialog. The default implementation returns `{}`.

### Custom UI JavaScript APIs

Custom configuration interfaces receive a sandboxed `window.orca` bridge:

- `window.orca.getConfig()` — Returns the current capability configuration.
- `window.orca.saveConfig(config)` — Requests that the host persist the supplied configuration.
- `window.orca.onConfig(callback)` — Immediately invokes the callback with the current configuration and invokes it again after successful saves or restores.

`saveConfig()` is asynchronous and does not return a Promise. Custom interfaces should use `onConfig()` to observe the successfully persisted state.

## Plugins Dialog

Every activated capability appears in the Plugins dialog’s **Config** tab, where its global configuration is edited.

The editor shown for a capability is selected as follows:

- If `has_config_ui()` returns `True` and `get_config_ui()` returns valid, non-empty HTML, the dialog renders the custom interface.
- Otherwise, the dialog renders the built-in JSON editor.
- If a custom interface cannot be loaded, the dialog reports the error and falls back to the JSON editor.
- The **Restore defaults** action replaces the stored configuration with the value returned by `get_default_config()`.

Custom interfaces run in a sandboxed iframe and can access configuration only through the provided `window.orca` bridge.

## Preset overrides

Process and printer presets gain a **Plugin Preferences → Capabilities** setting (Advanced mode). Its **Configure** button opens a dialog listing the capabilities that preset actually uses — the ones its `plugins` manifest declares *and* one of its plugin-backed options points at — and edits each one’s configuration for that preset alone. The button shows the number of overrides the preset carries.

That dialog offers two actions:

- **Save** — stores the edited configuration as this preset’s override.
- **Restore defaults** — discards the preset’s override, so the capability falls back to the global configuration. A preset holding no override *is* a preset at its defaults.

### How a running capability reads its configuration

`get_config()` resolves in this order:

1. The active preset’s override for this capability, if it has one.
2. The global configuration in `config.json`.
3. `{}`.

Which preset is consulted follows from the capability’s type. A plugin-backed option declares the capability type it accepts (`ConfigOptionDef::plugin_type`) and belongs to exactly one preset type, so `slicing-pipeline` capabilities are configured by the process preset and `printer-connection` capabilities by the printer preset. Nothing is hardcoded: declaring `plugin_type` on a new option is all it takes to place a new capability type on that map.

`get_config_version()` reports the version stamp from whichever level supplied the configuration, so a plugin migrating a stale config is never handed one level’s data with another level’s version.

`save_config()` from Python always writes the global configuration, never a preset — presets are the user’s to edit, and a plugin saving from a worker thread cannot mark one dirty. A capability whose active preset overrides it will therefore keep reading that override back rather than what it saved.

### Storage

A preset’s overrides live in an ordinary string setting on the preset (`plugin_preference_overrides`), holding a JSON array of entries keyed by plugin and capability. Because it is an ordinary setting, the whole preset lifecycle carries it for free: the dirty marker, the revert arrow, inheritance, project (3MF) round-tripping, and preset sync all behave exactly as they do for every other setting. The dialog is a pure editor over that text — it never writes to the preset itself and never writes to the global config file.

## Configuration Storage

All global plugin configuration is stored in a shared file:

`data_dir()/orca_plugins/config.json`

Configuration entries are isolated by plugin and capability. The host also records the plugin version that last wrote each entry.

The configuration file is intentionally stored outside individual plugin directories. This allows settings to survive:

- Plugin upgrades and reloads
- Local plugin deletion and reinstallation
- Cloud plugin unsubscribe and resubscribe operations

Reinstalling or resubscribing to the same plugin restores access to its previously saved configuration.

## Known limitations

**Filament capabilities cannot be overridden per preset.** There is no single active filament preset — one is selected per extruder — and `get_config()` does not say which extruder the capability is running for, so a filament override could only be applied by guessing. Rather than hand a plugin another extruder's settings, filament capabilities read the global configuration.

Nothing reaches this today: no filament option declares a `plugin_type`, so no capability type maps to the filament preset. Lifting it means pushing the extruder onto the plugin call context the Python trampoline already maintains and resolving the preset from that, with the extruder optional — whole slicing steps (`posSlice`, `psGCodePostProcess`) span every extruder and have no current filament.

# Tests

`tests/slic3rutils` covers the capability config store, the Python config API, the preset override layer, the capability-type → preset-type mapping, and which capabilities a preset counts as in use.

# Screenshots/Recordings/Graphs

Custom UI
<img width="855" height="703" alt="image" src="https://github.com/user-attachments/assets/745ecb7d-9e20-4c39-b857-5aa730a27142" />

Default JSON text editor
<img width="855" height="703" alt="image" src="https://github.com/user-attachments/assets/18b7b89c-6f77-4960-a9b2-964e71f74fc3" />

Process Sidebar
<img width="717" height="360" alt="image" src="https://github.com/user-attachments/assets/8fac3e66-c06a-44e4-ad4b-4cc6c003bb2b" />

Filament dialog
<img width="1090" height="832" alt="image" src="https://github.com/user-attachments/assets/ff6a4cbe-11c0-4d04-9ecb-9a717bdeb3f4" />

Printer settings dialog
<img width="1090" height="832" alt="image" src="https://github.com/user-attachments/assets/c616afb0-4eb2-40c2-92c0-7f5edc50b4e6" />

Dialog opened from preset settings
<img width="860" height="725" alt="image" src="https://github.com/user-attachments/assets/069408a8-e94b-47e0-8e16-a81e0d58b4d4" />

# Example plugin with custom UI used in screenshot
[custom_ui_screenshot_demo.py](https://github.com/user-attachments/files/29995212/custom_ui_screenshot_demo.py)

<!--
> 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-17 18:45:51 +08:00
SoftFever
a152c7bcad Name the plugin capability config option consistently
The preset option was plugin_preference_overrides while the GUI field type
that renders it was GUIType::plugin_config, for one and the same thing.
Settle on "config": the store, the dialog and the Python hooks all say config
already, so renaming that way touches 4 files instead of the whole plugin
subsystem and the public plugin API.

Keep the _overrides suffix — the option is the preset's override layer over
the base PluginConfig store, a distinction EffectiveCapabilityConfig tracks.

Also wrap the printer tab's group heading in L(); it was the only one of the
three missing it, and was therefore untranslatable.
2026-07-17 18:34:15 +08:00
SoftFever
cc471b95b6 Skip arm64 Linux and Windows builds on self-hosted runners (#14811)
There is no arm64 self-hosted build server, so when \`vars.SELF_HOSTED\`
is set the arm64 Linux and Windows legs previously fell back to
GitHub-hosted runners. Drop those legs entirely instead, along with the
unit test jobs that consume their artifacts.

**Changes:**

- **Linux / Windows builds:** matrices switch from a static \`include:\`
list to \`fromJSON(vars.SELF_HOSTED && ... || ...)\`, so self-hosted
runs build x86_64/x64 only. The Windows job's per-arch runner
conditional is gone — the runner is now baked into each matrix entry.
- **Unit tests:** \`unit_tests_linux_aarch64\` and
\`unit_tests_windows_arm64\` are gated on \`!vars.SELF_HOSTED\`; their
\`needs\` still succeed, so \`success()\` alone would not skip them.
- **Slice check:** the profile validator artifact is now named per-arch
and uploaded from the aarch64 leg normally, or x86_64 on self-hosted.
The job's runner and download name follow the same switch, keeping the
gate alive rather than failing on a missing artifact.
- **Comments:** trimmed across the touched blocks.

No change when \`SELF_HOSTED\` is unset — an unset variable is falsy, so
GitHub-hosted runs keep both arches, the same runners, and the aarch64
slice check.
nightly-builds
2026-07-17 16:09:40 +08:00
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
Ian Chua
4c60781cf4 fix: merge branch feat/plugin-feature 2026-07-17 14:02:10 +08:00
Andrew
b4f6573753 Merge branch 'feat/plugin-feature' into feature/speed-dial
# Conflicts:
#	src/slic3r/GUI/PluginsDialog.hpp
2026-07-17 13:59:56 +08:00
Ian Chua
96182e778a fix: merge pythonjsonutils into pluginfsutils 2026-07-17 13:35:51 +08:00
SoftFever
895a5c8d6b Skip arm64 Linux and Windows builds on self-hosted runners 2026-07-17 12:21:29 +08:00
SoftFever
b663f851fe Merge branch 'main' into feat/plugin-feature 2026-07-17 11:30:34 +08:00
Rodrigo Faselli
33909a51cd Fix external outline Thickness (#14741)
* Fix external outline

* Format and values

* frag_color -> gl_FragColor

* Remove Transform3d& view_matrix

* Clarify rendering comments in 3DScene.cpp

Updated comments to clarify rendering process using stencil buffer.

---------

Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
2026-07-17 09:04:36 +08:00
Alexandre Folle de Menezes
979e56f1d0 Standardize the Unicode chars for unit strings (#14631)
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2026-07-16 17:37:09 -03: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
Ian Chua
d9f8460a4e fix: merge PythonFileUtils into PluginFsUtils 2026-07-16 20:40:32 +08:00
Heiko Liebscher
671277b381 Update German (de) translation (#14577) 2026-07-16 09:11:09 -03:00
Mqrius
6d1974a662 Restore flow calibration special order, overridable via fill order (#14786) 2026-07-16 09:10:15 -03: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
Ian Chua
a86202b45b fix: change speed dial from popup to dialog for consistent behavior on linux 2026-07-16 19:47:26 +08:00
Ian Chua
f142716e51 fix: change popup to dialog with popup behavior 2026-07-16 18:29:40 +08:00
Ian Chua
74ffc46bbc fix: syntax 2026-07-16 18:22:55 +08:00
Ian Chua
f4414dd72b fix: collapse plugincapabilityid 2026-07-16 18:18:07 +08:00
Andrew
30170839c1 Merge branch 'feat/plugin-feature' into feature/speed-dial 2026-07-16 17:15:24 +08:00
Andrew
1c33983edf Confine plugin lifecycle callbacks to UI thread
Drop the orphaned PluginCallbackList (dead
after the PluginManager migration) and hop
run_on_*_callbacks onto the UI thread via
CallAfter, snapshotting under the mutex on the
worker first. Keeps wx subscribers off the
detached load/unload workers.
2026-07-16 17:08:03 +08:00
Andrew
13575601ff Give speed-dial popup GUI_App ownership 2026-07-16 16:09:30 +08:00
Andrew
13f5c777cc Rename SpeedDialDialog files to SpeedDialPopup 2026-07-16 16:09:30 +08:00
Ian Chua
49396699e7 fix: initialize embedded python from bundled runtime 2026-07-16 15:40:13 +08:00
Andrew
9279f86858 Migrate from PluginLoader to PluginManager
Refactor to streamline plugin management within
ActionRegistry by consolidating interface usage.
2026-07-16 13:32:40 +08:00
Andrew
c816e48b78 refactor: collapse speed-dial action source abstraction
Only one action source ever existed, so the
IActionSource interface and ScriptActionSource
are gone. ActionRegistry now subscribes to the
plugin loader and enumerates actions directly
in init() - no polymorphism for one impl.

Replace the opaque FNV-hash SpeedDialActionId
with a readable composed id of the form
prefix:title:source_key. Split AppAction's
single source field into source_key (stable
identity, e.g. plugin_key) and source_name
(display), so identity and display no longer
share one field.
2026-07-16 13:08:48 +08:00
Ian Chua
b6f98d9592 fix: load default config on initial load 2026-07-16 12:57:59 +08:00
Ian Chua
a15dcdfb3f move configure to plugin preferences in printer settings dialog 2026-07-16 12:16:22 +08:00
Ian Chua
525a16177c fix: bundle python runtime for macos plugin tests 2026-07-16 12:06:45 +08:00
Noisyfox
5e7ad1ad70 Fix OpenGL framebuffer object support on macOS (#14769)
* Fix OpenGL framebuffer object support on macOS
this enables the outline on macOS

* Revert "Fix OpenGL framebuffer object support on macOS"

This reverts commit 9872708954.

* Enable ARB framebuffer if OpenGL >= 3.0 (OrcaSlicer/OrcaSlicer#14770)

* FIX: plater toolbar image update

jira: STUDIO-12457

Change-Id: Ia855b4bd9bec884d52202f5d8f5cfad9efce0f2f
(cherry picked from commit 474691813b497654ef701fe7073992db366e81a9)

* FIX: thumbinal regen

jira: STUDIO-16732

Change-Id: I4cc4e76b36caf1a41793c1f84ee5e88a44ebdd42
(cherry picked from commit 53f33b7db260005e477842d6459b1b28aa8aa0d8)

* FIX: force update thumbinal once dirty

jira: STUDIO-13041

Change-Id: I770743b53c51351cb3b8a7133140974c75c05364
(cherry picked from commit 96198a12712f21d2b4521b6e3d1caff50bed21ff)

* FIX:thumbnail update

jira: STUDIO-13041 / STUDIO-13304

Change-Id: I9dec25f12454fc8485195cf5dc3feb421b6347c4
(cherry picked from commit cf778dc90f805d3f46e28f8f1d1d5814d3fb4d51)

---------

Co-authored-by: jun.zhang <jun.zhang@bambulab.com>
2026-07-16 11:02:55 +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
c8f70c6ca7

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
Ian Chua
8ba478913f fix merge conflicts 2026-07-15 22:09:47 +08:00