first commit

This commit is contained in:
2026-07-22 13:48:46 +08:00
commit c87751c3dc
2820 changed files with 726976 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
# Backend Storage
CAD Viewer uses a small backend interface so the same React app can run against
local files during development and hosted object storage in deployment. Client
code talks to HTTP routes and catalog URLs; it does not read filesystem paths
directly.
## Interface
Backend implementations expose this core shape:
```js
{
kind,
readCatalog({ rootDir, fileRef }),
refreshCatalog({ rootDir, fileRef }),
resolveFileAssetAccess({ fileRef, asset, catalog }),
}
```
Trusted upload tooling for hosted storage serializes catalog JSON and uploads it
as `catalog.json`. Writable backends may expose
`writeAsset({ fileRef, body, contentType })` and
`generateStepArtifact({ fileRef, force, catalog })`.
Local filesystem backends also expose helpers used by Vite and the local
production server:
```js
{
resolveRoot(rootDir),
openFileAsset({ fileRef, asset, catalog }),
assetPathForFileRef(fileRef, { resolvedRoot }),
entryForSourcePath(catalog, resolvedRoot, sourcePath),
contentTypeForPath(filePath),
}
```
`readCatalog()` returns catalog JSON from the backend's source of truth.
`refreshCatalog()` lets an adapter update or regenerate that in-memory view.
Writable helpers may write servable CAD assets such as hidden STEP GLBs or run
local CAD generation.
## Local Filesystem
`src/server/localAssetBackend.mjs` is the development and local deployment
implementation. `readCatalog()` and `refreshCatalog()` scan
the absolute `?dir=` root for the current request, keep the catalog as an
in-memory object, and return schema v4 entries whose `file` values are absolute
paths plus `rootRelativeFile` values for URL navigation. The local backend does
not write `catalog.json` or any hidden catalog cache file.
Local filesystem deployments are intentionally URL-driven. `?dir=` may be
absolute or relative to the directory where the Viewer was started; when omitted
it defaults to the startup `--dir`, or to the startup directory if `--dir` was
not passed. That default directory is also the first active directory. `?file=`
values are always relative to the active `?dir=` directory.
`VIEWER_LOCAL_ROOT_DIR`, `VIEWER_LOCAL_WORKSPACE_ROOT`, and the old fixed-root
startup flag have been removed and now fail at startup.
The local backend serves asset bytes from the active root and writes regenerated
artifacts back into it. It rejects path traversal and only serves or writes
supported CAD Viewer asset types.
Local STEP GLB/topology regeneration calls the Python `cadpy` package. The root
viewer carries a generated, installable copy under `viewer/packages/cadpy`; run
`scripts/bundle/bundle-skill.sh cad-viewer` after changing `packages/cadpy`, then
install `viewer/requirements.txt` into the Python runtime used by the viewer.
The generated cad-viewer skill runtime bundles that same installable package
under `scripts/viewer/packages/cadpy` and does not need the repository root.
Vite dev mounts this backend for:
- `GET /__cad/server`
- `GET /__cad/catalog`
- `GET /__cad/asset?file=...`
- `GET /__cad/download?file=...&asset=output|source`
- `POST /__cad/reveal?file=...&asset=output|source`
- `POST /__cad/step-artifact`
`download` streams the requested asset bytes and works for both local and hosted
deployments. `reveal` opens the asset in Finder or the platform file manager and
is only available for the local filesystem backend. `asset=output` resolves the
catalog entry file itself; `asset=source` resolves optional source code, such as
a same-stem Python generator for Python-backed STEP files.
The local production server uses the same backend:
```bash
npm run build
npm run serve
```
Then open the printed server URL with
`?dir=/absolute/root&file=model.step`. Pass `--port <number>` to
`npm run serve --` only when the default production port is already in use.
## Vercel Blob
`src/server/vercelBlobAssetBackend.mjs` is the hosted storage adapter. Vercel
deployments construct it in read-only mode: the hosted API reads the catalog and
serves public Blob assets, but it does not write Blob objects or regenerate STEP
artifacts.
Hosted catalog reads are deliberately conservative about Blob traffic.
Sustained per-request fetches of the public catalog URL from shared serverless
egress IPs trip Vercel's abuse mitigation with intermittent `403 Forbidden`
responses, so the hosted backend caches the parsed catalog in-function for 60
seconds (serving the last good catalog if a refresh fails), hosted
`/__cad/catalog` responses carry `s-maxage`/`stale-while-revalidate`
cache-control so the Vercel CDN absorbs client polling, and hosted viewer
builds poll the catalog every 60 seconds instead of the local 2-second
development cadence.
Expected deployment shape:
- Build the frontend normally.
- Run a trusted local upload/maintenance script to publish supported assets and a
schema v4 `catalog.json` containing public Blob URLs.
- Deploy the viewer with `VIEWER_ASSET_BACKEND=vercel-blob` and a read-only
catalog configuration. Catalog warnings such as stale STEP artifacts are shown
in the UI instead of being fixed by the hosted app.
- Hosted Vercel routes ignore local filesystem `?dir=` query values and read the
configured hosted `catalog.json` instead.
The Vercel deployment entrypoints are intentionally thin:
```text
api/cad/server.js
api/cad/catalog.js
api/cad/download.js
api/cad/reveal.js
vercel.json
```
`vercel.json` rewrites the client-facing routes below to those API functions:
```text
/__cad/server
/__cad/catalog
/__cad/download
/__cad/reveal
```
The local production server can mount the Blob catalog path by environment:
```bash
VIEWER_ASSET_BACKEND=vercel-blob \
VIEWER_VERCEL_BLOB_PREFIX=https://<store-id>.public.blob.vercel-storage.com/models2 \
npm run serve
```
Upload a local directory catalog and supported viewer assets with:
```bash
VIEWER_VERCEL_BLOB_READ_WRITE_TOKEN=... \
VIEWER_VERCEL_BLOB_PREFIX=models2 \
npm --prefix viewer run upload:blob -- models
```
`BLOB_READ_WRITE_TOKEN` may be used instead of
`VIEWER_VERCEL_BLOB_READ_WRITE_TOKEN`, matching the environment variable Vercel
Blob integrations create.
The uploader also reads `.vieweruploadignore` from the uploaded directory and
accepts repeated `--ignore-file` options for gitignore-style exclude patterns.
It excludes `mechbench/`, `mechbench2/`, `7dof_arm/`, and Python source files
by default, and public Blob catalogs omit Python source paths and URLs. Add
`--skip-existing --fetch-missing-lfs` for publish-style runs that compare
against the remote catalog, skip matching assets, and fetch only the Git LFS
objects needed for new or changed uploads. The repository publish wrapper,
`scripts/viewer/upload-viewer-models-catalog.sh`, owns the branch-defined Blob
path prefix for model catalog uploads. Catalog uploads use Vercel Blob's
minimum supported cache TTL so in-place `catalog.json` overwrites are visible
quickly, while large immutable model assets keep the default Blob cache policy.
Hosted catalog reads also append a catalog cache-busting query parameter so the
viewer does not inherit stale edge entries from earlier catalog overwrites.
For token-free read-only deployments, `VIEWER_VERCEL_BLOB_PREFIX` should be the
public Blob URL for the prefix directory. The hosted backend always reads
`catalog.json` at the root of that prefix. Trusted local upload or maintenance
scripts may use the path prefix, such as `models2`, with a read/write token.
The writable Blob helper methods remain available to local scripts that import
the backend directly and pass a Blob read/write token. They are not exposed by
the hosted Vercel API.
@@ -0,0 +1,114 @@
# MoveIt2 Server
The MoveIt2 server is an optional CAD Viewer backend module for SRDF IK and
path-planning controls. It is disabled for normal hosted viewer use and is only
needed when a user explicitly wants interactive solve or plan-to-pose controls.
## Install
From the viewer package or generated cad-viewer skill runtime directory:
```bash
npm run moveit2:setup
```
The setup script creates or updates the `moveit2-server` conda environment from
`moveit2_server/environment.yml`, installs the Python package in editable mode,
and runs a server import check.
Use a ROS 2 / MoveIt2 capable conda installation. Do not install ROS 2 or
MoveIt2 into the repository CAD `.venv`.
Useful environment variables:
```text
MOVEIT2_SERVER_CONDA_ENV_NAME
MOVEIT2_SERVER_CONDA_EXE
MOVEIT2_SERVER_REPO_ROOT
MOVEIT2_SERVER_HOST
MOVEIT2_SERVER_PORT
```
## Run
From the viewer package or generated cad-viewer skill runtime directory:
```bash
npm run moveit2:check
npm run moveit2:serve
```
The websocket endpoint defaults to:
```text
ws://127.0.0.1:8765/ws
```
CAD Viewer connects to that URL in local dev when MoveIt2 controls are enabled,
unless `VIEWER_MOVEIT2_WS_URL` or the browser `?moveit2Ws=` query parameter
overrides it.
## Hosted Backend
The MoveIt2 module is a long-running websocket backend, not a Vercel serverless
function. Host it in a ROS 2 / MoveIt2 capable VM or container by running the
same Python package and exposing its websocket URL:
```bash
MOVEIT2_SERVER_HOST=0.0.0.0 \
MOVEIT2_SERVER_PORT=8765 \
MOVEIT2_SERVER_REPO_ROOT=/srv/cad-viewer \
npm run moveit2:serve
```
Point the viewer at that endpoint with `VIEWER_MOVEIT2_WS_URL` at build time or
`?moveit2Ws=` at runtime. Production viewer builds keep MoveIt2 disabled unless
a websocket URL is explicitly configured by the deployment or user.
## Protocol
Requests use `protocolVersion: 1`.
Supported request types:
- `srdf.solvePose`
- `srdf.planToPose`
Use native joint-value fields:
- `startJointValuesByName`
- `jointValuesByName`
- trajectory `positions`
Legacy `*Deg` fields remain compatibility aliases. Angular values convert
between degrees and native radians; prismatic joints stay linear.
Pose targets require `target.frame`, `target.xyz`, and `target.endEffector`.
Set `target.targetLink` or `moveit2.targetLink` when the desired TCP is not
obvious.
Orientation can be provided as exactly one of:
```json
"quat_xyzw": [0, 0, 0, 1]
```
or:
```json
"rpy": [0, 0, 0]
```
If no orientation is provided, `moveit2.ik.positionOnly` defaults to true. If
`positionOnly` is false, an orientation must be supplied.
## Notes
- The server has no Python import dependency on the SRDF skill. It includes the
SRDF metadata parser it needs under `moveit2_server/moveit2_server`.
- Success depends on the linked URDF, SRDF, collision geometry, solver
availability, and MoveIt environment.
- The generated MoveIt config defaults are suitable for viewer smoke tests but
are not universal production planning settings.
- Cache invalidation is based on linked URDF/SRDF file size and modification
time.
+101
View File
@@ -0,0 +1,101 @@
# Browser Storage
CAD Viewer has four browser persistence tiers. Choose the smallest tier that
matches the lifetime and sharing behavior the user expects.
This doc covers browser state only. Catalogs, CAD assets, hidden STEP
GLB/topology artifacts, and hosted Blob uploads are backend concerns; use
[backend.md](./backend.md) for that interface.
## URL Query Params
Use query params only for shareable state that should survive copying a URL:
- `file`: active catalog entry, always relative to the active `dir` directory.
- `dir`: local filesystem directory to scan. It may be absolute or relative to
the directory where the Viewer server was started. When omitted, the Viewer
uses the active directory remembered for the tab, or the directory where the
server was started when that is the only active directory.
- `moveit2Ws`: explicit MoveIt2 websocket override for local or hosted sessions.
Do not put dense viewer state, panel state, drawing state, or per-file controls
in the URL.
## localStorage
Use `localStorage` sparingly. It is durable across tabs, browser restarts, and
unrelated CAD Viewer sessions, so it should only hold global preferences.
Current intended use:
- `cad-viewer:theme`: global saved-theme library, active theme id, and saved
appearance settings.
Avoid adding file-specific state to `localStorage`. If the value depends on the
selected file, the active root directory, a generated asset hash, or a tab
interaction, it belongs in per-file session state instead.
## Directory sessionStorage
Use directory-level `sessionStorage` for temporary app-wide UI state that should
survive reloads in the same browser tab, should not become a durable global
preference, and should not vary by selected file. Use
`src/client/workbench/persistence.js` rather than creating one-off storage keys.
Current keys:
```text
cad-viewer:directory-session:v1
cad-viewer:active-dir:v1
```
Current `cad-viewer:directory-session:v1` fields:
- `fileViewerOpen`: app-wide file viewer open/closed state.
- `fileViewerExpandedDirectoryIds`: app-wide open folder ids for the file
viewer tree. When absent, the first selected file on page load seeds the
initial expanded folder tree; an empty array means all folders are closed.
- `fileViewerWidthPx`: app-wide custom file viewer width, stored only when it
differs from the default.
- `fileSheetOpen`: app-wide file sheet open/closed state.
- `fileSheetWidthPx`: app-wide custom file sheet width, stored only when it
differs from the default.
- `theme`: app-wide unsaved theme settings for the current tab. Saved theme
presets and the active saved preset id still belong to `localStorage`.
`cad-viewer:active-dir:v1` stores the tab's preferred directory after a `?dir=`
URL is loaded or a directory is selected.
Do not put selected-file state, model controls, drawing state, or
generated-asset decisions in directory session state. Those belong in per-file
session state.
## Per-File sessionStorage
Prefer per-file `sessionStorage` for viewer state that should survive reloads in
the same browser tab without becoming a durable global preference. Use
`src/client/workbench/fileSessionState.js` rather than creating one-off storage
keys.
Per-file state is namespaced by the active root directory and keyed by file:
```text
cad-viewer:file-session:v1:<namespace>:<fileKey>
cad-viewer:file-session:index:v1:<namespace>
```
Per-file session state is intentionally tab-local. Do not sync these keys from
`storage` events; two tabs viewing the same file must be free to keep different
camera, display, tool, and sheet settings.
Existing slice intent:
- `tab`: file sheet section expansion, reference selection, part visibility,
camera, tools, and drawing history.
- `dxf`: DXF preview thickness and bend settings.
- `stepModule`: STEP module enablement, parameter values, and animation state.
- `urdf`: joint values and motion-planning controls.
- `largeFile`: large-file decisions such as selectable topology opt-in.
When adding another large-file control, reuse the `largeFile` slice instead of
adding a separate session storage key.