ui: jobs with progress, refusals listed, datum preview, form filters and defaults

- A solve or a mesher runs as a job with one progress mechanism: the overlay
  shows the step, stage, count and elapsed time with a bar when bounded, OK
  waits while it runs, and the status sums it up (elements, smallest angle,
  time) (#322, #357).
- Over the control channel such a command is a job too: answered when it
  ends however long it runs, or at once with wait: false, then job.status,
  job.wait, job.cancel; ui.inspect gives job; MCP waits for it (#367).
- A command that cannot run yet lists what it misses in the context panel,
  each with a button to the command that provides it (#323).
- A cancelled or refused solve stays in its workspace (#324, test).
- A mesher form starts from the parameters of the mesh it would replace (#343).
- Seed Edges and edge/vertex/face datum points switch the selection filter
  while open and restore it (#341).
- Datum forms draw the datum live, the Partition form shows its datum, and
  new datums and partitions get the next free name (#347).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-15 09:14:34 -04:00
1 parent 3eaca26246
commit 4ee855bb27
11 files changed
+447 -28

No files matched your search

+18 -3
View File
@@ -17,14 +17,18 @@ The channel is untrusted input. A request line may be at most 16 MiB; a longer o
with `{"ok": false, "error": "request too large"}` and the connection is closed, as are
connections that send eight lines that are not JSON. At most 16 connections are served at once,
at most 64 queued requests run per frame so a flood cannot starve drawing, and a request the UI
thread has not answered within 60 seconds gets `{"ok": false, "error": "timeout"}`. Numbers are
thread has not answered within 60 seconds gets `{"ok": false, "error": "timeout"}` (a solve or a
mesher is a job, answered when it ends however long it runs: see [Jobs](#jobs)). Numbers are
checked and clamped (drag steps, wheel lines, window size); `ui.render` refuses an image size out of range.
## Engine
| Method | Params | Result |
|---|---|---|
| `engine.execute` | `command`: command id, `params`: JSON object | the command's result |
| `engine.execute` | `command`: command id, `params`: JSON object, `wait?` (default true) | the command's result; a solve or a mesher runs as a [job](#jobs) |
| `job.status` | — | `job`: the running job (`title`, `command`, `elapsed` seconds, `progress` `{step: {name, index, count}, stage, done, total}`, `fraction` 0–1 when bounded, `line` as the window shows it), or null |
| `job.wait` | — | the running job's result when it ends (null `job` when none runs) |
| `job.cancel` | — | stops waiting for the job (`cancelled`: what it was); the model stays as it was and every request waiting for it gets `{"ok": false, "cancelled": true}` |
| `engine.script` | `commands`: `[{command, params}, …]` | stops at the first error; `journal`: commands that changed the model so far |
| `engine.commands` | — | every command: `id`, `label`, `params` (the parameter documentation), `schema` (its parameters as JSON Schema) and `requires` (what the session must hold before it runs: `design`, `geometry`, `nodes`, `mesh`, `analysable`, `material`, `step`, `result`, …; `engine.execute` of a command missing one fails without doing anything, naming each missing piece and its toolbar entry) |
@@ -41,7 +45,7 @@ commands (files, undo, `model.info`) leave the ribbon where it is.
| Method | Params | Notes |
|---|---|---|
| `ui.inspect` | — | view settings (`ui`), camera, viewport rect (points), renderer (`gpu`/`cpu`) and adapter, open command form, status bar, node and element counts, the results display (`results`: what the `results.*` commands reply: view, `stale`, contour range, modes), the probed node (`probe`), `solve` (the title bar's Solve: `enabled`, and `unavailable` as `ui.toolbar`'s entries give it, listing everything `solve.run` misses), `busy` (the background job: a file opening, a solve, a mesher run from its form by a click on OK), `prompt` (`{"unsaved": path}`, `{"design": path}` (a design of another part opened over a meshed or defined model), `"recover"`, `"samples"`), `recent` (recent files), `updates` (docs/updates.md: `automatic`, `checked_at`, `skip`, `available` (a newer version the status bar offers), `busy`, `staged`, `unsupported`; null when the run does not check), `selected` (the browser's selected definition `{kind, name}`, or seed `{kind: "seed", edge}`), `browser` (each top-level browser node's setup state: `[{node, state, tip, add}]`, `state` `done`, `next`, `blocked` (⚠: its command cannot run yet; `tip` says what it needs and where), `todo`, `optional` or null for nodes that are no setup stage, `add` its ghost rows `{stage, label, command, status}`; a ghost row's region is `layout`'s `browser/add/<stage>`), `nav` (the left-drag mode), `layout` (each region of the window drawn last frame as `[x, y, w, h]` in points: `app_bar`, `ribbon`, `workspace_switcher`, `document_tab`, `app_bar:<action>`, `browser`, `viewport`, `nav_bar`, `axis_view`, `selection_bar`, `legend` (while results are shown), `context`, `command_line`, `timeline`, `toolbar_dropdown`, `palette`), version |
| `ui.inspect` | — | view settings (`ui`), camera, viewport rect (points), renderer (`gpu`/`cpu`) and adapter, open command form, status bar, node and element counts, the results display (`results`: what the `results.*` commands reply: view, `stale`, contour range, modes), the probed node (`probe`), `solve` (the title bar's Solve: `enabled`, and `unavailable` as `ui.toolbar`'s entries give it, listing everything `solve.run` misses), `busy` (the background job: a file opening, a solve, a mesher run from its form by a click on OK), `job` (that job as `job.status` gives it), `prompt` (`{"unsaved": path}`, `{"design": path}` (a design of another part opened over a meshed or defined model), `"recover"`, `"samples"`), `recent` (recent files), `updates` (docs/updates.md: `automatic`, `checked_at`, `skip`, `available` (a newer version the status bar offers), `busy`, `staged`, `unsupported`; null when the run does not check), `selected` (the browser's selected definition `{kind, name}`, or seed `{kind: "seed", edge}`), `browser` (each top-level browser node's setup state: `[{node, state, tip, add}]`, `state` `done`, `next`, `blocked` (⚠: its command cannot run yet; `tip` says what it needs and where), `todo`, `optional` or null for nodes that are no setup stage, `add` its ghost rows `{stage, label, command, status}`; a ghost row's region is `layout`'s `browser/add/<stage>`), `nav` (the left-drag mode), `layout` (each region of the window drawn last frame as `[x, y, w, h]` in points: `app_bar`, `ribbon`, `workspace_switcher`, `document_tab`, `app_bar:<action>`, `browser`, `viewport`, `nav_bar`, `axis_view`, `selection_bar`, `legend` (while results are shown), `context`, `command_line`, `timeline`, `toolbar_dropdown`, `palette`), version |
| `ui.set` | any view setting: `style` (`shaded_edges`, `shaded`, `wireframe`), `show_nodes`, `show_geometry`, `show_mesh`, `show_loads` (boundary condition and load symbols), `show_interactions` (reference points, couplings, contact and tie surfaces, rigid walls), `symbol_size` (the setup symbols' size on screen in points, 6–48, at any zoom), `perspective`, `dark`, `show_tree` (the browser), `show_timeline`, `nav_preset` (the mouse navigation: `fieldcraft`, `fusion`, `solidworks`, `inventor`, `alias`, `tinkercad`, `openscad`; an unknown id acts as `fieldcraft`) and `reverse_zoom` (both remembered per user), `workspace` (`prepare`, `model`, `analysis`, `results`), `tab` (the toolbar tab: `geometry`, `mesh`, `properties`, `assembly`, `steps`, `loads`, `interactions`, `solve`, `results`, `tools`; a tab of another workspace brings that workspace), `palette` (the command search open), `menu` (the open menu: `file`, `help`, `workspace`, `view`, `more`, or a panel's drop-down as `<tab>/<panel>` such as `mesh/Tet`; `null` closes it) | an unknown setting or value is an error; returns the settings |
| `ui.view` | `view`: `front`, `back`, `left`, `right`, `top`, `bottom`, `iso`, `home` (iso and fit), `fit` | every view frames the model as drawn (the deformed shape included; the setup's symbols keep their size on screen and are not framed) in the part of the viewport the overlays (`layout`'s `legend`, `selection_bar`, `axis_view`, `nav_bar`) leave free; `fit` keeps the direction of view. Opening another model, sample or design frames it in the home view (the same design opened again after an edit keeps the camera), and results that come up with part of the model out of sight are framed. Returns the camera |
| `ui.camera` | `yaw`, `pitch` (degrees), `distance?` (model units) | turns the camera to any orientation about its target: `yaw` about Z (0 looks along +Y, the front view; −45 is the iso view's, −90 the right view's), `pitch` the elevation above the XY plane; `distance` moves the eye (the zoom of an orthographic view). Angles must be finite and the distance above 0: the yaw is wrapped, the pitch clamped to ±90 and the distance to 0.001–10⁹; anything else is an error. A sequence of calls orbits smoothly. Returns the camera (`ui.inspect`'s, angles in radians) |
@@ -120,3 +124,14 @@ User clicks (title bar File menu, toolbar entries, Cmd/Ctrl+O, S, Shift+S), drop
files named on the command line go through native file pickers and open in the background
(`ui.inspect`'s `busy`); commands sent here take their paths as parameters and run at once
(`file.open`, `file.save_as`, `geometry.open`, `geometry.import.mesh`, `deck.write`).
## Jobs
`engine.execute` of a solve (`solve.run`, `solve.static`, `solve.nonlinear`, `solve.heat`,
`solve.frequency`, `solve.buckle`, `solve.explicit`, `solve.fluid`) or a mesher (`mesh.tet.generate`,
`mesh.hex.*`) runs it off the window thread, as the Solve button and a mesher form's OK do: the
window keeps drawing its progress (step, stage and count, elapsed time) with Cancel. The reply is
the command's result when it ends, however long that takes; with `"wait": false` it is
`{"job": n}` at once, and `job.status`, `job.wait` and `job.cancel` follow it. One job runs at a
time; another long command meanwhile is refused, naming the job. The MCP server's `solve` and
`mesh` tools wait for the reply.