AGENTS.md@src/partcad_ide_client · git:20261008.9f2d5f9 · 2026-10-08 · sha256 9d3a2b68e88c0598
AGENTS.md@src/partcad_ide_client git:20261008.9f2d5f9A
Immutable. This exact content never changes and is served at /api/v1/blob/9d3a2b68e88c0598.
# partcad_ide_client The Python half of the socket protocol that connects `partcad` to the PartCAD IDE extension's **PartCAD Viewer**. Source: `./src/partcad_ide_client`. Tests: `./tests/partcad_ide_client`. Run all commands below from the repo root unless noted. **A package, not a distribution.** Nothing is published under this name: it is one of the packages inside the single `partcad` wheel, so `pip install partcad` is what puts `import partcad_ide_client` on a machine. See "The PartCAD IDE viewer client" in [../partcad/AGENTS.md](../partcad/AGENTS.md) for the rationale, including why giving this directory a `pyproject.toml` of its own would be a mistake. The other half of the protocol lives in `ide/vscode/src/viewer/protocol.ts`. **A change to the wire format is a change to both files**, and the frame layout is specified once, in `src/partcad_ide_client/protocol.py` — that docstring is the normative description. ## Constraints Two properties of this package are deliberate and easy to break: - **No dependencies.** It lands in whatever interpreter `partcad` is installed into. Depending on anything (a CAD library above all — which is exactly what depending on `ocp_vscode` did) risks dragging a second, differently-provisioned stack into that interpreter, and would put that dependency into the `partcad` wheel along with it. Standard library only. - **It does not import `partcad`.** Geometry has already been tessellated into glTF by a PartCAD sandbox before it reaches here, so there is nothing to import. `partcad` imports *this*, lazily, from `partcad.viewer`. **One message is a question rather than a display: `state`**, which `pc ide state` sends. The IDE answers in the acknowledgement it sends every message, under `state` -- the Explorer's selection, the Inspector's object and the Viewer's tabs, filters, selections and a screenshot path; the shape is the comment beside `MSG_STATE`. An IDE from before it acknowledges it without a state, which is how `client.state()` tells "too old" (`StateNotSupported`) from "no IDE" (`ViewerNotAvailable`). It is the one asynchronous answer on the IDE's side -- two webviews are asked and a screenshot taken -- so `PartcadViewerServer.answerState` bounds it and fails with a sentence rather than leave `pc` blocked. A node names its geometry (`KEY_GLTF_REF`) rather than carrying it, and the root holds one entry per distinct shape (`KEY_GEOMETRY`). An assembly that places the same bolt a hundred times therefore sends that bolt once and names it a hundred times — possible only because a node's placement was never part of its geometry, which is the same property that lets the tree be composed at all. `is_node()` recognises both spellings: a reader that only knew `KEY_GLTF` would stop seeing every node of an assembly the day the geometry was shared. The glTF payload codec (`encode_gltf`/`decode_gltf`) has two other implementations that have to agree with it: `ocp_serialize.encode_gltf` in the sandbox, and `decodeGltf` in the extension. Neither can import this package, which is why each carries its own copy; `tests/partcad/unit/test_viewer.py` and the extension's `viewerProtocol.test.ts` are what catch a drift. ## Setup All commands run **inside the dev container** — see "Where commands run" in the root [AGENTS.md](../../AGENTS.md). Dependencies are already installed; re-run `poetry install` only after changing `pyproject.toml`. Prefix commands with `poetry run`. ## Test and validate changes ```bash poetry run pytest tests/partcad_ide_client -x -p no:error-for-skips -p no:warnings --dist no # matches CI poetry run behave # integration tests ``` The client tests stand up a fake IDE on an ephemeral port and point the client at it with `PARTCAD_IDE_PORT`, so they never collide with a PartCAD IDE the developer actually has open. To exercise the whole path — a real part, tessellated in a sandbox, over a real socket — run `dev-tools/fake_viewer.py`, which listens where the IDE listens, acknowledges every frame and prints each show with its control tree and the geometry and frames attributed to the items that draw them. Give it `--port` and the same port in `PARTCAD_IDE_PORT` to stay out of the way of a PartCAD IDE that is actually open, and remember that the process doing the show is usually the **daemon** rather than the `pc` you ran, one per workspace. `tests/partcad/unit/test_viewer.py` covers the core side with the sandbox stubbed out, `tests/partcad/unit/test_shape_tree.py` covers what the tree holds, and the sandbox side is covered by the render tests. ## Lint / format ```bash poetry run black --check src/partcad_ide_client tests/partcad_ide_client poetry run flake8 src/partcad_ide_client tests/partcad_ide_client poetry run isort --check --filter-files src/partcad_ide_client tests/partcad_ide_client ``` `black`, `flake8` and `isort` all gate now — each is a `pre-commit` hook and a `Lint (...)` job in `test.yml` — and the tree satisfies all three, so a finding from any of them is yours. flake8 only reads its configuration because `Flake8-pyproject` is installed; without it it checks at 79 columns. See the root [AGENTS.md](../../AGENTS.md). ## Commit `pre-commit` hooks (`dev-tools/pre-commit-config.yaml`) run `pytest`, formatting, and lint checks on commit and are required to pass in CI before a PR can merge.