Merge origin/main into feat/profiles-by-curve-ids
This commit is contained in:
commit
4c466466a2
62 files changed
+3591
-347
No files matched your search
@@ -1,7 +1,8 @@
|
||||
# Test harnesses
|
||||
|
||||
`cargo xtask ci` runs everything a pull request must pass: fmt, clippy with `-D warnings`, the
|
||||
tests in release mode, `xtask layers`, `xtask assets` and the wasm build check.
|
||||
`cargo xtask ci` runs everything a pull request must pass: fmt, `xtask assets`,
|
||||
`xtask licences --check`, `xtask layers`, clippy with `-D warnings`, the tests with the `ci`
|
||||
profile and the wasm build check.
|
||||
|
||||
| Harness | Where | What it catches |
|
||||
|---|---|---|
|
||||
@@ -34,7 +35,14 @@ A new file in `crates/ui-egui/tests/scenarios/` is picked up automatically. To r
|
||||
before it's fixed, add a `{"pending": "why"}` step; its test is then ignored with that reason, and
|
||||
the fix removes the step.
|
||||
|
||||
## Release mode
|
||||
## CI profile
|
||||
|
||||
CI runs the tests with `--release`, because some truck assertions only exist in debug builds
|
||||
and fail on valid geometry. If a test fails only in debug, say so in the PR.
|
||||
CI runs the tests with `--profile ci`: light optimisation, line tables, no LTO and debug
|
||||
assertions off. Some truck debug assertions fail on valid geometry; the `ci` profile disables
|
||||
them while building faster than `--release`. If a test fails only in a plain debug build, say
|
||||
so in the PR.
|
||||
|
||||
Run `cargo xtask test` to build with this profile and run the test binaries concurrently,
|
||||
with doc tests alongside. Arguments after `--` go to each test binary, for example
|
||||
`cargo xtask test -- shell`. To run the test binaries sequentially instead, use
|
||||
`cargo test --workspace --profile ci`.
|
||||
@@ -52,6 +52,45 @@ Feature references to faces and edges are stored by name, so an upstream edit th
|
||||
keeps them on the right face (see [Persistent naming](../dev/naming.md)). When a reference can't
|
||||
be found, the feature warns instead of silently picking another face.
|
||||
|
||||
## Editing a feature from a script
|
||||
|
||||
`timeline.inspect {feature}` returns a feature's `definition`: the fields `timeline.edit` changes,
|
||||
with their current values. `timeline.edit {feature, set}` merges `set` into that definition
|
||||
(objects are merged field by field, anything else replaces the value), rebuilds the feature and
|
||||
reports `recomputed` and `restored`. The feature's type cannot change, and sketch geometry is
|
||||
edited with the sketch commands. Expressions are text (`"30"`, `"30 mm"`, `"width / 2"`,
|
||||
`"45 deg"`); a bare number is a length in the design's units, or degrees for an angle.
|
||||
|
||||
```json
|
||||
{"command": "timeline.inspect", "params": {"feature": "Slab"}}
|
||||
→ {"type": "ExtrudeFeature",
|
||||
"definition": {"sketch": 1, "profiles": {"type": "all"}, "operation": "new_body",
|
||||
"extent": {"distance": "10", "direction": "positive"}},
|
||||
"inputs": [{"label": "Distance", "param": "d1", "expression": "10", "unit": "mm"}], …}
|
||||
{"command": "timeline.edit", "params": {"feature": "Slab", "set": {"extent": {"distance": "12"}}}}
|
||||
```
|
||||
|
||||
`inputs` lists the feature's named inputs. `parameters.change {name: "d1", expression}` sets one
|
||||
of those directly. Optional fields that are not set do not appear in the definition; they can
|
||||
still be added through `set`. The fields of the common features:
|
||||
|
||||
| Feature | Fields |
|
||||
|---|---|
|
||||
| Extrude | `extent`: {`distance`, `direction`: positive\|negative\|symmetric, `distance2`, `start_offset`, `through_all`, `taper`, `to`, `to_offset`, `from`}; `operation`: new_body\|join\|cut\|intersect; `targets`; `profiles`; `sketch` |
|
||||
| Revolve | `angle`, `angle2`, `symmetric`, `axis`, `to`, `operation`, `targets`, `profiles`, `sketch` |
|
||||
| Fillet | `radius`, `edges` (a point on each edge), `body`, `style` |
|
||||
| Chamfer | `distance`, `distance2` or `angle`, `flip`, `edges`, `body` |
|
||||
| Box | `length`, `width`, `height`, `corner`, `operation` |
|
||||
| Cylinder | `radius`, `height`, `base`, `axis`, `operation` |
|
||||
| Hole | `diameter`, `depth`, `position`, `direction`, `hole` (simple, drilled, counterbore, countersink), `thread`, `to` |
|
||||
| Shell | `thickness`, `faces`, `direction`, `body`, `tangent_chain` |
|
||||
| Draft | `angle`, `faces`, `neutral`, `pull`, `body` |
|
||||
| Offset face | `distance`, `faces`, `body` |
|
||||
| Pattern | `pattern` (its kind and counts/spacing), `features`, `bodies` |
|
||||
| Mirror | `plane`, `features`, `bodies`, `combine` |
|
||||
|
||||
For any other feature, `timeline.inspect` shows the fields.
|
||||
|
||||
Press **Delete** on anything selected (bodies, faces, sketches, features, planes, components,
|
||||
canvases). It runs as one undoable step and lists the features that would go with it or fail.
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@ fillet, chamfer, offset (O), mirror, patterns, move/copy and scale.
|
||||
**Project** (P) and **Intersect** bring model edges into the sketch as linked geometry that
|
||||
follows later changes. Sketching on a face projects its edges automatically.
|
||||
|
||||
Run from a script or the control channel, every command that adds sketch geometry returns the
|
||||
ids of what it added (`created: {points, curves, constraints, dimensions}`). Ids are stable and
|
||||
never reused within a sketch, even after a delete, so a script can dimension or constrain the new
|
||||
geometry right away.
|
||||
|
||||
Extend finds intersections with exact ellipses, rational conics and spline spans. It keeps the
|
||||
original line or circular support and extends only beyond the selected endpoint; conflicting
|
||||
constraints or an ambiguous overlap leave the sketch unchanged.
|
||||
@@ -54,12 +59,21 @@ fresh link once the source is available; their stored point values are preserved
|
||||
|
||||
## Constraints and dimensions
|
||||
|
||||
**Horizontal/Vertical** accepts a click on a line or clicks on two distinct sketch points,
|
||||
including the origin, endpoints and circle centres. For two points, it chooses horizontal
|
||||
or vertical alignment from their current positions. Esc cancels a pending first point.
|
||||
|
||||
The solver is SolveCraft's own (damped Gauss–Newton with degree-of-freedom analysis). Constraints:
|
||||
coincident, horizontal/vertical, parallel, perpendicular, tangent, equal, concentric, collinear,
|
||||
midpoint, symmetry, fix, curvature (G2) and polygon. **Sketch Dimension** (D) adds linear,
|
||||
aligned, horizontal, vertical, radius, diameter and angle dimensions. Each is a parameter
|
||||
(`d1`, `d2`, …), so it can hold an expression.
|
||||
|
||||
For Parallel, Perpendicular, Equal, Tangent, Concentric and Collinear, select exactly two
|
||||
sketch curves (Shift-click to add the second) and choose the constraint to apply it immediately.
|
||||
You can also start the constraint first and then pick its curves. An incomplete or mixed
|
||||
selection starts that pick workflow; a rejected constraint keeps the selection for correction.
|
||||
|
||||
Under-constrained geometry is blue and fully constrained geometry turns dark, and the status
|
||||
shows the remaining degrees of freedom. A constraint or dimension that would over-constrain the
|
||||
sketch is refused rather than applied. Drag a point to move it; the solver keeps every
|
||||
|
||||
@@ -17,6 +17,18 @@ events, one pointer event per frame.
|
||||
|
||||
Commands never open dialogs when run this way. `solvecraft-cli commands` prints the same registry.
|
||||
|
||||
Every `sketch.*` command that changes a sketch adds `created: {points, curves, constraints,
|
||||
dimensions}` to its result (and `wires` for 3D curves): the ids of what it added, in creation
|
||||
order, next to whatever else the command returns (`curves`, `sides`, `corners`…). Sketch ids are
|
||||
stable and never reused, even after a delete (`l1`, `l2`, then `l3` once `l2` is gone; only undo
|
||||
rolls the numbering back with the design), so they can go straight into later commands:
|
||||
|
||||
```text
|
||||
{"id":7,"method":"engine.execute","params":{"command":"sketch.line","params":{"points":[[0,0],[40,0]]}}}
|
||||
→ {"curves":["l1"],"created":{"points":["l1.start","l1.end"],"curves":["l1"],"constraints":["k1"],"dimensions":[]},…}
|
||||
{"id":8,"method":"engine.execute","params":{"command":"sketch.dimension","params":{"entities":["l1"],"value":50}}}
|
||||
```
|
||||
|
||||
## UI
|
||||
|
||||
| Method | Params | Notes |
|
||||
@@ -43,14 +55,14 @@ Commands never open dialogs when run this way. `solvecraft-cli commands` prints
|
||||
| `ui.key` | `key` (egui key name, `Enter`, `Escape`…), `cmd?`, `shift?` | |
|
||||
| `ui.text` | `text` | typed text |
|
||||
| `ui.screenshot` | `path?` | PNG of the window (needs a presented frame) |
|
||||
| `ui.render` | `path`, `width?`, `height?` | CPU render of the model with the current camera (no window needed) |
|
||||
| `ui.render` | `path`, `width?`, `height?` | CPU render of the model with the current camera (no window needed); cut by the section the viewport shows (`inspect.section`, an open Section Analysis dialog's preview, a sketch's Slice), with the cut face capped |
|
||||
| `ui.confirm` | `accept?`: bool | answers the Delete confirmation (shown when a delete takes other features with it or makes some fail); without `accept`, what it lists |
|
||||
| `ui.documents` | `action?`: `new`, `switch`, `close`, `quit`; `index?`; `force?` (close without asking); `answer?`: `save`, `dont_save`, `cancel` (answers "save changes?") | the open designs (tabs): names, unsaved flags, the active one, a pending "save changes?" prompt, whether a quit is asking |
|
||||
| `ui.file` | `path`; `insert?`: bool | starts an interactive background Open or Insert; poll `ui.inspect.file_busy` for completion; the window stays responsive |
|
||||
| `ui.home` | `open?`: bool; `sample?`: index | the start page: show or hide it, open a built-in sample (once built); returns `open` and the recent designs |
|
||||
| `ui.shortcut` | `command`, `key?`, `replace?` | a command's key, or rebinds it (`""` clears; a key another command uses is refused unless `replace`) |
|
||||
| `ui.help` | `item?`: `about`, `shortcuts` (read only), `report` (copies diagnostics) | the version, commit, build date and diagnostic text |
|
||||
| `ui.prefs` | any of `default_units`, `nav` (`fusion`, `solidworks`, `inventor`, `alias`, `tinkercad`), `zoom_reverse`, `zoom_to_cursor`, `orbit_cursor`, `look_at_anim`, `look_at_speed`, `msaa`, `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system`), `ui_scale`, `up_axis` (`z`, `y`), `offer_stand_up`; `open` (a page: General, Navigation, Keyboard Shortcuts, Design, Graphics, Sketch, Selection) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file | sets preferences (kept between runs), returns them all plus `dialog` (the open page or null), `dark` and `stand_up_offer` |
|
||||
| `ui.prefs` | any of `default_units`, `nav` (`fusion`, `solidworks`, `inventor`, `alias`, `tinkercad`), `zoom_reverse`, `zoom_to_cursor`, `orbit_cursor`, `look_at_anim`, `look_at_speed`, `msaa`, `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system`), `language` (`en`, `ja`; see [localization](localization.md)), `ui_scale`, `up_axis` (`z`, `y`), `offer_stand_up`; `open` (a page: General, Navigation, Keyboard Shortcuts, Design, Graphics, Sketch, Selection) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file | sets preferences (kept between runs), returns them all plus `dialog` (the open page or null), `dark` and `stand_up_offer` |
|
||||
| `ui.capture` | `action?`: `capture`, `revert`, `cancel` | answers the Capture Position question (asked when a command starts or the design is saved while components were moved without capturing); without `action`, what waits |
|
||||
| `ui.window` | `action`: minimize, maximize, restore, toggle, close | what the title bar's caption buttons do (on Windows and Linux the application bar is the title bar); close asks about unsaved designs first |
|
||||
| `ui.agent_cursor` | `show` (bool), `speed`: a number from 1 to 5 (times the normal speed) or `"instant"` (no animation: the command runs at once and the pointer jumps to where it acted), `follow_camera` (bool); all optional | the settings. While the cursor is shown, `engine.execute` first glides to the command's toolbar button and to each pick, then runs and replies; later requests wait their turn. `"animate": false` on a call skips it |
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# UI localization
|
||||
|
||||
SolveCraft embeds its translations and works offline. English and Japanese are currently
|
||||
registered in `crates/ui-egui/src/i18n.rs`. Adding a language requires an embedded catalog and
|
||||
one registration there; the preferences selector and validation use the same registry.
|
||||
|
||||
## Messages
|
||||
|
||||
`crates/ui-egui/src/locales/en.json` defines stable message IDs and English fallback text.
|
||||
Keep IDs when changing wording. Other catalogs use the same IDs; missing messages fall back
|
||||
to English. Use `app.tr("ui.preferences")` for static messages and
|
||||
`Locale::format` for full sentences with named placeholders. Translate the entire sentence,
|
||||
including punctuation, so a translator can reorder its arguments. Placeholder values are
|
||||
inserted literally, even if a document name contains braces.
|
||||
|
||||
The English-label adapter (`app.tr_label`) supports existing engine and UI tables. It maps
|
||||
display text to a message ID, then uses the same catalog lookup. New UI messages should use
|
||||
stable IDs directly. The adapter does not change command IDs, model names, or scenario handles.
|
||||
When a table's English wording changes, update the corresponding English catalog entry too.
|
||||
Generated browser labels use named templates while their underlying model names stay intact.
|
||||
|
||||
Catalogs are parsed once and looked up through ordered maps. This is offline localization,
|
||||
not a claim of zero runtime overhead. The catalog tests check registered locale IDs,
|
||||
translation keys, and placeholder consistency. Run `cargo xtask ci` before submitting changes.
|
||||
|
||||
## Rendering and coverage
|
||||
|
||||
One immutable `Locale` type handles all translations. Table widgets without an app reference
|
||||
use a render scope that restores the previous locale when it exits, including nested renders.
|
||||
Standalone translated panels enter the same scope. Measure rendered text to size custom
|
||||
widgets; UTF-8 byte lengths do not describe their displayed width.
|
||||
|
||||
The desktop host tries system CJK fonts and skips files that cannot be parsed. No system fonts
|
||||
are redistributed. Japanese glyph coverage depends on installed fonts; browser hosts cannot
|
||||
load local system fonts and must provide suitably licensed font assets separately.
|
||||
|
||||
The current messages support named substitution, not locale-specific plural selection,
|
||||
number/date formatting, or right-to-left layout and shaping. Add those capabilities when
|
||||
introducing messages or languages that need them; use an established message formatter for
|
||||
plural rules instead of adding language branches to panels. The catalogs cover the UI surfaces
|
||||
introduced by this PR, not every engine error or every newer command dialog.
|
||||
@@ -62,6 +62,11 @@ Claude Desktop (`claude_desktop_config.json`), bridged to a running app:
|
||||
| `open` | `path` | Open a `.solvecraft` design, a STEP file (`.step`/`.stp`) as a new design holding its bodies (an Import base feature), or a 3MF/STL file as mesh bodies. |
|
||||
| `save` | `path?` | Save the design (to its current file without `path`). |
|
||||
|
||||
To change a feature after it is made, read its definition with `execute` `timeline.inspect`
|
||||
(`{feature}`: id or name) and change fields with `timeline.edit` (`{feature, set}`; e.g.
|
||||
`{"extent": {"distance": "30"}}`). The fields per feature type are listed in the book's
|
||||
*Solid features* chapter.
|
||||
|
||||
Resources: `solvecraft://design` (inspect JSON with measurements) and `solvecraft://commands`
|
||||
(the catalog).
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ solvecraft-cli fuzz --from 0 --count 1000 --jobs 8 --timeout 90 --out runs/ --re
|
||||
solvecraft-cli fuzz-one <seed> --minimise # one design, cut down to the steps that matter
|
||||
```
|
||||
|
||||
`--count` seeds are run starting at `--from`. A range that exceeds `u64::MAX` is
|
||||
rejected before any jobs or output directories are created. The maximum seed
|
||||
(`18446744073709551615`) is valid with `--count 1`; `--count 0` runs no seeds.
|
||||
|
||||
- **Designs** (`crates/engine/src/fuzz.rs`): a seeded generator starts from a box, an extruded
|
||||
sketch profile closed by an arc (sometimes notched), or a revolved section (partial or full
|
||||
turns, sometimes touching the axis). It then adds up to eight steps: joins and cuts of
|
||||
|
||||
Reference in new issue
Block a user