first commit
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user