A curve written over a knot span of 1e-7 or less was refused on the STEP side
("knot vector consists single value"), and both faces it bounds were lost. Its
knots are moved and scaled onto (0, 1), which leaves its shape as it is.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
cone_point and cone_point_vertex join the sweep now that #3871 reads a cone
bounded only by its base circle up to its point.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix recovery for unsaved background tabs (#2884)
* Recovery: scheduled autosaves of every tab write on the worker thread (#2884)
After #3139, Autosaver::save waits for the write and Autosaver::tick runs
it on a worker. The scheduled path now goes through tick (which skips a
tab whose previous write is still running), so several dirty tabs no
longer write on the window thread each interval (#3044). save stays for
Save, close and shutdown. Each tab's autosaver has no clock of its own:
the shared cadence decides when it is due. The tests wait for the
writes a tick started.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Recovery: closing a tab waits for its running autosave before removing the entry (#2884)
With the autosave on a worker thread, discard could remove the files and
the lock while a write was still running; the write then recreated the
entry without a lock, and the next launch offered a design closed with
Don't Save. discard now ends the running write first, as Drop does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A BREP_WITH_VOIDS void's faces face into the cavity, so it encloses volume of
the opposite sign to the outer shell. Its ORIENTED_CLOSED_SHELL flag alone
doesn't tell: SOLIDWORKS writes .F. over a shell already facing into the
cavity, SolidCraft .T., and flipping by the flag added the cavity's volume. A
void enclosing volume of the outer shell's sign is now turned inside out.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* STEP import: regression sweep over the step-conformance parts of known shape
Fifteen generated parts from step-conformance (CC0): plain, NURBS, seamed,
placed, inch and far-away cylinders, domes, a grooved shaft, a plate with 100
holes, a 3.6 m disc, a 0.5 mm pin, a B-spline half-torus and a washer. Each
reads back with its exact volume, its box and its face count. The half-torus
on a TOROIDAL_SURFACE and the cones to a point are left out until they read
right.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* STEP conformance sweep: the pin's volume as FRAC_PI_8 (clippy)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A cone face written with no vertex at its point has the base circle as its only
loop (or that circle and a VERTEX_LOOP at the point, which has no edges). Its
surface was spanned over the circle alone, a sliver, so a solid cone came in as
a flat disc. Such a face is now spanned to the apex, and its circle is joined to
the apex by a seam line out and back, as a cone with a seam is written.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Boolean: a small hole in a large body's face stays a hole when the face is divided
A boolean divides faces at a tolerance that follows the larger operand (a few
millimetres on a part a few metres long). A small hole's rim, two half circles
when read from STEP, then became two chords: its polygon in the face's
parameters had no area, and the sign that tells a hole from a face piece came
out at random. The hole became a face of its own, inside out, and the face it
was cut in stayed whole. A box cut round such a hole gave a piece without it,
and the shared part of that piece and a pin in the hole came out negative
("malformed") or the boolean failed.
divide_face now divides a loop whose polygon is flat again, finer (down to a
hundredth of the loop's own size), before reading its sign. Only the polygon
that places the loop changes; loops that have an area are untouched.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Kernel: the long-plate hole test in tests.rs; a flat loop's size from its box
Review: the test moves into the kernel's test file (no file of its own),
and finer_if_flat takes the loop's size from the diagonal of the box round
its sample points instead of the largest distance between any two.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aac7mP9rthK3WsPpPRvCsF
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* claim: #3358
* IGES import: trim curves with reversed pieces no longer drop their faces
A curve on a surface (142) gives each trim loop twice: B in the surface's
parameter space and C in model space, running the same way. Some exporters
write C as a composite curve (102) whose pieces are in order but some of
them end to start. The reader passed those pieces on as they were, so the
loop did not close and the STEP reader left the face out ("face boundary is
not a closed loop").
The pieces of C are now turned to run head to tail, starting at S(B(start)),
the point where the parameter-space curve begins on the surface. That keeps
the loop's sense from the file, so holes still run against the outer loop.
A piece whose ends are one vertex at the sewing tolerance keeps its
direction.
On a 9,276-face trimmed-surface car model, 7,887 faces now import instead
of 3,882.
A surface of revolution (120) wrote its arc generatrix as a whole STEP circle, which
turns into a whole sphere twice over when the arc is centred on the axis; the trim could
not pick the cap out of it and the face meshed as the full ball. An arc in a plane through
the axis now comes in as a sphere or torus, any other arc as an exact rational B-spline of
just the arc.
Hovering an edge lights its circular rim, which starts from the edge's
tangent chain. That looked at every edge of the mesh for each edge in the
chain (and searched the chain itself each time), so a long helical edge in
many pieces, a thread's crest, took seconds: 7.6 s inside the frame on the
first hover of the stem's thread in a real watch-movement STEP file. The
edges are now found by the grid cells of their ends, two join tolerances
across, so each step looks only at the edges that end near it: the same chain
in the same order, in 29 ms for that thread.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #2757
* fix(params): autocomplete offers a parameter for any part of its name (#2757)
Typing `side` now offers `cab_side_thickness`: parameters match the word
anywhere in their name, after those that start with it, both in the expression
boxes and in parameters.complete. Functions, constants and units still match
from the start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
The first hover over a large body made two things in the frame: the body's
pick index (its triangles and edges in hierarchies; about 0.3 s for a long
modelled thread, a quarter of a million triangles) and, with the GPU viewport,
the plan mesh a highlighted face is drawn from (about 0.7 s for the same
thread). The window froze for the sum.
A large mesh's pick index is now made on a worker, and picks scan the mesh
until it is there (a few milliseconds); a body's plan mesh is made on a worker
too (`ModelBody::plan_mesh_soon`), and until it is there the hovered face is
lit on the mesh the viewport already has. Small meshes, and the browser build,
make both at once as before.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* ui: spacing, radius and type tokens, per-tab accents, agent and on-accent tokens
* ui: per-tab accent behaviour and a larger application bar with a Start page button
* icons: silhouette master and flat assistant and agent link icons (open for review)
* icons: apply review notes (sparkle assistant, refined plug, silhouette halo)
* icons: wire the silhouette master to inspect.silhouette; record the accepted icons
* ui: application bar to the design: separators, Save, house, assistant icon, readable hovers
* ui: ribbon structure: Model to View tabs, contextual Sketch and Sheet Metal, migrated pins
* ui: ribbon painting to the compact design
* ui: smaller icon button highlight and assistant mark on the bubble corner
* ui: short ribbon labels that wrap at spaces
* ui: ribbon buttons fit their labels and Finish Sketch shows its full name
* ui: constraint gallery, workspace menu, section list headings and richer tooltips
* ui: lighter selected tab label, ribbon surface and dimmer bar separators match the dark design
* ui: document tabs as rounded chips with a dimmer inactive label
* ui: document tabs fit their names and show close only when active or hovered
* icons: close and plus masters for the document tabs (open for review)
* ui: centre the new-tab plus and overflow button on the bar
* ui: a quiet rule under the ribbon tabs on non-contextual tabs
* ui: the application bar mark separates its faces with the bar colour, not white
* ui: open ribbon section label uses the selected tint and a chevron
* ui: dim the tab close and plus icons until hovered
* ui: every ribbon dropdown uses the chevron, the workspace card included
* ui: the active document tab uses the design's surface colour
* ui: the bug button's right padding mirrors its icon side
* ui: square constraint cells and a rule before the grid
* ui: constraint cells back to their earlier size, keeping the rule
* ui: smaller constraint cells and icons, centred in the section
* ui: constraint cells fit the height of the large buttons' icon and label
* ui: sketch Create titles share the other labels' line
* ui: format the application bar and ribbon changes
* ui: active tab surface token and the colour and hover tests that follow the redesign
* tests: the merged parameter and narrow toolbar tests follow the compact ribbon
* i18n: the Malay Gap label has an English source and its Japanese and Chinese translations
* apply_off_thread tests: the evaluation lingers in tests, so still computing never depends on machine speed
---------
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* perf: renaming a feature evaluates nothing (#3044)
A feature's name labels it and builds nothing, but a timeline rename evaluated
the whole design again (about 70 s on the Prusa Core One assembly, and again
for undo). When the only change is one feature's name, the model renames its
result, sketch and construction geometry in place and takes the renamed
design's fingerprints. A rename other features follow (a plane a sketch is on)
or of a failed feature still evaluates the design.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Guard cheap commands on designs with thousands of bodies, plus a scaling bench (#3044) (#3121)
Cheap commands stay cheap on designs with thousands of bodies, with a scaling bench.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Save As and Open use the browser's file dialogs where the File System Access API exists; Save writes back to the same file, its handle kept in IndexedDB across reloads. File > Download design added. Other browsers keep storing designs in OPFS.
A closed loop whose joints meet a few nanometres apart along their parallel
axes (faces modelled that far apart) passes the conflict check, but the loop
solver wanted 1e-7 mm before it moved anything. No value of a planar loop
takes such a gap out, so every drive of it stopped at once (stopped_by_loop,
fraction 0) and joint.reach called the loop open.
The solver now closes such a loop as far as it comes and, when what is left
is within the conflict check, keeps it that closed on the way. joint.drive,
joint.reach and the nearby pose of the dof count share it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Tessellation: trimmed faces of display meshes stay on their surfaces
Display meshes triangulate a trimmed face in parameters scaled by the
surface's speed along each, so a strip stretched along one parameter is
not fanned across, and every boundary segment stays a constraint: one that
crosses another is split where they meet. Boolean meshes keep truck's way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
* Tessellation: one boundary rule for every mesh; drop the #1648 retry
A boundary segment that crosses one already added is split where they
meet in Boolean meshes too, which keeps a pinched face (#1648) whole
without the snap-and-retry. Faces whose segments all go in mesh as
before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM
* Robustness goldens: seeds 1, 23, 55, 64 and 86 re-recorded on current main for #1653
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
* Thread preview test: wait for the stale fit error to clear before the next preview
After switching back from M6x1.3 to M8x1.3, wait_feature_preview stopped at the M6 error still on
screen when the new preview started a few frames later (seen on Linux CI with #1653's meshing).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
* Robustness goldens: the slow seeds 14, 20 and 40 re-recorded for #1653 from the merge queue
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
---------
Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>