--- kind: reference --- # Plan: move the GUI out of the SDK into `orisys-studio` Status: **planned, then postponed (2026-09-30).** Nothing here is in progress. This document records what would move, what would stay, and the one coupling that makes the move non-trivial, so that the work can be picked up without re-deriving it. It is a plan, not a description of current behaviour — everything described below is still in the SDK exactly as listed. ## Why `examples/gui/` is not an example. It is a full Qt + open3d + pyqtgraph application — 30 modules, 4,815 lines — that ships inside the SDK wheel. The GUI belongs to the `orisys-studio` application, which already exists for that purpose. Leaving it in the SDK means the SDK ships, versions and installs an application it is not responsible for. ## Current state | Path | Modules | Lines | What it is | |---|---|---|---| | `examples/gui/` | 30 | 4,815 | The application: `viewer2d/`, `viewer3d/`, `widgets/`, `app.py`, `log_config.py` | | `examples/viz/` | 11 | 2,298 | **Shared** drawing/rendering helpers — see the coupling below | | `examples/pipeline/` | 6 | 577 | Sensor service, CSV recorder, profiling. **SDK support code, not GUI** | ### Good news: the SDK core does not depend on the GUI Verified by AST, not by grep: **no module under `src/orisys/` imports `gui`, `viz`, `pipeline` or `examples`.** The only match is `src/orisys/__init__.py` importing `orisys.pipeline`, which is the SDK's own module. The dependency runs one way, so the GUI can leave without touching the library. Two apparent hits are prose, not code: `src/orisys/configs.py` mentions `examples/` in a docstring about run-root layout, and `export_sensors.py` has an `Examples::` docstring section. The `configs.py` anchor is a real behavioural dependency to preserve — the run-root heuristic keys off a sibling `examples/` directory, so that directory must keep existing in the packaged layout even after `gui/` leaves it. ### The coupling: `viz/` is shared, so it cannot simply follow the GUI ``` examples/gui/ ──uses──▶ examples/viz/ ──uses──▶ orisys (SDK) examples/pipeline/service.py ──uses──▶ examples/viz/ tests/test_contact_arrow_scale.py ──uses──▶ examples/viz/ ``` `examples/pipeline/service.py` and a test depend on `viz`, and `viz` is a mix: `mesh_camera.py`, `contact_arrow.py`, `flow_arrow.py` and `offscreen_mesh.py` are open3d rendering helpers, while `render_2d.py`, `recording.py`, `signal_filters.py` and `force_charts.py` are toolkit-independent. **Moving `viz/` wholesale would break `examples/pipeline/`.** It has to be split, not moved. ### The real coupling is in the tests, and it is inverted `tests/test_qt_viewer_backend.py` imports `examples/gui` directly, and `tests/conftest.py` inserts `EXAMPLES_ROOT` into `sys.path` with a guard whose stated reason is that **`examples/gui` must win over `build/gui`, because both are packages named `gui`**. So the SDK's test suite currently depends on the GUI application being present and on a name-resolution guard existing for it. This is the inversion to fix: the SDK must not depend on the GUI. Two related facts: - `examples/gui/` contains committed `__pycache__` directories (`adapter.cpython-310.pyc`, `cpython-313.pyc`, …) — build artefacts tracked alongside source. - `tests/test_wheel_contents.py` and `tests/test_pyproject.py` assert on what the wheel bundles, so they will need updating when the `gui/**/*.py` bundle pattern goes. ## What moves, what stays **Moves to `orisys-studio`:** - `examples/gui/**` — the whole application, as the single home for GUI code. - `examples/finger_pipeline_3dview.py` — a 91-line launcher for the dashboard; its only purpose is to start the GUI, so it belongs with the GUI. - The open3d rendering half of `examples/viz/`: `contact_arrow.py`, `flow_arrow.py`, `mesh_camera.py`, `offscreen_mesh.py`, plus the open3d-dependent `__init__` re-exports. - `tests/test_qt_viewer_backend.py` — it tests the GUI, so it moves with the GUI. **Stays in the SDK:** - `examples/pipeline/` — SDK support code (sensor service, CSV recording, profiling). - `examples/viz/` toolkit-independent half: `render_2d.py`, `recording.py`, `signal_filters.py`, `force_charts.py` — needed by `examples/pipeline/service.py`. - `examples/plot_data.py` — uses pyqtgraph for force charts only; 96 lines. Decide separately whether it follows the GUI; it is the one remaining example with a Qt dependency after the move. - `src/orisys/finger/viewer.py` (`UVMeshViewer`) — **see the deprecation note below.** ## The public-API problem, handled separately `UVMeshViewer`, `init_uv_mesh_viewer` and `update_uv_mesh_viewer` are exported in `src/orisys/finger/__init__.py`'s `__all__`. **Removing them is a breaking change to a released API**, so it must not be bundled into a code move: 1. Release with them present and documented `DeprecationWarning`s emitted on construction. 2. Remove them at the next major version, with the release notes naming the replacement. The audit (`docs/api-audit.md`) already lists `UVMeshViewer` among the surface that is used by examples and absent from the reference. ## Sequence, lowest risk first | # | Step | Risk | Notes | |---|---|---|---| | 1 | Add `docs/plan-gui-extraction.md` (this file) | none | Done | | 2 | Delete the committed `__pycache__` under `examples/gui/` and add it to `.gitignore` | none | Pure hygiene; ship independently | | 3 | Tag the SDK at the commit whose `examples/gui` is known-good, so the studio move has a pinned source | none | A tag, not a submodule or subtree | | 4 | In `orisys-studio`: import `examples/gui` + the open3d half of `viz` as its own provenance-stated change, reconciling against its existing viewer code | **medium** | The studio repo already has viewer code; this is a merge decision, not a copy | | 5 | In the SDK: move `tests/test_qt_viewer_backend.py` out, and simplify the `conftest.py` guard | **medium** | The guard exists for `gui`; with `gui` gone, `EXAMPLES_ROOT` may still be needed for `viz`/`pipeline`. Re-derive, do not guess | | 6 | In the SDK: retire `examples/gui/`, `finger_pipeline_3dview.py`, and the moved `viz` modules | low once 4 and 5 land | | | 7 | Update the bundle patterns in `pyproject.toml` (drop `gui/**/*.py`, adjust `viz/**`) and the wheel tests | low | `test_wheel_contents.py`, `test_pyproject.py` | | 8 | Update `examples/README.md` and the SDK reference to stop pointing at the dashboard | low | The README already says the GUI is moving | | 9 | Deprecate `UVMeshViewer` in a release; remove at the next major | low | Separate release cycle | Steps 2 and 3 are safe to do immediately and independently. Steps 4–5 are the actual move and need both repositories. Nothing here requires a library behaviour change, which is what makes the move tractable. ## Open questions for the studio side - **Merge or adopt?** `orisys-studio` already has its own viewer. If its code is the descendant of this one, take the SDK copy only where it is ahead; otherwise choose one and delete the other. This must be decided before step 4, because the answer changes what gets imported. - **Do the SDK's 2D and 3D viewers need to keep working headless for CI?** `examples/viz` contains offscreen rendering used to produce evidence images. If any SDK or sensor-design workflow depends on that, the offscreen half may need to stay reachable from the SDK even after the app moves. - **Does `plot_data.py` follow the GUI?** It is an example, not an application, and it is the last Qt-dependent file in `examples/`. ## Acceptance criteria - [ ] `src/orisys/` still imports no `gui`/`viz`/`examples` module (re-run the AST check). - [ ] The SDK test suite passes with `examples/gui/` absent. - [ ] No module named `gui` ships in the wheel; `test_wheel_contents.py` asserts it. - [ ] `examples/pipeline/service.py` and `tests/test_contact_arrow_scale.py` still import their `viz` dependencies. - [ ] `UVMeshViewer` is either deprecated-but-present or removed by an explicit major-version decision — never dropped silently in a move. - [ ] The `examples/` sibling directory still exists in the packaged layout, because `configs.py`'s run-root anchor depends on it.