* feat(sketch): constructive solving ahead of the numeric solver Part 1 of splitting #251: the sketch crate only. A graph-constructive front end (Owen 1991; Fudos & Hoffmann 1997) places what it can by ruler-and-compass loci and cluster decomposition, Levenberg-Marquardt finishes and verifies the rest, and the rank analysis stays authoritative for degrees of freedom. Solves can follow a reference (the previous solution), so edits and drags keep their branch instead of flipping. New API: solve_with (hold, reference, analysis, budget) and solve_after_value_edit; solve and solve_holding keep their signatures as wrappers, so no caller changes here. When the seed moves an arc's centre, the arc's unconstructed ends start carried along (the arc translates) instead of left behind for LM to turn; found by the reference test 20-slot-constrained. Refs #203 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44 * feat(doc): keep a sketch's solved state across value edits Part 2 of splitting #251 (builds on part 1, the sketch crate): - Sketch edits solve for the document's values continuing from the stored solved state (Document::solve_sketch), so a dimension edit keeps the sketch's branch instead of re-solving from scratch. - A value edit (parameters, a configuration, the units) that would make a sketch that solved before fail or turn over is refused with the conflicting constraints named; otherwise the solved sketches are written back into the document (Model::adopt_solved_sketches), which changes what saved designs store. - Later features are keyed by a sketch's solved geometry (to a 1e-6 mm quantum), so setting a value back reuses their earlier results. - Solved sketches in a model state are shared (Arc) between states. Refs #203 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44 * feat(sketch): live drag that keeps its solution, ended by sketch.drag_end Part 3 of splitting #251 (builds on part 2): - sketch.drag follows a long move in short held steps, so the geometry keeps its branch, and reports how far it got (reached, at). - live: true runs one frame of an interactive drag: the sketch is solved once and shown, later features keep their last result (marked stale) until sketch.drag_end or any command that reads the model evaluates it (CommandSpec::live, reads_model, doc_only). - sketch.drag_end adds the constraint the drag let go on and evaluates the model once. - Undo and redo evaluate a new document once (execute does) instead of twice. - The viewport drives drags through live frames and drag_end; dimension text drags start only off sketch geometry. Refs #203 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44 --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
336 lines
22 KiB
Markdown
336 lines
22 KiB
Markdown
# Design loading
|
||
|
||
## Parallel STEP body preparation (2026-10-12)
|
||
|
||
Profiling an original 48-cylinder STEP fixture before changing the importer measured
|
||
3.17 ms parsing, 6.06 ms reconstructing B-reps and 45.58 ms preparing/orienting bodies.
|
||
The five complete imports had a median of 59.21 ms. The expensive orientation mesh,
|
||
rather than the text parser, was the first parallelization target for #287.
|
||
|
||
Desktop import now prepares consecutive independent solids in a representation with
|
||
up to eight workers. Each batch contains at most 64 solids. Parsing, B-rep reconstruction,
|
||
assembly traversal, first-use unit resolution, naming and result collection remain ordered.
|
||
Mapped items end a batch. Fewer than four solids use the serial path; wasm always uses it.
|
||
Thread creation failure falls back to serial work, and kernel/worker failures remain
|
||
per-body errors reported by the importer.
|
||
|
||
Windows x64, AMD Ryzen AI Max+ 395 (16 cores / 32 logical processors), optimized `ci`
|
||
profile, no concurrent build: three fresh test processes per fixture, five alternating
|
||
serial/parallel pairs in each process. These are complete uncached kernel imports,
|
||
including parsing and body preparation; fixture generation/export and later desktop
|
||
display meshing are outside the timer. Both paths use the same input text and code.
|
||
|
||
| Original generated fixture | Bodies | Serial median | 8 workers median | Reduction |
|
||
|---|---:|---:|---:|---:|
|
||
| Cylinders, radius 5 mm, height 12 mm | 48 | 57.54 ms | 17.38 ms | 69.8% |
|
||
| Cylinders, radius 5 mm, height 12 mm | 512 | 896.31 ms | 313.06 ms | 65.1% |
|
||
| Tori, major radius 20 mm, minor radius 4 mm | 48 | 269.05 ms | 51.90 ms | 80.7% |
|
||
| Boxes, 10 × 15 × 12 mm | 512 | 247.97 ms | 90.15 ms | 63.6% |
|
||
| Boxes, 10 × 15 × 12 mm | 4 | 1.49 ms | 1.02 ms | 31.5% |
|
||
| One cylinder (serial fallback) | 1 | 1.22 ms | 1.29 ms | serial fallback |
|
||
|
||
There are 15 samples per cell. The single-cylinder modes run identical serial code; their
|
||
0.07 ms median difference is within the overlapping 1.11–1.57 / 1.13–1.52 ms ranges. The 512-cylinder runs ranged from 567.40–983.61 ms
|
||
serial and 197.72–345.51 ms parallel; scheduling/thermal variation is material. These
|
||
synthetic results do not establish a speedup for every assembly or complete desktop Open.
|
||
In particular, parts containing only one solid are not parallelized across assembly nodes.
|
||
Cross-part scheduling, parallel B-rep reconstruction and later display meshes remain open.
|
||
|
||
Reproduce after compiling, without other builds running:
|
||
|
||
```sh
|
||
cargo test -p solvecraft-kernel --profile ci benchmark_independent_step_solids --no-run
|
||
SOLVECRAFT_STEP_BENCH_COUNT=512 SOLVECRAFT_STEP_BENCH_KIND=cylinder \
|
||
cargo test -p solvecraft-kernel --profile ci benchmark_independent_step_solids \
|
||
-- --ignored --nocapture --test-threads=1
|
||
```
|
||
|
||
Use `torus` or `box` for the other fixtures and repeat in three fresh processes.
|
||
`SOLVECRAFT_STEP_BENCH_FIXTURE` optionally writes the original STEP for native inspection.
|
||
No timing threshold runs in CI. Regressions compare serial and threaded imports with
|
||
different worker counts: exact display meshes, measures, body/face source identities,
|
||
names, colours/opacity, units, rotated assembly placements, duplicates and partial failures
|
||
across the batch boundary. The public analytic/FreeCAD reference suite also checks STEP
|
||
export/import; its current results are reported in docs/oracle.md.
|
||
## Analytic extrusion source preparation
|
||
|
||
New-body extrusion certificates now prepare the sketch's bounded analytic segments once
|
||
per certification pass instead of allocating segments and resolving their points for each
|
||
generated cap boundary and side face. Saved designs run this same code during cold rebuilds.
|
||
Exact containment tolerances, ambiguous-source rejection and the certification work budget
|
||
are unchanged. Prepared segments borrow an immutable evaluated sketch and are not retained
|
||
across edits.
|
||
|
||
The reproducible microbenchmark is:
|
||
|
||
```sh
|
||
cargo test -p solvecraft-doc --profile ci benchmark_prepared_extrude_sources -- --ignored --nocapture
|
||
```
|
||
|
||
Three Windows x64 optimized `ci` runs, during other compilation, compared the previous
|
||
segment reconstruction loop and the prepared matcher in the same process. For 1,000
|
||
analytic lines and 1,000 generated face segments, median time fell from **45.90 ms to
|
||
12.30 ms** (73% less, about 3.7x faster). Preparation is included and every returned source
|
||
identity is compared. This measures certification only, not complete extrusion, file-open
|
||
latency or Boolean operations; it does not establish an end-to-end loading speedup.
|
||
Remaining cold geometry rebuild and import costs are tracked in
|
||
[#255](https://github.com/bherbruck/solidcraft/issues/255); certification work is
|
||
[#254](https://github.com/bherbruck/solidcraft/issues/254).
|
||
|
||
## Cold geometry rebuilds after 0.3.1
|
||
|
||
Three alternating cold-process comparisons on the same Windows machine and optimized
|
||
`ci` profile, with no concurrent build, compare the unchanged 0.3.1 source with the
|
||
straight-line projection and exact surface-refinement reuse changes:
|
||
|
||
| Saved design | Before, median evaluation | After | Reduction |
|
||
|---|---:|---:|---:|
|
||
| Public sensor enclosure, 21 features, 52 KB | 11.96 s | 8.85 s | 26.0% |
|
||
| Local Pi case assembly, 25 features, 2,990 bodies | 14.75 s | 11.57 s | 21.6% |
|
||
|
||
These are geometry rebuild times, not complete desktop opening times. The public fixture
|
||
can be generated with `solvecraft-cli run examples/acceptance/sensor_enclosure.json
|
||
--save sensor.solvecraft --quiet`, then measured with `solvecraft-cli bench-open
|
||
sensor.solvecraft`. Each benchmark starts a new process; this does not measure a warm
|
||
timeline-cache hit. The local assembly is private and is not part of the repository.
|
||
|
||
Straight segments now use guarded algebraic closest-point projection instead of sampling
|
||
65 points and running Newton iterations. Other curve types retain their existing behavior.
|
||
Curved-face refinement reuses derivative and midpoint evaluations with exact finite input
|
||
keys and bounded, local caches. It keeps the original tolerance, seam and singularity
|
||
checks, refinement passes, and triangle limits.
|
||
|
||
Identity assembly placements retain the definition body's shared display-mesh cache.
|
||
Changed placements continue to build separate meshes in their own coordinates. Face-name
|
||
preparation remains eager during placement: deferring it merely shifted expensive work to
|
||
thumbnails and the first face query, so that approach was excluded.
|
||
|
||
Both saved fixtures retain exactly the same body names, face/edge/vertex/shell counts,
|
||
bounds, volumes, and surface areas. Focused tests compare cached and uncached refinement
|
||
coordinates and normals bit for bit on periodic, singular and transformed surfaces.
|
||
|
||
For desktop phase profiling, set `SOLVECRAFT_PROFILE_OPEN=1` before launching the app.
|
||
Interactive Open reports geometry, placement, tessellation, thumbnail and total worker
|
||
times to stderr without logging the source path. Thumbnails were about 0.1 seconds of
|
||
the slow assembly's open and were not the main bottleneck. Further cold Boolean and
|
||
import costs remain; these changes do not make all documents open instantly.
|
||
|
||
### Prepared assembly geometry and selection
|
||
|
||
Prepared world geometry is now keyed by the document, evaluated model and exact placement
|
||
context instead of the session revision. Selecting a face or edge, changing visibility,
|
||
or moving the view no longer discards already prepared assembly bodies and meshes.
|
||
Geometry edits, pending occurrence moves, authoring context changes, undo/redo and timeline
|
||
rollback still invalidate the cache. Scratch sessions share prepared geometry while their
|
||
inputs match, and adopting a computed session retains its warm cache.
|
||
|
||
On the private 2,990-body Pi assembly, a matched Linux desktop control probe reduced an
|
||
edge-selection round trip from 9.24 s to 0.57 s. Initial opening stayed essentially unchanged
|
||
(33.54 s to 33.32 s); this fixes a separate selection stall, not cold geometry evaluation.
|
||
Three fresh Windows desktop runs with the corrected cache measured a median 15.87 s to
|
||
ready: 13.04 s geometry, 2.20 s placement, 0.53 s meshing and 0.07 s thumbnails. These used
|
||
the optimized `ci` profile and actual Vulkan rendering, with isolated settings and caches.
|
||
Linux and Windows timings are separate measurements and must not be compared as a speedup.
|
||
|
||
`solvecraft-cli bench-open design.solvecraft` reports JSON lines for file reading,
|
||
JSON decoding and migrations (including input-name repair), timeline evaluation, individual
|
||
features, and world placement/display meshing. `--parse-only` stops before geometry evaluation.
|
||
The parse timer includes document validation and repair, not just the JSON parser.
|
||
|
||
Windows x64, optimized `ci` profile, measured during other builds:
|
||
|
||
| Design | Parse/repair | Evaluate | Display meshes |
|
||
|---|---:|---:|---:|
|
||
| 19-feature enclosure, 34 KB | 1.2 ms | 14.9 s | 6 ms |
|
||
| 25-feature assembly, 15.2 MB, 2,990 bodies | 21 ms | 12.5 s | 2.1 s |
|
||
| Generated 1,000 independent boxes, 185 KB | 5 ms | 0.82 s | 0.15 s |
|
||
| Generated 5,000 boxes in five grids, 10 features | 0.45 ms | 17.6 s | 0.81 s |
|
||
|
||
The enclosures spend most of evaluation in joins and cuts, not parsing. Designs save feature
|
||
definitions; opening them rebuilds their geometry. Those times remain after the repair fix.
|
||
|
||
A separate generated timeline with missing input names reproduced minutes in the reader:
|
||
|
||
| Timeline (geometry rolled back) | Parse/repair before | After |
|
||
|---|---:|---:|
|
||
| 2,000 boxes, 6,000 unnamed inputs | 78.1 s | 10–20 ms |
|
||
| 5,000 boxes, 15,000 unnamed inputs | interrupted after 131 s, still repairing | 26–46 ms |
|
||
|
||
Previously, each missing input restarted the `d1`, `d2`, … search, scanning the document for
|
||
every candidate. This approaches cubic work as unnamed inputs accumulate. Loading now reserves
|
||
names once and assigns missing names with an advancing cursor. Existing names, names on later
|
||
features, parameter collisions and reuse of surplus names retain the previous behavior.
|
||
Regression tests compare the old and bulk repairs and load a 5,000-feature unnamed timeline.
|
||
|
||
# Viewport performance
|
||
|
||
Measured with the control channel (`ui.inspect` `frame_ms`: CPU time of one UI frame, which
|
||
includes picking under the cursor) on the sample plate patterned 20 × 20 (400 bodies, about
|
||
600k triangles), 1600 × 1000 window under Xvfb, release build. Hover: 60 cursor moves across
|
||
the model; orbit: a right-drag.
|
||
|
||
| | hover median | hover p90 | orbit median |
|
||
|---|---|---|---|
|
||
| before | 18.7 ms | 37.5 ms | 21.0 ms |
|
||
| after | 1.9 ms | 2.4 ms | 2.6 ms |
|
||
| after, Browser hidden | 0.4 ms | — | 0.4 ms |
|
||
|
||
What changed:
|
||
- **Picking culls by bounding box.** Per-body boxes are cached until the model changes. The
|
||
face raycast skips bodies whose box the ray misses or enters behind the nearest hit so far.
|
||
Edge and vertex tests skip bodies whose box, projected on screen, is not under the cursor.
|
||
- **Picks are reused** while the cursor, camera and model are unchanged, and **no picking runs
|
||
while the view moves** (orbit, pan, zoom).
|
||
- **The model extent is cached** (it sets the view's depth range and was recomputed from every
|
||
mesh several times per frame).
|
||
|
||
With 49 bodies the frame was already 2.8 ms (hover) before these changes. The remaining frame
|
||
time with the Browser shown is mostly the Browser listing every body row. GPU ID-buffer picking
|
||
was not needed at this size.
|
||
|
||
## Browser
|
||
|
||
Same model and method (400 bodies, Bodies folder open, 60 hover moves over the viewport):
|
||
|
||
| | median | p90 |
|
||
|---|---|---|
|
||
| Browser shown, before | 1.95 ms | 2.71 ms |
|
||
| Browser shown, after | 0.72 ms | 1.19 ms |
|
||
| Browser hidden | 0.30 ms | 0.72 ms |
|
||
|
||
What changed: rows scrolled out of view only reserve their height. They register no widgets
|
||
and paint nothing (no text layout, icons or eye/fold buttons), so the cost follows the rows on
|
||
screen rather than the rows in the tree. Scrolling, Find in Browser and drag and drop still see
|
||
every row's place.
|
||
|
||
# Recompute performance
|
||
|
||
`solvecraft-cli bench-edit <script> <param> "(<old>) * 1.02"` builds a design from its script
|
||
(timing the build), then times a parameter edit, setting the parameter back to its old
|
||
expression, undo and redo. Each cell is before → after the evaluation cache, with the number of
|
||
features recomputed in parentheses. Release build, on a machine shared with other build jobs (load average about 45; the
|
||
times vary by ±50 % run to run, so compare the recompute counts and the zeros). "Early" edits a parameter of the
|
||
first sketch or feature, "late" one of the last feature.
|
||
|
||
| Design | Edit | Build | Edit | Set back | Undo | Redo |
|
||
|---|---|---|---|---|---|---|
|
||
| sample plate, 20 × 20 pattern | early (`width`) | 7587 ms (10) → 1764 ms (10) | 7969 ms (10) → 1691 ms (10) | 7557 ms (10) → 0 ms (0) | 6534 ms (0) → 0 ms (0) | 4570 ms (0) → 0 ms (0) |
|
||
| sample plate, 20 × 20 pattern | late (`d9`) | 7870 ms (10) → 1699 ms (10) | 3944 ms (1) → 812 ms (1) | 4039 ms (1) → 0 ms (0) | 1464 ms (0) → 0 ms (0) | 1398 ms (0) → 0 ms (0) |
|
||
| oracle 58 phone stand | early (`d1`) | 163 ms (6) → 140 ms (6) | 228 ms (5) → 140 ms (5) | 171 ms (5) → 0 ms (0) | 158 ms (0) → 0 ms (0) | 130 ms (0) → 0 ms (0) |
|
||
| oracle 58 phone stand | late (`d4`) | 144 ms (6) → 195 ms (6) | 118 ms (1) → 118 ms (1) | 145 ms (1) → 0 ms (0) | 149 ms (0) → 0 ms (0) | 187 ms (0) → 0 ms (0) |
|
||
| oracle 59 enclosure base | late (`d8`) | 11582 ms (11) → 11179 ms (11) | 5772 ms (1) → 5742 ms (1) | 5606 ms (1) → 0 ms (0) | 6023 ms (0) → 0 ms (0) | 6119 ms (0) → 0 ms (0) |
|
||
| oracle 60 pipe flange | early (`d1`) | 8137 ms (7) → 8348 ms (7) | 7569 ms (6) → 10143 ms (6) | 8354 ms (6) → 0 ms (0) | 8607 ms (0) → 0 ms (0) | 10034 ms (0) → 0 ms (0) |
|
||
| oracle 60 pipe flange | late (`d6`) | 7661 ms (7) → 11990 ms (7) | 67 ms (1) → 361 ms (1) | 69 ms (1) → 0 ms (0) | 84 ms (0) → 0 ms (0) | 66 ms (0) → 0 ms (0) |
|
||
| oracle 61 shaft keyway | early (`d1`) | 1723 ms (6) → 3016 ms (6) | 1785 ms (5) → 2745 ms (5) | 1760 ms (5) → 2323 ms (5) | 1667 ms (0) → 0 ms (0) | 1719 ms (0) → 0 ms (0) |
|
||
| oracle 61 shaft keyway | late (`d4`) | 1905 ms (6) → 2247 ms (6) | 1734 ms (1) → 2101 ms (1) | 1682 ms (1) → 0 ms (0) | 1719 ms (0) → 0 ms (0) | 1876 ms (0) → 0 ms (0) |
|
||
| oracle 62 heat sink | early (`d1`) | 382 ms (6) → 643 ms (6) | 362 ms (5) → 697 ms (5) | 233 ms (5) → 568 ms (5) | 245 ms (0) → 0 ms (0) | 360 ms (0) → 0 ms (0) |
|
||
| oracle 62 heat sink | late (`d5`) | 479 ms (6) → 688 ms (6) | 270 ms (1) → 657 ms (1) | 321 ms (1) → 0 ms (0) | 413 ms (0) → 0 ms (0) | 283 ms (0) → 0 ms (0) |
|
||
| oracle 63 hinge knuckle | early (`d1`) | 1305 ms (8) → 1671 ms (8) | 1329 ms (7) → 2397 ms (7) | 1971 ms (7) → 0 ms (0) | 1705 ms (0) → 0 ms (0) | 1321 ms (0) → 0 ms (0) |
|
||
| oracle 63 hinge knuckle | late (`d4`) | 1430 ms (8) → 2072 ms (8) | 1387 ms (1) → 2073 ms (1) | 1480 ms (1) → 0 ms (0) | 1192 ms (0) → 0 ms (0) | 1035 ms (0) → 0 ms (0) |
|
||
|
||
What changed: every feature evaluation is cached by a key made of the feature's own inputs
|
||
(definition and parameter values), the document context it may read (construction planes,
|
||
sheet metal and plastic rules, components) and a digest of the model it starts from, in which
|
||
each body is identified by the evaluation that produced it (bodies a feature passes through
|
||
unchanged keep their identity). The same feature on the same inputs is never evaluated twice:
|
||
setting a value back, undo, redo, a preview followed by the same command, and the repeated
|
||
evaluations while a script builds the design all reuse earlier results. The cache is shared by a
|
||
model and its copies (previews, scratch sessions) and keeps the last 4096 results.
|
||
|
||
An edit still recomputes the edited feature and every later feature whose starting model
|
||
changed; in these parts every later feature works on the edited body, so early edits recompute
|
||
almost everything, as they must. `cached_evaluation_matches_from_scratch` checks that cached and
|
||
from-scratch evaluation give the same models (bodies, volumes and areas to 1e-6, topology,
|
||
errors) through edits, suppression, roll-back, undo and redo. Setting a sketch dimension back
|
||
can still recompute (oracle 61, 62): solving the sketch moves its points by rounding noise, so
|
||
its inputs are not exactly the old ones. Oracle 59's first parameter edit fails (a recipe
|
||
expression the bench's `* 1.02` can't scale) and is left out.
|
||
|
||
|
||
## Desktop loading and recent thumbnails (2026-10-10)
|
||
|
||
The Arduino Uno enclosure (19 features, 34 KB) and Raspberry Pi 5 case (24 features,
|
||
61 KB) each decode in about 1 ms. A release build spends roughly 12.1 s and 8.2 s,
|
||
respectively, evaluating their geometry on this Windows machine. Changing the document
|
||
encoding does not eliminate these boolean operations.
|
||
|
||
Interactive Open, recent-file clicks, drag/drop and STEP/mesh insertion now execute on a
|
||
worker, including preparation of the world-space bodies and display meshes. The existing
|
||
design stays available until the result is ready; a failed or cancelled job cannot replace
|
||
it. Insertion preserves the file, saved state, earlier undo history and view settings.
|
||
Timeline copies share their lazy mesh cell, and adopting a prepared session keeps its warm
|
||
world-space cache instead of transforming and tessellating its components again on the UI.
|
||
A STEP body's missing-face check reuses its orientation tessellation when the tolerances
|
||
match and its orientation was not reversed.
|
||
|
||
Live desktop tests (`ci` profile, with other builds/tests running) opened the Arduino and
|
||
Pi case in 15.2 s and 10.5 s. During those jobs, median `ui.inspect` round trips were 20.5 ms
|
||
and 8.3 ms; the slowest were 33.7 ms and 28.6 ms. This fixes the window freeze, not the
|
||
remaining cost of cold geometry evaluation.
|
||
|
||
Recent thumbnails are 240 × 150 PNG files in `SolveCraft/thumbnails` under the platform
|
||
cache directory (`LOCALAPPDATA` on Windows). Their keys include the source path, file size
|
||
and modification time. Open and Save render from the model already in memory. Cached
|
||
recents decode the PNG without reading or rebuilding a document, and run before cold
|
||
recents and sample thumbnails. Missing or invalid pictures fall back to a background rebuild.
|
||
No thumbnail or base64 image is added to the design JSON. A regression test verifies the
|
||
source bytes remain unchanged, a cached picture works even without a parseable source
|
||
file, and changing the source invalidates the cache.
|
||
|
||
`solvecraft-cli bench-open` also accepts STEP/IGES. For exchange files its `parse` phase
|
||
includes constructing and checking the imported B-rep, not merely decoding STEP syntax.
|
||
|
||
The live test loaded both cached recent cards in 21 ms and inserted the 2 MB lightweight
|
||
Pi STEP (27 bodies) into the case in 279 ms; undo restored the original two bodies. The
|
||
full 77 MB board STEP (2689 solid definitions, 45,689 faces) still had not completed after
|
||
300 s. Its window continued handling inspection and Cancel, and cancelling preserved the
|
||
case. Complex STEP construction remains an outstanding kernel performance problem.
|
||
|
||
# Sketch solver and drags (2026-10-11)
|
||
|
||
Unknowns the constraints pin down one after another from the constants (each one's own rows
|
||
of full, well-conditioned rank, reading nothing but constants and unknowns pinned before it)
|
||
are eliminated exactly: values the constructive seed placed and whose constraints hold there
|
||
stay constants while Levenberg–Marquardt solves the rest, and the rank analysis (degrees of
|
||
freedom, determined geometry) runs on the remainder only. A component tied to no constant takes
|
||
one point as a constant first (its translation). All the held solves of one live drag frame
|
||
share a 1M decomposition work budget (about 16 ms; a solve's own, 20M, is about 0.3 s), and a
|
||
drag's steps are a quarter of the smallest held size instead of an eighth, except once a seed
|
||
ran out of budget: the rest of that drag goes in eighths, as before, and continuation keeps
|
||
the branch the seed no longer places.
|
||
|
||
`cargo run --release -p solvecraft-sketch --example solve_bench`, macOS arm64, load average
|
||
45–75 (best of 5):
|
||
|
||
| Grid (origin corner fixed, rows horizontal, columns vertical, every edge a distance) | Start | Before | After |
|
||
|---|---|---:|---:|
|
||
| 10 × 10 | at solution | 0.36 ms | 0.21 ms |
|
||
| 10 × 10 | ±1 mm | 0.34 ms | 0.21 ms |
|
||
| 15 × 15 | at solution | 1.41 ms | 0.41 ms |
|
||
| 15 × 15 | ±1 mm | 1.39 ms | 0.40 ms |
|
||
| 20 × 20 | at solution | 4.06 ms | 0.72 ms |
|
||
| 20 × 20 | ±1 mm | 4.44 ms | 0.73 ms |
|
||
|
||
The seed constructs every grid point, so LM gets no variable (a test counts them). Before, the
|
||
time was the dense rank analysis of the whole grid. A 100-rectangle strip with nothing fixed,
|
||
solved whole from a moved corner, still costs 20–27 ms: nothing is constructed there and LM
|
||
iterates on every point with dense normal equations (a sparse solve would be the next step).
|
||
|
||
Drag frames, `sketch.drag` + `world_state()` per frame (60 frames of 0.5 mm, best of 5; the
|
||
"before" column is one run of the same bench on the previous code):
|
||
|
||
| Sketch | Points | Scripted ms/frame before → after | Live ms/frame before → after | `drag_end` ms before → after |
|
||
|---|---:|---:|---:|---:|
|
||
| 100 rectangles | 401 | 2.77 → 2.37 | 1.13 → 1.02 | 1.44 → 1.37 |
|
||
| 100 rectangles, rolled back under an extrude | 401 | 2.49 → 2.33 | 1.13 → 0.97 | 1.42 → 1.28 |
|
||
| 100-rectangle connected strip | 401 | 100.9 → 2.32 | 1.32 → 1.20 | 53.6 → 1.41 |
|
||
| 100-rectangle strip, rolled back under an extrude | 401 | 203.8 → 2.23 | 1.31 → 1.18 | 54.0 → 1.42 |
|
||
| rigid truss of 40 triangles, pinned at one end, dragged at the other | 42 | — → 6.8 | — → 6.3 | — → 0.59 |
|
||
|
||
The strip floats (nothing fixed), so its full solves (scripted frames, `drag_end`) ran the
|
||
dense rank analysis on 800 variables; with one corner taken as a constant all of it is
|
||
eliminated. A live frame's held solve was already cheap: the seed places the strip and LM
|
||
returns at once. Of a live strip frame, the seed reads the constraints in about 0.15 ms, so a
|
||
per-gesture cache of that reading is not worth its invalidation (its signs depend on the
|
||
reference positions, which change every step).
|