Files
OneCAD-Simulation/docs/control-protocol.md
T
0e97f8dd1d Results display (M4) and offscreen full-UI capture (#28)
* ui.capture: the whole window drawn offscreen and rasterised on the CPU

ui.screenshot falls back to it when no frame is presented (hidden window,
locked screen), so agents can look at panels and the toolbar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Draw the open design and imported surfaces in the viewport

DisplayMesh keeps node labels on every element primitive (for results),
gains add_surface (geometry faces and their boundaries, coincident points
welded) and displaced (elements moved by nodal offsets). Bodies are
tessellated at a thousandth of their size; meshed models show the geometry
as boundaries only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* results.* commands: deformed shape, contours, mode shapes, animation, reaction sums

The results display is session state (ResultsView), set by commands that are
not edits. Nodal fields (displacement, von Mises, principal and component
stresses; principal stresses in closed form after Smith 1961) and reaction
force and moment sums per set. Results kept from an earlier revision stay
viewable, marked stale; solve.probe says when a result is stale. The Results
tab's display and reaction entries are ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Results display: deformed shape, contours with legend, mode shapes and animation, probe, reaction sums

The viewport draws the session's results view: elements moved by their nodes'
displacements or a mode shape (automatic or given scale), faces split exactly
along contour band boundaries into flat-coloured pieces (same on GPU and CPU),
a legend with levels and extremes, a results panel whose controls run the
results.* commands, a click probing the nearest node (solve.probe), and stale
results marked. Solving from the form shows the result on the Results tab.
sample::cantilever is a solvable model for tests and demos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Simplify the results display (review)

One Contour in the plot; the results scene reuses scene::build; a solve shows
its result through the engine (ResultsView::for_result) and the UI raises the
Results tab when a view appears; the plot's scale and contour values are kept
across animation frames; design bodies are tessellated once per design; the
first content is framed where the display is rebuilt; field_values in one
match; probe reuses results::displacements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Results display for explicit steps: field frames, energy histories

The results view works on the engine's step results (static, frequency,
explicit): ResultsView.frame and results.frame {frame | time} pick an explicit
field frame; the deformed shape (true scale) and contours (element stresses
averaged to the nodes, velocity) follow it; animation runs through the frames.
The Results panel has a frame slider and plots the kinetic, internal, hourglass
and external energies with their values at the frame. sample::impact is a
steel bar flying onto a rigid wall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs/features.md after the rebase

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:23:05 -04:00

5.6 KiB
Raw Blame History

Control protocol

modelcraft --control PORT (or MODELCRAFT_CONTROL_PORT=PORT) listens on 127.0.0.1:PORT for JSON lines. Each request is one line {"id": …, "method": "…", "params": {…}}; each reply is one line {"id": …, "ok": true, "result": …} or {"id": …, "ok": false, "error": "…"}. Requests are handled on the UI thread between frames; pointer and keyboard input is injected as real egui events, one pointer event per frame. The protocol has the same shape as SolidCraft's.

modelcraft --sample --control 7070
{"id":1,"method":"engine.execute","params":{"command":"model.info","params":{}}}
→ {"id":1,"ok":true,"result":{"elements":{"hex8":64,"tet4":162},"nodes":199}}
{"id":2,"method":"ui.render","params":{"path":"/tmp/model.png","width":900,"height":600}}

The channel is untrusted input. A request line may be at most 16 MiB; a longer one is answered 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 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.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)

Commands never open forms when run this way. modelcraft-cli commands prints the same registry.

UI

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) and the probed node (probe), version
ui.set any view setting: style (shaded_edges, shaded, wireframe), show_nodes, perspective, dark, show_tree, show_commands, tab (the toolbar tab: geometry, mesh, properties, assembly, steps, loads, interactions, solve, results, tools) 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 returns the camera
ui.toolbar — the toolbar (ui.set {tab} switches tabs): tab, tabs, and every catalogue entry (docs/features.md) with id, label, tab, panel, command (null while coming soon), more (its drop-down's commands), coming_soon (the milestone, or null), rect (its button in screen points last frame, on the shown tab; a collapsed panel's entries share its drop-down button) and fit (full, compact icon-only, or collapsed into the panel's drop-down, by window width)
ui.start command: a command or toolbar entry id like clicking it on the toolbar or in the Commands panel: a command without parameters runs, one with parameters opens its form; an entry that is coming soon is an error naming its milestone
ui.dialog params? (JSON object, fills the form), run?, cancel? drives the open command form; run returns the command's result and closes the form on success
ui.click x, y (screen points), button?: left/right/middle, shift?, ctrl?, alt?, double? real pointer input; a click on a results plot probes the nearest node of the face under it (solve.probe; ui.inspect shows the values)
ui.move x, y
ui.drag x0, y0, x1, y1, button?, shift?, ctrl?, steps? (1–200) press, move, release: in the viewport a left drag orbits, a right or middle drag (or Shift + left) pans
ui.scroll x, y, delta? (wheel lines, −100–100; positive zooms in) zooms at the point
ui.key key (egui key name, Enter, Escape…), cmd?, shift? F over the viewport frames the model
ui.text text typed text (into the focused field)
ui.worldToScreen points: [[x, y, z]…] (at most 1000) screen points in the viewport drawn last frame (null when not on screen)
ui.resize width, height window inner size in points
ui.screenshot path? PNG of the window, taken a few frames later; waits for a presented frame; when none comes within 8 seconds (a hidden window, a locked screen) it answers with ui.capture's offscreen picture ("offscreen": true). Without path: modelcraft-shot.png in the temporary folder
ui.capture path?, width?, height? (points; default the window's), scale? (pixels per point, 0.5–4; default the window's) PNG of the whole window (menu, toolbar, panels, forms and the viewport) drawn offscreen: the UI pass runs in a separate egui context and is rasterised on the CPU, so it works with the window hidden, minimised or the screen locked. At most 8192 pixels a side and 16 megapixels. Without path: modelcraft-shot.png in the temporary folder
ui.render path, width? (default 1280), height? (default 800): whole pixels, each 1–8192 and at most 16 megapixels in all CPU render of the model with the current camera and view settings (no window or GPU needed). A size out of range, or a scene over 20 million primitives, is an error
app.quit — closes the window