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.
5.7 KiB
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).
- A bare string is also accepted:
- Result:
{ "ok": true, "loaded": <count> }countisload_files(...).size()— the number of objects added to the scene.
- Errors:
Code Constant Condition 1002 kInvalidParamspathsmissing/empty, or a non-string entry1004 kErrGuiBusyGUI-thread marshal timed out ( m_gui_timeout_ms)1007 kErrLoadFailedload_filesreturned 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.
src/slic3r/GUI/Automation/IUiBackend.hpp— add pure-virtualint open_files(const std::vector<std::string>& paths)returning the loaded count, throwingAutomationErroron failure. Header stays wx-free (noLoadStrategyleak).src/slic3r/GUI/Automation/WxUiBackend.{hpp,cpp}— implementopen_files:run_on_gui(m_gui_timeout_ms, …)→wxGetApp().plater()->load_files(paths, default_strategy, false); throwkErrLoadFailedif the returned vector is empty.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 inm_version.
- add
tests/automation/MockUiBackend.hpp—open_filesoverride recording the paths vector + a configurable return-count (and a throw/fail knob).tests/automation/test_dispatcher.cpp— Catch2 v2 tests:- array of paths → routes to backend, returns
loadedcount - bare-string path → normalized to one path
- missing/empty
paths→1002 - backend load failure →
1007 automation.versioncapabilities array includes"file.open"
- array of paths → routes to backend, returns
tools/automation/orca_automation.py—open(self, paths)wrapper (normalizestr→[str], sendfile.open).tools/automation/example_slice.py— launch without a model arg, thenorca.open([model]), then wait forproject_loaded.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:
automationCatch2 suite green including new tests (≈31 → ≈34 cases). - Manual: launch with
--automation-serverand no model arg → callfile.open→ confirmapp.state().project_loadedflipstrueandscreenshot.windowshows 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.