Files
OneCAD-Simulation/docs/formats/modelcraft.md
T
2d99a8571c The .modelcraft file, undo/redo on the journal, atomic saves and autosave recovery (M3) (#21)
* modelcraft-io: the .modelcraft file (modelcraft/1), atomic writes, autosave storage

A stored ZIP of a manifest (format tag, geometry sources with SHA-256,
part sizes and hashes), the model definition as stable pretty JSON, the
mesh and geometry tags in a compact binary part, and the journal one
command per line. Reading caps every size, checks every checksum and
replays the model through its checked methods. Model::without_mesh and
empty mesh fields left out of the model's JSON; geometry::read_file.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: undo/redo on the journal, file.open/save/save_as, autosave and recovery

CommandSpec gains a kind: Model commands are journalled and become undo
steps; Session commands (file.*, edit.*) manage the session. Undo history
is bounded by steps and estimated memory. Geometry sources are recorded
with content hashes and reopened on file.open (missing or changed files
are warnings). Desktop app autosaves every 30 s and reports recoverable
models; Ctrl/Cmd+Z and Shift+Z/Y undo and redo.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: the .modelcraft format, undo history, roadmap and parity status

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* simplify: shared path parameter, one load path for open/recover, history accounting in History

Session commands skip the per-command model copy; saved revision is an
Option; journal written from borrowed entries and read without an
intermediate tree; backups copied by the file system; recovery lock and
error helpers; geometry file wrappers use read_file.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-13 17:22:01 -04:00

95 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# The `.modelcraft` file (format `modelcraft/1`)
A `.modelcraft` file holds one model: its definition, its mesh, the geometry it is built on and
the commands that built it. It is written and read by `modelcraft-io` (`write`, `read`; bytes in,
bytes out, so the browser build uses the same code) and by the commands `file.save`,
`file.save_as` and `file.open`.
## Package
The file is a ZIP package with exactly four parts, in this order, all stored uncompressed (so any
unzip tool reads them: `unzip -p bracket.modelcraft model.json`):
| Part | Content |
|---|---|
| `modelcraft.json` | Manifest: `format` (`"modelcraft/1"`), `generator` (`"ModelCraft 0.1.0"`), `geometry`, and `parts`: the byte size and SHA-256 of each other part. |
| `model.json` | The model definition: unit system, sets, materials, sections, reference points, couplings, boundary conditions, loads and steps. Pretty-printed JSON with a fixed key order (maps sorted by name), so a change shows as a line diff. Its form is the model's own JSON form, which commands take as parameters. |
| `mesh.bin` | Nodes, elements and geometry tags, binary (below). |
| `journal.json` | The commands that built the model, `[{"command": id, "params": {...}}, ...]`, one per line. It is a script `modelcraft-cli run` replays. |
The same model gives the same bytes on every platform: entry times are fixed (1980-01-01),
permissions are fixed, and every map is written in sorted order. To diff two versions with git,
add `*.modelcraft diff=modelcraft` to `.gitattributes` and
`git config diff.modelcraft.textconv "unzip -p"`; the definition and the journal show line by
line, the mesh part as binary.
## Geometry
```json
"geometry": {
"design": {"path": "plate.solidcraft", "sha256": "…"},
"meshes": [{"path": "/parts/tet.3mf", "sha256": "…"}]
}
```
`design` is the SolidCraft design (`geometry.open`), `meshes` the face-tagged 3MF files
(`geometry.import.mesh`) in import order. Each records the SHA-256 of the bytes that were opened.
Paths inside the model file's folder are stored relative to it (with `/`), others absolute.
Opening the model opens each file again: a missing, unreadable or changed file (its hash differs)
is reported as a warning and the model still opens; sets bind to geometry names, so a remesh
brings the mesh in line with a changed design. A geometry file that could not be opened keeps its
reference, so saving again does not lose it.
## `mesh.bin`
Little-endian:
```
"MCMESH01" magic (the mesh encoding's version is 01)
u64 nodes, u64 elements, u64 entities, u64 tagged elements, u64 tagged nodes
nodes × { u32 label, f64 x, f64 y, f64 z } labels strictly ascending
elements × { u32 label, u8 kind, u32 node × n(kind) } labels strictly ascending
entities × { u8 kind (0 body, 1 face, 2 edge, 3 vertex),
u16 length, body name, [u16 length, entity name] unless a body } UTF-8
tagged elements × { u32 element, u32 region (0xFFFFFFFF none),
u8 n, n × { u8 face, u32 tag }, u8 n, n × { u8 edge, u32 tag } }
tagged nodes × { u32 node, u32 tag }
```
Element kind codes: 1 rod2, 2 beam2, 3 tri3, 4 tri6, 5 quad4, 6 quad8, 7 tet4, 8 tet10, 9 wedge6,
10 hex8, 11 hex20. Node order is the model's (Abaqus/VTK). A tag indexes the entity list, which
lists each geometry entity once. Coordinates keep every bit (signed zero included).
## Reading a file safely
Every file is treated as hostile:
- The file is at most 8 GiB; the manifest, definition and journal parts at most 256 MiB each. No
allocation is larger than the input.
- Only the four parts, each once. The ZIP CRC and the manifest's size and SHA-256 are checked for
every part.
- A `format` other than `modelcraft/1` is refused: a later `modelcraft/N` as a version error, any
other value as not a ModelCraft file. Unknown fields are errors.
- `mesh.bin`: counts are capped (50 million nodes and elements, one million entities) and checked
against the bytes present before anything is read; labels must ascend; unknown kinds and
trailing bytes are refused.
- The model is rebuilt through the same checked methods commands use: names, references,
finite coordinates within ±1e9, element connectivity, tag dimensions. A file cannot hold what
commands would refuse.
## Saving
`file.save` writes the bytes to a temporary file beside the target, flushes it, keeps the old
file as `<file>.bak`, renames the new file over the target and flushes the folder: a crash at any
moment leaves the old file or the new one. A path that exists and is not a regular file is
refused.
## Autosave and recovery
The desktop app autosaves every 30 seconds while there are unsaved changes, into the recovery
folder (`MODELCRAFT_RECOVERY_DIR`, else `ModelCraft/recovery` in the per-user data folder):
`<id>.modelcraft`, `<id>.json` (the model's own path, time, counts) and `<id>.lock`, held while
the app runs. Saving (or undoing back to the saved state) drops the autosave. After a crash, the
next launch reports the entries; `file.recovery.list` lists them, `file.recover` opens one as
unsaved work under its original path, and `file.recovery.discard` deletes them.