mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-06-10 22:12:49 +00:00
docs(automation): design spec for file.open runtime model-loading method
Adds the approved design for a `file.open` JSON-RPC method that loads files into a running OrcaSlicer via Plater::load_files (synchronous, GUI thread). Dialog-driving approaches deferred.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# Design — `file.open` automation method (runtime model loading)
|
||||
|
||||
**Date:** 2026-06-03
|
||||
**Branch:** `feature/automation`
|
||||
**Status:** Approved for implementation
|
||||
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
Today a model can be loaded into OrcaSlicer **only at process launch**: the model path
|
||||
is passed as a CLI positional arg and OrcaSlicer's normal startup file-loading ingests
|
||||
it. The JSON-RPC automation protocol has **no** load method, so swapping or adding a
|
||||
model in an already-running instance requires a fresh process launch.
|
||||
|
||||
Driving the native File→Import dialog via `input.click` is not a viable substitute: the
|
||||
OS file picker is not a `wxWindow`, so it never appears in the `tree.dump` hierarchy
|
||||
(`WxUiBackend::dump_tree` walks `wxGetApp().mainframe` children only), and `input.click`
|
||||
can only target nodes resolved from that tree (no raw-coordinate click). Blind typing via
|
||||
`input.type` is mechanically possible but unobservable: the native picker is not a
|
||||
`wxDialog`, so `app.state().modal_dialog` and `sync.wait_for` cannot gate on it, leaving
|
||||
only sleep-and-hope timing. A direct API method is the clean fix.
|
||||
|
||||
## Goal
|
||||
|
||||
Add a `file.open` JSON-RPC method that loads one or more files into a running instance by
|
||||
calling `Plater::load_files(...)` directly on the GUI thread. Out of scope: any
|
||||
dialog-driving mechanism (intercept hook or true OS-level drive) — explicitly deferred.
|
||||
|
||||
---
|
||||
|
||||
## Protocol
|
||||
|
||||
- **Method:** `file.open`
|
||||
- **Params:** `{ "paths": ["C:/abs/a.stl", ...] }`
|
||||
- A bare string is also accepted: `{ "paths": "C:/abs/a.stl" }`.
|
||||
- Paths must be **absolute**. The server reads them from the host filesystem
|
||||
(client/server are localhost-only).
|
||||
- **Result:** `{ "ok": true, "loaded": <count> }`
|
||||
- `count` is `load_files(...).size()` — the number of objects added to the scene.
|
||||
- **Errors:**
|
||||
| Code | Constant | Condition |
|
||||
|------|----------|-----------|
|
||||
| 1002 | `kInvalidParams` | `paths` missing/empty, or a non-string entry |
|
||||
| 1004 | `kErrGuiBusy` | GUI-thread marshal timed out (`m_gui_timeout_ms`) |
|
||||
| 1007 | `kErrLoadFailed` | `load_files` returned empty / threw (not found, parse error, unsupported format) — **new code** |
|
||||
|
||||
## Semantics — synchronous
|
||||
|
||||
`Plater::load_files` runs and completes on the GUI thread. The backend marshals via the
|
||||
existing `run_on_gui(m_gui_timeout_ms, …)` helper and returns only after the load
|
||||
finishes. Consequently, when `file.open` returns `ok:true`, `app.state().project_loaded`
|
||||
is already `true` — there is no polling race.
|
||||
|
||||
Rejected alternative — async "fire-and-poll-`project_loaded`": adds client complexity and
|
||||
loses a definitive per-call error result, with no benefit since loading is synchronous.
|
||||
|
||||
**Caveat:** an extremely large model could exceed `m_gui_timeout_ms` and surface as
|
||||
`1004 kErrGuiBusy`. Documented; not mitigated in v1.
|
||||
|
||||
## Load strategy (v1 minimal)
|
||||
|
||||
Pass the default `LoadStrategy::LoadModel | LoadStrategy::LoadConfig` (identical to
|
||||
drag-drop / `Plater::load_files`'s default) with `ask_multi = false`. This already routes
|
||||
`.3mf` files as projects and meshes as models based on file content, so **no `as_project`
|
||||
flag is needed in v1**. A future `{ "as_project": bool }` flag remains possible but is not
|
||||
implemented now.
|
||||
|
||||
---
|
||||
|
||||
## Components / files to touch
|
||||
|
||||
Follows the existing `screenshot_window` / `app_state` method pattern.
|
||||
|
||||
1. **`src/slic3r/GUI/Automation/IUiBackend.hpp`** — add pure-virtual
|
||||
`int open_files(const std::vector<std::string>& paths)` returning the loaded count,
|
||||
throwing `AutomationError` on failure. Header stays wx-free (no `LoadStrategy` leak).
|
||||
2. **`src/slic3r/GUI/Automation/WxUiBackend.{hpp,cpp}`** — implement `open_files`:
|
||||
`run_on_gui(m_gui_timeout_ms, …)` → `wxGetApp().plater()->load_files(paths, default_strategy, false)`;
|
||||
throw `kErrLoadFailed` if the returned vector is empty.
|
||||
3. **`src/slic3r/GUI/Automation/JsonRpcDispatcher.{hpp,cpp}`** —
|
||||
- add `constexpr int kErrLoadFailed = 1007;`
|
||||
- declare + define `m_file_open(params)` (param parsing/validation; accept string or
|
||||
array; require ≥1 non-empty string path)
|
||||
- add dispatch route `if (method == "file.open") return make_result(id, m_file_open(params));`
|
||||
- add `"file.open"` to the capabilities array in `m_version`.
|
||||
4. **`tests/automation/MockUiBackend.hpp`** — `open_files` override recording the paths
|
||||
vector + a configurable return-count (and a throw/fail knob).
|
||||
5. **`tests/automation/test_dispatcher.cpp`** — Catch2 v2 tests:
|
||||
- array of paths → routes to backend, returns `loaded` count
|
||||
- bare-string path → normalized to one path
|
||||
- missing/empty `paths` → `1002`
|
||||
- backend load failure → `1007`
|
||||
- `automation.version` capabilities array includes `"file.open"`
|
||||
6. **`tools/automation/orca_automation.py`** — `open(self, paths)` wrapper (normalize
|
||||
`str` → `[str]`, send `file.open`).
|
||||
7. **`tools/automation/example_slice.py`** — launch **without** a model arg, then
|
||||
`orca.open([model])`, then wait for `project_loaded`.
|
||||
8. **`doc/automation.md`** — document method (params/result/errors), add to the
|
||||
capabilities list, method index, and error table (`1007`).
|
||||
|
||||
---
|
||||
|
||||
## Testing / verification
|
||||
|
||||
- **Build (Windows):** `cmake --build . --config RelWithDebInfo --target ALL_BUILD -- -m`.
|
||||
- **Unit:** `automation` Catch2 suite green including new tests (≈31 → ≈34 cases).
|
||||
- **Manual:** launch with `--automation-server` and **no** model arg → call `file.open`
|
||||
→ confirm `app.state().project_loaded` flips `true` and `screenshot.window` shows the
|
||||
model.
|
||||
- **Gating:** unchanged — the server only runs under `--automation-server`, so the method
|
||||
is a no-op (unreachable) when automation is disabled.
|
||||
|
||||
## Backward compatibility
|
||||
|
||||
Additive only: a new method, a new error code, and a new capabilities entry. No change to
|
||||
existing methods, profiles, project-file handling, or default behavior.
|
||||
Reference in New Issue
Block a user