Commit Graph
712 Commits
Author SHA1 Message Date
NOWN-DEVandClaude Opus 5.5 f34ac5881f fix: the sheet metal flat budget counts exact lines and arcs once (#430)
The flat-pattern point budget (16 384) counted every cut-out by its 0.001 mm
chords, arcs included, so a plate with 240 round 4 mm holes (about 100 chords
each, 480 exact arcs) was refused with "sheet metal flat geometry exceeds the
bounded processing budget" when the holes were cut in the unfolded sheet. The
planar booleans work on exact lines and arcs whole, so those now count once
each; a sampled curve still counts its chords. A second limit keeps the
chords of everything, arcs included, at 65 536, the same figure the planar
boolean applies to the sampling it does itself, so the work stays bounded.

planar_region no longer samples an exact loop only to discard the result: the
complexity check right above it already covers the same budget.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 18:58:45 -04:00
f290382498 fix(ui): wrap long toolbar panel labels and centre one-button panels (#409) (#410)
A panel label too wide for its panel (beside its caret) wraps between words
onto a second line; a single word wider than the panel stays whole. Each line
is centred, and the label's hover area grows to hold both lines. A panel's
buttons are centred in the panel, so a one-button panel such as Finish Sketch
lines its icon up with its label.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: bherbruck <[email protected]>
2026-10-12 18:57:20 -04:00
deb96318a1 fix: fillet fit review follow-ups (#404)
* fix: fillet fit review follow-ups

From the review of #388 (merged before these landed):
- The Fillet dialog's Applied Radius showed nothing when editing a fillet
  that was already reduced: the preview found the radius by diffing the
  states before and after. The engine's preview now keeps the command
  replies, and the dialog reads the radius of the feature they name.
- Pattern copies of a reduced fillet try the radius the original was
  reduced to first, instead of each searching again (up to 16 fillets).
- fit: reduce with a chord or variable radius is refused instead of being
  silently ignored.
- One helper appends a note to a feature's warning; too_large is imported
  like unsupported in the loop and curve blends.

Refs #293

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

* Use the solidcraft_doc crate name after the rename

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: bherbruck <[email protected]>
2026-10-12 18:57:09 -04:00
11ecb64059 feat(ui): heading bands for dialogs, sketch status label, tighter palette footer (#418)
- frame::heading draws a panel's title row (heading_bg) or a section heading
  (subheading_bg) on a band darker than the dialog, in both themes. Every
  docked command dialog, the Sketch Palette and Redefine Sketch Plane use it.
- Sketch Palette: "Options" and a new "Sketch Status" heading are section
  bands; the status line sits under its own heading.
- Sketch Palette: Finish Sketch no longer centres in the leftover height, so
  the palette ends one line below it, one line after the status.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: bherbruck <[email protected]>
2026-10-12 18:55:57 -04:00
MqriusandClaude Opus 5.5 1d7450b04c fix(doc): To Object takes the face at the pick, not the first body near it (#429)
object_height, curved_target and curved_target_any took the first solid body with any face
within its pick tolerance of the To Object point. That tolerance is 5 % of the body's size,
so a large body beside a small target took picks made on the target: a ridge extruded up
to a post 2 mm above a plate treated the plate as the target, nothing trimmed the tool, and
it ran past the post (to x 9.08 instead of -2.31).

They now share face_at: the nearest face over every solid body, still within that body's
tolerance. appearance gains nearest_face (the face and its distance) and pick_tolerance,
which face_index_at now uses as well.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 18:55:47 -04:00
MqriusandClaude Opus 5.5 968a22897c fix(doc): extrude and revolve in a moved component from another component's sketch (#403) (#407)
* fix(doc): extrude and revolve in a moved component from another component's sketch

A feature built from a sketch of another component read the sketch's plane as if it were in
the feature's own component. When the two components are placed differently (a moved or
jointed part), the tool landed transformed along with the feature's component: a join on a
part turned half a turn about Z, from a root sketch, came out on the far side of the origin.

Extrude and Revolve now see the sketch through both placements, as sweeps along 3D paths and
sketches on another component's face already do (sketch_in_feature_frame, using
profile_in_frame). The sheet-metal rotated-frames test built its cap witness from a sketch of
the moved component while the root was active, relying on the old reading; it now builds the
witness in the sketch's own component, which is what it measures against.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test: To Object extrude in a moved component from a root sketch

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* style: rustfmt

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 18:54:19 -04:00
NOWN-DEVandClaude Opus 5.5 0154572580 feat: close sheet flange corners at any angle (#406)
Edge flanges meeting at a corner of the base were closed only at exactly
90°; at any other angle they stayed open (a V at 72°) and the rule's gap
was not used.

The flanges of one feature now close at any convex base corner and any
bend angle: each panel end is cut square to the sheet on a line in the
flat (slanted off 90°, stored as ext plus a new per-end slope) so the
edges nearest each other, on the inner faces, run parallel the rule's gap
apart about the plane bisecting the inner faces. The construction reads
the two flanges' own angles, so it holds for unequal ones as well. At 90°
it is the extension the old code gave (reference case 69-sm-tray is
unchanged). A corner that can't close (a gap under 0.01 mm, flanges
bending opposite ways, ends that would overlap in the flat pattern) stays
open, and the flange feature reports a warning that says where and why.

A flange of a later feature leaves the corner as it was (reference case
75-sm-bracket-holes: the earlier flange keeps its length).

Tested at 72° and 90° against a hand derivation, on a trapezoid, and
every 5° from 45° to 135° on a rectangle and a trapezoid (gap exact, one
flat region without hairline edges, one valid shell holding the flat
material).

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 18:48:19 -04:00
Tobias BreckleandClaude Opus 5.5 0bb5b7eb09 fix(ui): Sketch Palette line type buttons follow the selected curves (#422)
With sketch curves selected, the palette's Normal / Construction / Centerline
buttons now show the line type of the selection (each type of a mixed
selection); with nothing selected they show the drawing mode, as before.
Previously no button was highlighted while curves were selected.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 18:45:19 -04:00
Nathan Dane b447adafaa Finalize macOS icon appearances and Composer sizing (#426) 2026-10-12 18:45:14 -04:00
NOWN-DEVandClaude Opus 5.5 3938e267bb fix: fail solidcraft-cli run when a timeline feature fails (#402)
A parameter change can break a feature without failing the command that
made it; parameters.change reports it only in its `errors`. `run` then
exited 0 and exported the bodies as they were before the failing feature:
a shrunk sheet-metal tray whose unfold failed came out folded, without
the holes and cut-outs made after the unfold.

`run` now checks the evaluated timeline before writing anything. Any
feature with an error fails the run with status 1, lists the failing
features on stderr and writes none of the --out and --save files, as a
failing command already does. --allow-errors writes them anyway. `eval`
is unchanged and still reports the errors in its timeline.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 18:45:10 -04:00
bherbruckandClaude Opus 5.5 daed2686bc Snap package: strict core24 snap on every release, optional Snap Store upload (#413)
* Snap package: build, test and attach a strict snap to every release

packaging/linux/snap/snapcraft.yaml (core24, strict, gnome extension) repackages
the release's Linux tarball like the Flatpak does. The release workflow builds it
for amd64 and arm64, installs it with --dangerous, models a box in the confined
app on headless X11 and Wayland, and attaches solidcraft-<version>-linux-<arch>.snap
plus the version-less copy. When SNAPCRAFT_STORE_CREDENTIALS is set, nightlies go
to edge and release drafts to candidate, and snap-store.yml promotes candidate to
stable when a release is published. The README gets a Snap badge and install line.

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

* snap smoke: stop the app's whole process group, refuse a busy control port

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

* snap smoke: quit the app cleanly between backends

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 18:07:26 -04:00
bherbruckandClaude Opus 5.5 cddbfedb28 Remove the Generative Design workspace placeholder (#412)
It isn't planned, so it no longer appears in the workspace menu's
"coming later" list. Drops ui.generative_design from every locale and
checks the menu doesn't list it.


Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 17:31:21 -04:00
bherbruckandClaude Opus 5.5 e56e055fa1 README: put a Download section with per-platform installers at the top (#411)
Badges and one-line instructions for Windows, macOS, Linux and the web app,
right after the tagline and demo. The links use releases/latest/download with
version-less file names, which release.yml now attaches (and checksums)
alongside the versioned installers. The Flatpak note and the name/compatibility
section move into the book's Getting started page.


Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 17:25:27 -04:00
bherbruckandClaude Opus 5.5 296683229d Rename SolveCraft to SolidCraft everywhere, with compatibility shims (#389)
* WIP deep rename (not for push)

* WIP2

* WIP3

* WIP4 wxs fix

* rustfmt

* Rename the remaining identifiers in merged crash-report code

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 17:17:01 -04:00
27415e4dd7 Crash reports: replayable, bounded, local, reachable by agents (#384)
Every panic (in a command, in the kernel, anywhere else) and every unclean
exit (abort, stack overflow, kill) leaves a report in a local crash folder:
message, file:line, a trimmed backtrace, version, commit, system and renderer,
the command and recent commands, and when possible the design and replay.json
that reproduce it (solvecraft-cli crashes replay <id>).

- Cost: nothing until the app installs the hook (tests, CLI and web never
  do); then one small journal line per top-level command of the design on
  screen, taken back with a truncate when the command changed nothing.
- Unclean exits: each run holds a lock on its journal; a journal nobody holds
  at the next start belongs to an app that died. Autosaves restart the
  journal from the autosaved design.
- Bounded: a crash's signature (place + message, numbers ignored) is counted,
  not rewritten; at most 5 new reports per run, 20 reports, 50 MB, 90 days;
  recovered kernel panics are pruned first.
- Agents: diagnostics.crash_reports / crash_report / crash_reports.clear
  commands, the MCP crash_reports tool, solvecraft-cli crashes, solvecraft
  --crash-log; diagnostics.crash_test (with SOLVECRAFT_CRASH_TEST) checks it.
- People: the Crash Reports window opens after a crash (Help > Crash
  Reports…) with a reviewed issue draft per report; nothing is sent.
- Release builds keep line tables for file:line in backtraces.

Tests: child processes that really panic in a command, in the kernel, on a
thread, and abort; dedup counts, limits, hostile folders, journal parsing,
the UI window and draft, the MCP tool. docs/crash-reports.md.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: bherbruck <[email protected]>
2026-10-12 16:50:49 -04:00
Nathan Dane 55f2f29fff feat(macos): integrate approved layered app icon (#399) 2026-10-12 16:39:45 -04:00
d381d2b7f3 fix: build very flat arcs in sewn faces from their ends and midpoint (#398)
kernel::sew builds an arc edge, and the wall an extruded arc sweeps, with truck's
three-point arc (circumcentre, then a turn about it). On a very flat arc that
construction ends off its own vertex: a 12 mm chord with a 0.08 um sagitta (a 225 m
radius) ends about 1e-6 mm short of it, and the planar face holding the arc, 1238 mm
from the origin, fails with "cannot attach a plane to a wire that is not on one plane".
truck's plane fit takes the sum of the wire's control points as its centre, so a gap
of that size is scaled by a distance of the order of the coordinates and tilts the
estimated normal beyond the fit's 1e-6 flatness tolerance.

Arcs whose sagitta is under 1% of the half chord are now built as their exact rational
quadratic (apex 2/(1-t^2) along the bow, weight (1-t^2)/(1+t^2), t the sagitta over the
half chord), for edges and for the extruded walls on them. Deeper arcs keep truck's
construction.

Co-authored-by: NOWN-DEV <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 16:39:42 -04:00
c62879b00d feat: fillet fit option reduces a radius too large for its edges (#388)
* feat(kernel): typed TooLarge refusal and fillet_max search

Blend refusals caused only by the size (larger than the neighbouring faces,
taller than the walls, longer than the next edge, larger than a corner
radius) now return KernelError::TooLarge, displayed as before. fillet_max
finds the largest radius below a request at which the fillet of the edges
works, to three significant digits, by building candidates; it is an explicit
call (up to 16 fillets), not run on every refusal.

A corner edge that doesn't run square to the edge is now reported as such
instead of as a blend larger than the neighbouring faces.

Refs #293

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

* feat: fillet fit option reduces a radius too large for its edges

solid.fillet takes fit: exact (the default, as before: a radius too large
for the geometry around the edges is refused) or reduce: the radius is
reduced to the largest that works for all the picked edges (kernel
fillet_max, three significant digits, every candidate built). The feature
stores the requested radius, so it refits on every evaluation; when it
reduces, the feature's warning says so and the command returns
radius_applied. Pattern copies of the fillet use the same rule. The file
format writes fit only when it is reduce.

The Fillet dialog gets a Radius Fit row (Exact / Reduce to Fit, constant
radius) and, when the preview reduced the radius, an Applied Radius line;
editing the feature keeps the choice.

One radius for all edges for now; per-edge radii and taper follow.

Refs #293

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]>
Co-authored-by: bherbruck <[email protected]>
2026-10-12 16:21:17 -04:00
Konrad 3c94f59d43 feat(ui): trackpad pan with two fingers, zoom with pinch (#386)
Two-finger scroll pans the model instead of zooming; pinch-to-zoom
zooms toward the cursor and honors reverse zoom. Trackpad scrolls
arrive bracketed (Start..End, momentum included) while mouse wheels
send bare moves, so phases tell them apart: bracketed gestures pan,
bare vertical input zooms and bare horizontal input pans (thumb wheel).
Mouse line/page input keeps zooming, and ui.scroll now sends line
input so scripts and agents are unaffected. New Navigation preference
'Two-finger scroll pans' (default on) restores the old behaviour
when off.
2026-10-12 16:15:22 -04:00
Jonas Wrede 829912c6c5 perf: cut many clear tools of any cut in one boolean (#368) (#387)
One extrude cutting many circles from one sketch still cut the plate once per
circle, each boolean rebuilding and re-meshing a plate grown by every hole before
it (32 circles 2 s, 128 circles 60 s). The #261 fix joined clear tools with
kernel::union_apart only in pattern replay; apply_traced now does it for every
cut with 8 or more tools (patterns included), keeping each piece's face lineage.
2026-10-12 16:09:46 -04:00
bherbruckandClaude Opus 5.5 6090e89b3f Release under the SolidCraft name: binaries, assets and installers (#383)
* Release under the SolidCraft name: binaries, assets and installers

The desktop binary is now `solidcraft` and the CLI `solidcraft-cli` (the two app packages
are renamed; their folders move with the rest of the rename later). Every release asset is
named solidcraft-<ver>-… / SolidCraft-<ver>-windows-x64.exe, and the installers show the
SolidCraft product name.

Kept so existing installs keep updating (docs/compatibility.md):
- `solvecraft-cli` alias binary in every package, forwarding to solidcraft-cli with a
  deprecation note on stderr (tests/legacy_alias.rs).
- Linux app ID ai.storyteller.solvecraft (Flatpak ID, desktop entry, Wayland app_id).
- MSI UpgradeCode, so the MSI upgrades in place.
- deb Replaces/Conflicts/Provides and rpm Obsoletes/Provides solvecraft.
- An old-named .zsync copy so earlier AppImages find updates; a legacy .flatpakrepo copy.
- .solvecraft file associations; SOLVECRAFT_* fallbacks in the packaging scripts.

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

* MSI: register the legacy .solvecraft extension without a duplicate open verb

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
v0.5.0
2026-10-12 16:05:35 -04:00
Jonas Wrede f06b76e14f fix: number a profile added after a named multi-profile extrude after body_name (#373) (#385) 2026-10-12 16:04:54 -04:00
Jonas Wredeandbherbruck 4b8872e4d0 test: failing regression test for #374 (ignored until fixed) (#382)
Co-authored-by: bherbruck <[email protected]>
2026-10-12 15:43:56 -04:00
Jonas Wredeandbherbruck e7b5b2618e test: failing regression test for #373 (ignored until fixed) (#381)
Co-authored-by: bherbruck <[email protected]>
2026-10-12 15:35:32 -04:00
Jonas Wredeandbherbruck ef9bb0db09 test: failing regression test for #371 (ignored until fixed) (#379)
Co-authored-by: bherbruck <[email protected]>
2026-10-12 15:34:41 -04:00
Jonas Wrede f538eeb5fa test: failing regression test for #372 (ignored until fixed) (#380) 2026-10-12 15:25:36 -04:00
Jonas Wrede d470bfc52a test: failing regression test for #370 (ignored until fixed) (#378) 2026-10-12 15:25:32 -04:00
Jonas Wrede f82b6beedb test: failing regression test for #368 (ignored until fixed) (#377) 2026-10-12 15:25:27 -04:00
bherbruckandClaude Opus 5.5 80672460ed Version 0.5.0 (#376)
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 15:17:00 -04:00
bherbruckandClaude Opus 5.5 57e6396fbb Tangent hole contacts split no faces, consistently (#307) (#364)
* Tangent hole contacts: every symmetric contact gives the same faces (#307)

Three things made the four symmetric contacts of reference case 42 differ:

- The 2D prism boolean's ray-parity test miscounted a ray grazing the bottom
  of a patterned hole, whose two halves are read back with centres a few ulps
  apart: one half counted the touch, the other missed it. The loop chaining
  then failed and those cuts fell back to the general boolean, which treats
  circles tangent within its tolerance as overlapping and splits the hole.
  The ray is now moved off vertices and arc extremes by far less than any
  modelling tolerance.
- The prism result's volume check used a coarser mesh than the operands, so
  a plate with many holes read too large and correct results were rejected.
- The merged topology did not join a hole's two pieces across the line where
  two tangent holes touch (four faces meet there).

Case 42 now gives 39 faces (33 holes, 6 planes); its expected.json pins it.

Fixes #307

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

* Kernel test: tangent hole pinch no longer splits the wall (#307)

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 15:01:03 -04:00
Iraklis KourtisandClaude Opus 5.5 a5cc1243e3 fix(doc): a pattern repeats a fillet on the edges it found, not its picks (#363)
A rectangular or circular pattern of a fillet or chamfer repeated it at the
points picked when the feature was made. The feature itself finds its edges
by name on every evaluation, but a parameter that moves the edges (a taller
pocket) leaves the old points on other geometry, and the copies then
blended the wrong edges or failed ("the faces at the edge ends must be
planar"). The model state now records, per fillet or chamfer, the edges it
blended in this evaluation (part of the state's digest), and the pattern
repeats those.

Fixes #219


Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 14:58:52 -04:00
Iraklis KourtisandClaude Opus 5.5 afcec4d0bd fix(kernel): fillets that end in faces at an angle or across two faces (#362)
A straight-edge fillet required each end of the edge to meet exactly one more
face, square to the edge. Now an end may meet one planar face at any angle
(the round ends in it along an exact ellipse, the cross-section slid along
the edge onto the plane) or two planar faces meeting at the end vertex: the
round ends across both and cuts back the edge between them where its
cylinder crosses it (Vida, Martin and Varady 1994: trimlines joined across
existing faces). The cylinder is swept to cover both trimmed ends. Square
ends are built as before.

This rounds the mitres where two chamfers meet at a plate's corner, and a
concave edge where a post stands on a plate (the round's end caps extend
the post's end faces); both were refused.

Fixes #215


Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 14:56:00 -04:00
Vitali M.andVitali M. 6efbd6ff14 Place a dimension that would over-constrain the sketch as a driven one (#366)
Sketch Dimension refused a dimension the sketch couldn't take, with
"that dimension would over-constrain the sketch; add it as a driven
(reference) dimension with driven: true", which the tool gave no way to
do. It now places it as a driven dimension instead: while placing, the
preview shows it in parentheses in the driven colour, and the click adds
it at once (it has no parameter and no value to edit), with a status line
saying why. Other refusals, such as an unsupported pick, are reported as
before, since the driven dimension is refused too.

Refs #320

Co-authored-by: Vitali M. <[email protected]>
2026-10-12 14:54:34 -04:00
Pablo López c51735c885 Toolbar drop-downs scroll when taller than the window (#360)
A panel drop-down taller than the window ran past its bottom edge, so the
last rows (Sketch → Create → Intersect and below) could not be reached. The
rows now scroll inside the window, and a row reached with the arrow keys
scrolls into view. Row ids no longer depend on the enclosing Ui, so the
keyboard focus still finds them inside the scroll area.
2026-10-12 14:38:27 -04:00
Nathan Dane 4eaadf3099 fix(macos): package the app as SolidCraft (#347) (#354) 2026-10-12 14:37:35 -04:00
bherbruckandClaude Opus 5.5 882ef0e9c6 Face counts: classify sliver faces from their exact surface (#308) (#357)
Since 4e595de a partial-depth cut that ends inside a cylinder runs through the
exact stacked-section path, which leaves 0.3 mm wide strips of the cylinder wall
beside the cut. Their tessellation is one triangle wide, too few distinct
samples for the cylinder fit, so the merged topology reported them as a sphere
and a freeform face. Where the mesh fit finds no plane or cylinder, use the
face's exact surface (plane, cylinder or sphere) instead.

Fixes #308


Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 14:35:13 -04:00
bherbruckandClaude Opus 5.5 d15869ab85 CI: shard tests across 4 runners; heavy tests on main and nightly (#350)
* CI: build tests once and run them in 4 nextest shards

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

* CI: quote the shard arguments

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

* CI: extract the test archive into the checkout; skip the heaviest tests on pull requests

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

* Docs: CI shards and the tests pull requests skip

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 14:30:35 -04:00
Pablo López 511d105dcb Project faces split by hidden seams as a whole (#352)
Projecting a cylinder's wall or the face of a round along a circular edge
failed with "face projection has an open or invalid boundary": these
surfaces are stored in halves joined by hidden seams, and the projection
dropped the seams but kept only the picked half. It now projects the whole
patch, the face as drawn, through the same surface_patch the viewport uses
to highlight it.
2026-10-12 14:29:08 -04:00
bherbruckandClaude Opus 5.5 2ebceb6918 Sheet metal: sew the folded-material guard per piece so adjacent flanges cut (#296) (#355)
The guard that refuses machining through bends sewed every bend zone and
nonparallel panel into one solid. Two bends meeting at an open corner relief
share only an edge, so that sew failed with "nonmanifold edge contact" and any
cut on the base was refused. Sew and test each piece on its own instead.

Reference case 75-sm-bracket-holes now passes; its known_failure mark is removed.

Fixes #296


Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 14:27:40 -04:00
7db66bb824 Pick a horizontal or vertical dimension from where its text is placed (#345)
Between two points, or on a line, that don't line up with an axis, the
Sketch Dimension tool only offered the distance between them: an auto
dimension ignored where its text went. Now the placement picks what it
measures: across from the two (between the perpendiculars through them)
the distance between them, as before; past that, above or below them the
horizontal distance, beside them the vertical one. With no text, an
explicit type, or two ends already on an axis, nothing changes.

The tool previews the dimension from the cursor, so the choice follows
the pointer while placing it.

Fixes #257

Co-authored-by: Vitali M. <[email protected]>
Co-authored-by: bherbruck <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 14:18:11 -04:00
a7e47fdae6 fix: unfold sheets shrunk by a parameter change (#349)
A sheet-metal tray built at 2000 x 2500 mm with an edge flange on all four
edges (72° or 90°) and unfolded failed to rebuild when W and H went to
400 x 300, 600 x 450, or W alone to 400: "planar region arrangement failed
or exceeded its bounded processing budget". The same tray drawn at 400 x 300
unfolded, and growing it worked.

The sketch solved again after the change leaves its corners ~1e-13 mm off,
so the flanges inherit a hair of tilt and their panels and bend zones meet
the trimmed base within 1e-13 mm instead of exactly. The flat pattern is a
balanced union of those pieces, and the arrangement chains its output loops
within 10 tol. In a later round, ray parity classified a bend zone's edge
midpoint that lay level with a corner whose two lines did not quite meet:
the ray slipped through the gap without a crossing, the zone's edges counted
as inside the other operand and were dropped, and the outline no longer
closed. A rectangle turned by 1e-14 rad failed the same way.

inside_loop now closes the gap where one line ends and the next begins with
a straight connector while it counts crossings; exactly closed joints are
unaffected. Joints with an arc keep the arc's own endpoint rule, which
treats ends within its angular tolerance as on the ray (a connector there
double counted at a circle split into two arcs).

Co-authored-by: NOWN-DEV <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 14:15:55 -04:00
bherbruckandClaude Opus 5.5 ce226a88eb Regression tests for the XZ plane orientation (#78): a positive extrude from XZ and from an XZ offset plane goes +Y; a format 5 design with XZ sketches, an offset plane and extrudes opens, and resaves, with identical volumes and bounding boxes (#353)
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 14:10:48 -04:00
0b122e6d4a Support 3D mice on Linux and devices that send all six axes in report 1 (#343)
The space_mouse module was Windows-only (hidapi). On Linux it now finds the
multi-axis HID interface through /sys/class/hidraw and reads /dev/hidrawN with
plain std I/O, so there is no new dependency and no libudev needed.

A small report descriptor parser tells devices apart: a top-level Multi-Axis
Controller collection marks the 3D mouse, and report 1 declaring all six axes
selects the combined layout (X Y Z Rx Ry Rz in one report) that some homemade
devices use. Real split devices (report 1 translation, report 2 rotation) keep
their existing decoding. Windows reads the descriptor through hidapi and falls
back to the split layout if it is unavailable.

Co-authored-by: jochenhammes <[email protected]>
Co-authored-by: Claude Sonnet 5.5 <[email protected]>
2026-10-12 13:55:54 -04:00
Vitali M.andVitali M. b2fc6d1fac Keep a zoomed-in profile's hover fill: count the slabs each edge crosses (#336)
fill_triangles bounded the slab triangulator by edges x slabs, as if every
edge crossed every slab. A ring sampled finely at a close zoom has hundreds
of edges and slabs but only four edges in each slab, so the estimate ran a
hundred times over the hover highlight's budget and the fill was dropped,
leaving only the outline. Count the slabs each edge actually spans instead.

Region2::triangulate tested every edge against every slab, so its work was
the overestimate. It now sweeps upward with the edges crossing the current
slab, giving the same triangles in time proportional to them.

Fixes #164

Co-authored-by: Vitali M. <[email protected]>
2026-10-12 13:47:11 -04:00
Tobias BreckleandClaude Opus 5.5 e6a95615a0 fix(ui): start with an anti-aliasing the graphics device can't do (#329)
Choosing 8x anti-aliasing in Preferences › Graphics on a device that
only allows 1x and 4x for the window's format made every later start
panic in wgpu's pipeline validation (`Sample count 8 is not supported
by format Bgra8Unorm on this device`), so the app could not be opened
again without editing graphics.json by hand.

- egui-wgpu (vendored): request TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
  when the adapter has it (WebGPU only guarantees 1x and 4x), and clamp
  the sample count to the largest one the device supports for both the
  colour target and the depth/stencil format. The supported counts are
  exposed as `RenderState::msaa_supported`.
- At start, a saved count the device can't do goes back to the default
  (4x, or the largest supported count below it), is written back, and a
  "Settings were changed" notice says what changed.
- The Anti-aliasing list greys out unsupported counts, `ui.prefs`
  rejects them, and an invalid saved `msaa` loads as the default.
- New messages translated in en, de, ja and ko.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 13:46:54 -04:00
palaniraja 6dab0fb2c1 openscad navigation (#211)
* openscad navigation

* addressed pr feedback: fixing box selection conflict with openscad and use constant for openscad
2026-10-12 13:46:40 -04:00
Iraklis KourtisandClaude Opus 5.5 a04ef4ca59 T-spline core library and sheet bodies (#250, step 1) (#337)
* Add B-spline basis, knot insertion and NURBS surfaces to geom

Freeform faces need local refinement and gap-free joins between patches
with different knot vectors, which tensor B-spline grids cannot give;
T-splines can, and the first user is an N-sided Boundary Fill of
non-planar loops. This lays the univariate groundwork they rest on, in
the L0 geometry crate so it stays independent of the B-rep kernel:

- Cox-de Boor basis of one function from its local knot vector, on
  half-open spans with a left-limit side for the domain end, with
  derivatives (NURBS Book eq. 2.5, 2.9)
- knot insertion into a single basis function (Sederberg et al. 2004
  section 3) and Bezier extraction of one function by repeated insertion
- Boehm knot insertion for curves (NURBS Book A5.1), de Casteljau
  evaluation and sub-segments
- a rational tensor NURBS surface with evaluation, knot insertion and
  clamping, and a rational Bezier patch type

All input is checked (finite, monotone knots, positive weights, size
caps) and returns SplineError; nothing indexes unchecked.

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

* Add T-spline surfaces: T-mesh, evaluation, AS check, refinement, extraction

Tensor B-spline faces refine only by whole rows and columns and cannot
join patches with different knot vectors without gaps. T-splines allow
T-junctions in the control net, which fixes both; the first user is an
N-sided Boundary Fill. This adds the maths only (no command, no change
in behaviour):

- T-mesh in index space with knot values per line, validated (outer
  rectangle, rectangular faces, no empty lines, size caps); local knot
  vectors read off rays from each vertex, rays leaving the mesh repeat
  the boundary (Sederberg et al. 2003 section 3)
- rational T-spline evaluation and first derivatives for odd bi-degree,
  with a complete (p-1)/2-line frame and the domain inside it
- T-junction extensions and the analysis-suitability check (Scott et al.
  2012 section 2)
- local refinement that keeps the surface exactly: requested knot lines
  are cut in, then every original blending function is carried as a
  record and split (violation 1) or the mesh gains edges (violations 2
  and 3) until each record is a blending function of the refined mesh
  (Sederberg et al. 2004 section 3); analysis-suitable input stays
  analysis-suitable by carrying conflicting T-junctions across faces
- NURBS <-> T-spline conversion for meshes without T-junctions, and
  Bezier extraction over the rectangles no blending-function knot line
  crosses

Measured: regular T-spline vs NURBS < 1e-12, round trip exact, partition
of unity with T-junctions < 1e-14, refinement deviation <= 1.4e-14 over
40 random refinements, Bezier patches vs surface < 1e-14.

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

* Convert T-splines to sewn NURBS sheet bodies in the kernel

A T-spline is only useful to the modeller once it is a B-rep face set.
tspline_sheet builds an open shell: a mesh without T-junctions becomes
one clamped NURBS face; otherwise each Bezier element is a face, and
sides are split at the T-points where neighbours' corners land, so all
faces share vertices and edges and the sheet is one oriented shell
(V - E + F = 1). Every truck call runs under guard. No command yet;
Boundary Fill will be the first caller.

Tests: one face for a regular surface, sewn Bezier faces with
T-junctions, tessellated points on the surface to ~1e-9 mm, and hostile
control nets (coincident, 1e300, needle-thin) never panic.

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

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 13:46:22 -04:00
Iraklis KourtisandClaude Opus 5.5 8e87a37511 feat(sketch): constructive solving ahead of the numeric solver (#339)
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


Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 13:46:08 -04:00
Iraklis KourtisandClaude Opus 5.5 fe4d171e22 feat(sketch): live drag that keeps its solution, ended by sketch.drag_end (#341)
* 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]>
2026-10-12 13:45:48 -04:00
bherbruckandClaude Opus 5.5 75a5609b23 docs/releasing.md: refresh Cargo.lock with cargo update --workspace --offline (#348)
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 13:43:42 -04:00